\co Macro package for the Doom sound system.
\co
\include "m/sharedass"
\co
; DOOM sound via voice generators.
; Advantage: allows concurrent other sounds (trackers etc.)


DefineRegisters



; structure for storing variables of the voice handler
voice_number		equ	0x00
voice_channel_base	equ	0x04
voice_old_channel1	equ	0x08
voice_old_channel2	equ	0x0c
voice_old_num_channels	equ	0x10
voice_old_samp_length	equ	0x14
voice_old_samp_period	equ	0x18
voice_old_stereo1	equ	0x1c
voice_old_stereo2	equ	0x20
voice_other_buffer	equ	0x24
voice_size		equ	0x28


	idfn	(C) 1999 by Andreas Dehmel



	AREA	CODE, READONLY
	align	4
	import	|sound_ready|
	import	|channels|
	import	|LinToLogTable|
	import	|ResampleSound|
	export	|InstallDoomSound|
	=	"InstallDoomSound"
	align	4

|VoiceVarAdr|:
	dcd	|VoiceVariables|

|InstallDoomSound|:			; r0 = number of 1st channel (1-7), 2nd channel is
	stmdb	sp!, {r4-r8, r12, lr}	; always r0+1. r1 = sample length, r2 = sample period
	ldr	r12, [pc, #|VoiceVarAdr| - . - 8]
	mov	r5, r1
	mov	r6, r2
	mov	r8, r0
	adr	r0, |DoomVoiceGenerator|
	mov	r1, #0
	swi	0x60183			; Sound_InstallVoice
	cmp	r1, #0
	mvneq	r0, #0
	beq	|IDSexit|
	str	r1, [r12, #voice_number]
	str	r8, [r12, #voice_channel_base]
	mov	r7, r1
	mov	r0, #0
	str	r0, [r12, #voice_other_buffer]
	mov	r1, #0
	mov	r2, #0
	mov	r3, #0
	mov	r4, #0
	swi	0x60140			; Sound_Configure
	add	r3, r12, #voice_old_num_channels
	stmia	r3, {r0-r2}
	add	r1, r8, #1		; number of channels required for these voice-numbers
	cmp	r1, #5
	movge	r1, #8
	cmp	r1, #3
	moveq	r1, #4
	cmp	r0, r1
	movlt	r0, r1
	mov	r1, r5
	mov	r2, r6
	mov	r3, #0
	mov	r4, #0
	swi	0x60140
	mov	r1, r7
	mov	r0, r8
	swi	0x60185			; Sound_AttachVoice
	str	r1, [r12, #voice_old_channel1]
	mov	r1, r7
	add	r0, r8, #1
	swi	0x60185
	str	r1, [r12, #voice_old_channel2]
	mov	r0, r8
	mvn	r1, #0x7e
	swi	0x60142			; Sound_Stereo
	str	r1, [r12, #voice_old_stereo1]
	add	r0, r8, #1
	mov	r1, #0x7f
	swi	0x60142
	str	r1, [r12, #voice_old_stereo2]
	mov	r0, r8
	mov	r1, #0x0180
	mov	r2, #0x8000
	mov	r3, #0xff
	swi	0x60189			; Sound_Control
	add	r0, r8, #1
	swi	0x60189
	mov	r0, #0
|IDSexit|:
	ldmia	sp!, {r4-r8, r12, pc}^


	align	4
	export	|RemoveDoomSound|
	=	"RemoveDoomSound"
	align	4

|RemoveDoomSound|:
	stmdb	sp!, {r4, r12, lr}
	ldr	r12, [pc, #|VoiceVarAdr| - . - 8]
	ldr	r2, [r12, #voice_channel_base]
	movs	r0, r2
	beq	|RDSexit|
	ldr	r1, [r12, #voice_old_stereo1]
	swi	0x60142			; Sound_Stereo
	add	r0, r2, #1
	ldr	r1, [r12, #voice_old_stereo2]
	swi	0x60142
	mov	r0, r2
	ldr	r1, [r12, #voice_old_channel1]
	swi	0x60185			; Sound_AttachVoice
	add	r0, r2, #1
	ldr	r1, [r12, #voice_old_channel2]
	swi	0x60185
	add	r3, r12, #voice_old_num_channels
	ldmia	r3, {r0-r2}
	mov	r3, #0
	mov	r4, #0
	swi	0x60140			; Sound_Configure
	ldr	r1, [r12, #voice_number]
	swi	0x60184			; Sound_RemoveVoice
|RDSexit|:
	ldmia	sp!, {r4, r12, pc}^



; The channel_desc structure. Don't rearrange the order!!!
channel_desc_samples	equ	0x00
channel_desc_remain	equ	0x04
channel_desc_leftvol	equ	0x08
channel_desc_rightvol	equ	0x0c
channel_desc_step	equ	0x10
channel_desc_end	equ	0x14
channel_desc_size	equ	0x18

channel_ctrl_lump	equ	0x00
channel_ctrl_sampsize	equ	0x04
channel_ctrl_offset	equ	0x08
channel_ctrl_size	equ	0x0c


\define CheckChannelValidRes = {
	movs	r2, r9
}
\define ReadChannelSampleRes = {
	ldrb	r10, [r2], #1
	cmp	r2, r8
	ldrccb	r2, [r2]
	subcc	r2, r2, r10
	movcc	r1, r4, lsr #16
	mulcc	r2, r1, r2
	addcc	r10, r10, r2, asr #16
}
\define CheckChannelValidNorm = {
	cmp	r9, #0
}
\define ReadChannelSampleNorm = {
	ldrb	r10, [r9]
}
\define ChannelSampleGeneric(next,check,read) = {
	ldr	r9, [r3], #channel_desc_size	; samples
$check$
	beq	|DVFchannel$next$|
	ldmdb	r3, \{r4-r8\}		; r4 remainder, r5 left, r6 right, r7 step, r8 end
$read$
	ldr	r5, [r5, r10, lsl #2]	; leftvol
	add	r11, r11, r5
	ldr	r6, [r6, r10, lsl #2]	; rightvol
	add	r12, r12, r6
	adds	r10, r4, r7, lsl #16	; use top 16 bits (carry!) and move remainder to r10
	adc	r9, r9, r7, lsr #16	; carry set means remainder overflowed
	cmp	r9, r8
	movcs	r9, #0
	sub	r5, r3, #(channel_desc_size - channel_desc_samples)
	stmia	r5, \{r9, r10\}		; store samples, stepremainder
|DVFchannel$next$|:
}
\define ChannelSampleCore(next) = {
ChannelSampleGeneric($next$,CheckChannelValidNorm,ReadChannelSampleNorm)
}
\define ChannelResampleCore(next) = {
ChannelSampleGeneric($next$,CheckChannelValidRes,ReadChannelSampleRes)
}
\define StoreChannelSamples(loop) = {
	sub	r3, r3, #(8*channel_desc_size)
	mvn	r10, #0x80000000
	mov	r9, #0x80000000
	cmp	r11, r10, lsr #0x10
	movgt	r11, r10, lsr #0x10
	cmp	r11, r9, asr #0x10
	movlt	r11, r9, asr #0x10
	mov	r11, r11, lsl #0x10
	ldrb	r11, [r1, r11, lsr #0x13]	; bits in this here instruction.
	cmp	r12, r10, lsr #0x10
	movgt	r12, r10, lsr #0x10
	cmp	r12, r9, asr #0x10
	movlt	r12, r9, asr #0x10
	mov	r12, r12, lsl #0x10
	ldrb	r12, [r1, r12, lsr #0x13]
	strb	r12, [r0, r7]
	strb	r11, [r0], r8
	cmp	r0, r2
	bcc	|$loop$|
}

|DoomVoiceGenerator|:
	b	|DoomVoiceFill|		; fill code
	b	|DoomVoiceFill|		; update code
	b	|DoomVoiceFill|		; gate on code
	b	|DoomVoiceMute|		; gate off code
	ldmia	sp!, {pc}^
	ldmia	sp!, {pc}^
	ldmia	sp!, {pc}^
	dcd	|DoomVoiceName| - |DoomVoiceGenerator|
|DoomVoiceName|:
	=	"DoomVoice"
	align	4

|DVGsready|:
	dcd	sound_ready
|DVGctrlvars|:
	dcd	channel_ctrl
	dcd	lumpcache
	dcd	numlumps
|DVGimports|:				; don't change the order!
	dcd	LinToLogTable
|DVGchannels|:
	dcd	channels
	dcd	ResampleSound

; the problem here is that a lot of efficiency would be wasted if the two voices
; were synthesized separately. So use a hack that only does the synthesis every
; second time the voice handler is called and remember the offset into the DMA
; buffer passed the first time. Tried and tested with 2, 4 and 8 channels.
; BTW, channel1 is left, channel2 is right.
|DoomVoiceFill|:
	ldr	r0, [pc, #|DVGsready| - . - 8]
	ldr	r0, [r0]
	cmp	r0, #0
	beq	|DoomVoiceMute|
	ldr	r9, [pc, #|VoiceVarAdr| - . - 8]
	ldr	r0, [r9, #voice_other_buffer]
	cmp	r0, #0
	streq	r12, [r9, #voice_other_buffer]
	beq	|DVFexit|
	adr	r1, |DVGctrlvars|
	ldmia	r1, {r1-r3}
	ldr	r2, [r2]
	ldr	r3, [r3]
	ldr	r6, [pc, #|DVGchannels| - . - 8]	; find unmapped lumps
	mov	r4, #8
|DVFfindunmapped|:
	ldr	r5, [r1, #channel_ctrl_lump]
	cmp	r5, r3
	movcs	r5, #0
	ldrcc	r5, [r2, r5, lsl #2]
	cmp	r5, #0
	streq	r5, [r6, #channel_desc_samples]
	add	r1, r1, #channel_ctrl_size
	add	r6, r6, #channel_desc_size
	subs	r4, r4, #1
	bgt	|DVFfindunmapped|
	and	r0, r0, #7
	and	r2, r12, #7		; more secure to use just the offsets modulo 8...
	bic	r12, r12, #7
	ldr	r1, [r9, #voice_channel_base]
	cmp	r7, r1			; r7 is channel-1, i.e. 0...7 rather than 1...8 !
	subeq	r7, r2, r0		; this is channel 2 (ChannelBase)?
	addeq	r0, r12, r0
	subne	r7, r0, r2		; this is channel 1 (ChannelBase-1)?
	addne	r0, r12, r2
	mov	r3, #0
	str	r3, [r9, #voice_other_buffer]
	adr	r1, |DVGimports|
	ldmia	r1, {r1, r3, r9}
	mov	r2, r10
	ldr	r9, [r9]
	cmp	r9, #0
	bne	|DVFresample|
	stmdb	sp!, {r7, r11}
|DVFmainloop|:				; basically the same thing as in ROasm
	mov	r11, #0			; dl
	mov	r12, #0			; dr
|DVFchannel0|:				; unrolled 8 times ==> chan no longer needed
ChannelSampleCore(1)
ChannelSampleCore(2)
ChannelSampleCore(3)
ChannelSampleCore(4)
ChannelSampleCore(5)
ChannelSampleCore(6)
ChannelSampleCore(7)
ChannelSampleCore(8)
	ldmia	sp, {r7, r8}
StoreChannelSamples(DVFmainloop)
	add	sp, sp, #8
|DVFexit|:
	mov	r0, #8
	ldmia	sp!, {pc}
|DVFresample|:
	stmdb	sp!, {r1, r2, r7, r11}	; l2l, end, offset of 2nd channel, buffer increment
|DVFmainresloop|:
	mov	r11, #0
	mov	r12, #0
|DVFchannelR0|:
ChannelResampleCore(R1)
ChannelResampleCore(R2)
ChannelResampleCore(R3)
ChannelResampleCore(R4)
ChannelResampleCore(R5)
ChannelResampleCore(R6)
ChannelResampleCore(R7)
ChannelResampleCore(R8)
	ldmia	sp, {r1, r2, r7, r8}		; l2l, end, channel 2 offset, channel increment
StoreChannelSamples(DVFmainresloop)
	add	sp, sp, #16
	mov	r0, #8
	ldmia	sp!, {pc}

|DoomVoiceMute|:
	mov	r0, #0
|DVMloop|:
	strb	r0, [r12], r11
	cmp	r12, r10
	bcc	|DVMloop|
	mov	r0, #8
	ldmia	sp!, {pc}





	AREA	|Asm$$Data_ROasm|, DATA

|VoiceVariables|:
	%	voice_size



	END
