
	LOCALS			;; Enable local labels
	P386

        IDEAL                   ;; Use Turbo Assembler's IDEAL mode
	JUMPS

SMALL_MODEL	equ	0   ;: True if declaring C procedures as near.
			; It is false here because all procedures are
			; far, so that you can link any memory model
			; to theme. (They are prototyped as well.)


        INCLUDE "PROLOGUE.MAC"          ;; common prologue
	INCLUDE "386.MAC"


SEGMENT  _TEXT PARA PUBLIC 'CODE'               ;; Set up _TEXT segment
        ENDS

	ASSUME	CS: _TEXT, DS: _TEXT, SS: NOTHING, ES: NOTHING

SEGMENT _TEXT

Macro	CPROC	name		; Macro to establish a C callable procedure.
	public	_&name
IF	SMALL_MODEL
Proc	_&name	near
ELSE
Proc	_&name	far
ENDIF
	endm


Struc	MODSPEC

sound	dd	?	; segment of sound mod. (on paragraph boundary!)
sndloc	dw	?	; Offset portion, currently at.
sndlsg	dw	?	; Unused, segment portion, sam asn sound segment.
sndlen	dw	?	; Length of this mod sample.
silence dw	?	; Amount of silence, between samples.
silout	dw	?	; Number of silence samples remaining from before.
freq	dw	?	; fixed decimal frequency modulator.
oneshot db	?	; True if sample is one shot.
trigger db	?	; True if sample is to be triggered.
buff	dd	?	; address of buffer stream being built.
bufflen dw	?	; Length of output buffer.
volume	dw	?	; Volume of mod.
lastvol dw	?	; Last volume of mod.
voltab	dw	256 dup(?)	; Address of precomputed volume table.

	Ends

WorkMod MODSPEC <>		; Working modspec area, current mod.

CHANNELS	equ	4	; Number of physical channels.


CPROC   BuildModStream
	ARG	MODS:DWORD
	PENTER	0
	PushCREGS

	lds	si,[MODS]	 ; Get address of MOD.
	xor	ebx,ebx 	; Zero out entire EBX register.
	mov	bx,[ds:(MODSPEC si).volume]	; Get current volume
	cmp	bx,[ds:(MODSPEC si).lastvol]	; Same as last volume?
	je	@@VOK			; yes, volume table Ok!
	mov	[ds:(MODSPEC si).lastvol],bx ; Update, new last volume.
	lea	di,[ds:(MODSPEC si).voltab]  ; Get address of volume table.
	mov	ecx,-128	 ; from -128
	ALIGN	2
@@GO:	mov	eax,ecx 	; get the current scale value.
	mul	ebx
	sar	eax,(8-4)	    ; ok have signed 'word'.
	mov	[ds:di],ax
	add	di,2
	inc	ecx
	cmp	ecx,128
	jl	@@GO
@@VOK:
	lds	si,[MODS]	 ; Get address of MOD.
	push	cs
	pop	es
	lea	di,[WorkMod]
	mov	cx,SIZE WorkMod/2
	rep	movsw		; Copy into our area.


	push	bp

	lds	si,[cs:WorkMod.sound]	; Get base sound location.
	mov	bp,si			; into BP
	add	bp,[cs:WorkMod.sndlen]	; Ending mod location.
	mov	si,[cs:WorkMod.sndloc]	; Get current sound location.
	les	di,[cs:WorkMod.buff]	; Buffer being built.
	mov	cx,[cs:WorkMod.bufflen] ; Length of output buffer stream.
	xor	ebx,ebx 		; Zero out all 32 bits of EBX
	lea	bx,[cs:WorkMod.voltab]	; Volume table

	cmp	[cs:WorkMod.trigger],0	; Channel active?
	je	@@EMPTY
	cmp	[cs:WorkMod.oneshot],0	; Is this a one shot sample?
	je	@@NOTONE		; no, ignore...

@@EMPTY:push    di
	push	cx

	mov	ax,0

	ALIGN	2
@@SND:	mov	[es:di],ax		;; Build null buffer.
	add	di,CHANNELS*2		;; skip to next entry.
	CLOOP	@@SND
@@NL:
	pop	cx
	pop	di
	cmp	[cs:WorkMod.trigger],0	; Channel active?
	je	@@don

@@NOTONE:

;; On entry to loop:
;;	AX ->scratch.
;;	BX ->base address of volume translate table.
;;	CX ->number of bytes in destination buffer to fill.
;;	DL ->low byte fractional down sample.
;;	DH ->accumlates downsample count.
;;   DS:SI ->source mod address.
;;   ES:DI ->destination buffer address.
;;	BP ->ending mod offset.

;; Silence check.
@@SCHK:
	mov	dx,[cs:WorkMod.silout]	; Silence remaining from last time?
	or	dx,dx		; Any silence remainging to be sent?
	je	@@SDON		; no, send data as one to one.
	mov	ax,0

	ALIGN	2
@@SIL:	mov	[es:di],ax
	add	di,CHANNELS*2
	dec	dx		; Decrement silence remaining.
	jz	@@SDON
	CLOOP	 @@SIL

	mov	[cs:WorkMod.silout],dx ; Silence remaining.
	jmp	@@don
@@SDON:
	mov	[cs:WorkMod.silout],0	; finished.
	mov	dx,[cs:WorkMod.freq]	;
	cmp	dx,256			; 1:1 frequency transposition?
	jb	@@DOWNIT
	ja	@@UPIT
	xor	eax,eax 	; Zero out all of EAX

	ALIGN	2
@@ONE:	xor	ah,ah		; Zero high byte.
	mov	al,[ds:si]		; Get source byte.
	inc	si			; Advance source index.
	mov	ax,[cs:ebx+eax*2]	; Translate against volume table.
	mov	[es:di],ax		; Store result.
	add	di,CHANNELS*2		; Advance
	cmp	si,bp	; Past end of mod?
	je	@@WRAP0 ; no, keep going.
	CLOOP	@@ONE	; continue.

	jmp	@@don
@@WRAP0:
;;**	    sub     si,bp
	mov	si,[word cs:WorkMod.sound]
	mov	ax,[cs:WorkMod.silence]
	mov	[cs:WorkMod.silout],ax
	cmp	[cs:WorkMod.oneshot],1
	jne	@@SCHK
	mov	[cs:WorkMod.trigger],0
	jmp	@@don

@@UPIT: mov     al,dh
	xor	ah,ah
	mov	[cs:MODIFY+2],ax		; Self modify this code.
	xor	dh,dh
	xor	eax,eax
	jmp	@@cnt2	; Force cache reload!!!!!!!!
	ALIGN	2
@@cnt2: xor	ah,ah
	mov	al,[ds:si]	     ; Get sample.
	mov	ax,[cs:ebx+eax*2]	  ; translate against volume.
	mov	[es:di],ax
	add	di,CHANNELS*2
	add	dh,dl
LABEL	MODIFY	WORD	; This Add with Carry instruction is modified.
	adc	si,1234h
	cmp	si,bp	; Past end of mod?
	jae	@@WRAP1 ; no, keep going.
	CLOOP	@@cnt2	; continue.

	jmp short @@don

@@WRAP1:
;;**	    sub     si,bp
	mov	si,[word cs:WorkMod.sound]
	mov	ax,[cs:WorkMod.silence]
	mov	[cs:WorkMod.silout],ax
	cmp	[cs:WorkMod.oneshot],1
	jne	@@SCHK
	mov	[cs:WorkMod.trigger],0
	jmp	@@don

@@DOWNIT:
	xor	dh,dh	; Inverse sample
	xor	eax,eax
	ALIGN	2
@@cnt:	xor	ah,ah
	mov	al,[ds:si]	;lodsb		 ; Get sample.
	mov	ax,[cs:ebx+eax*2] ; Translate against volume table.
	mov	[es:di],ax
	add	di,CHANNELS*2
	adc	dh,dl	; Add fractional amount in.
	adc	si,0	; Back off source address if didn't underflow.
	cmp	si,bp	; Past end of mod?
	je	@@WRAP2 ; no, keep going.
	CLOOP	@@cnt	; continue.

	jmp short @@don
@@WRAP2:
;;**	    sub     si,bp
	mov	si,[word cs:WorkMod.sound]
	mov	ax,[cs:WorkMod.silence]
	mov	[cs:WorkMod.silout],ax
	cmp	[cs:WorkMod.oneshot],1
	jne	@@SCHK
	mov	[cs:WorkMod.trigger],0
	jmp	@@don


@@don:
	pop	bp

	lds	bx,[MODS]		 ; Get back address of mod.
	mov	ax,[cs:WorkMod.silout]
	mov	[ds:(MODSPEC bx).silout],ax	; Update silence remaining
	mov	[ds:(MODSPEC bx).sndloc],si	; Current mod location.
	mov	al,[cs:WorkMod.trigger]
	mov	[ds:(MODSPEC bx).trigger],al

	PopCREGS
	PLEAVE
	ret
	endp

CPROC	ModSilence
	ARG	MOD1:DWORD,DEST:DWORD
	PENTER	0
	PushCREGS

	lds	si,[MOD1]
	les	di,[DEST]
	mov	cx,[ds:(MODSPEC si).bufflen]

	mov	al,080h
	RepStosb		; Store silence


	PopCREGS
	PLEAVE
	ret
	endp


;; There is a signed word in AX, we need to adjust it down to an
;; unsigned byte 0-255.
Macro	AdjustByte
	LOCAL	@@OVER,@@UNDER
	sar	ax,4
	add	ax,128		; Get it into 0-255.
	jns	@@OVER		; It's positive.
	xor	ax,ax
@@OVER: or	ah,ah		; high byte zero?
	jz	@@UNDER
	mov	ax,255		; Max it.
@@UNDER:
	endm

CPROC   MergeMod1
	ARG	MOD1:DWORD,DEST:DWORD
	PENTER	0
	PushCREGS

	lds	si,[MOD1]
	les	di,[DEST]
	mov	cx,[ds:(MODSPEC si).bufflen]
	lds	si,[ds:(MODSPEC si).buff]

	xor	ax,ax
	mov	dx,ax

@@OK:	mov	ax,[ds:si]
	add	si,CHANNELS*2		 ; Advance source.
	AdjustByte
	mov	[es:di],al
	inc	di
	dec	cx
	jnz	@@OK

	PopCREGS
	PLEAVE
	ret
	endp

CPROC	MergeMod2
	ARG	MOD1:DWORD,DEST:DWORD
	PENTER	0
	PushCREGS

	lds	si,[MOD1]
	les	di,[DEST]
	mov	cx,[ds:(MODSPEC si).bufflen]
	lds	si,[ds:(MODSPEC si).buff]

	xor	ax,ax
	mov	dx,ax

@@OK:	mov	ax,[ds:si]	; Get all 2 channels of data.
	add	ax,[ds:si+2]
	add	si,CHANNELS*2	; Advance source.
	AdjustByte
	mov	[es:di],al	; Store result.
	inc	di
	dec	cx
	jnz	@@OK

	PopCREGS
	PLEAVE
	ret
	endp

CPROC	MergeMod3
	ARG	MOD1:DWORD,DEST:DWORD
	PENTER	0
	PushCREGS

	lds	si,[MOD1]
	les	di,[DEST]
	mov	cx,[ds:(MODSPEC si).bufflen]
	lds	si,[ds:(MODSPEC si).buff]

	xor	eax,eax
	mov	dx,ax

@@OK:	mov	ax,[ds:si]
	add	ax,[ds:si+2]
	adc	ax,[ds:si+4]
	AdjustByte
	add	si,CHANNELS*2
	mov	[es:di],al
	inc	di
	dec	cx
	jnz	@@OK

	PopCREGS
	PLEAVE
	ret
	endp


CPROC	MergeMod4
	ARG	MOD1:DWORD,DEST:DWORD
	PENTER	0
	PushCREGS

	lds	si,[MOD1]
	les	di,[DEST]
	mov	cx,[ds:(MODSPEC si).bufflen]
	lds	si,[ds:(MODSPEC si).buff]
	xor	ax,ax

@@OK:	mov	ax,[ds:si]
	add	ax,[ds:si+2]
	adc	ax,[ds:si+4]
	adc	ax,[ds:si+6]
	AdjustByte
	add	si,CHANNELS*2
	mov	[es:di],al
	inc	di
	dec	cx
	jnz	@@OK


	PopCREGS
	PLEAVE
	ret
	endp



	ENDS
	END

