;Sound ChannelHandler
;
;Sits between SoundDMA and the current sound ChannelHandler, which could
;be either the game or RISCOS's SoundChannels module
;
;Allows emulation of MEMC sound DMA registers and differences in sound
;buffer size, between the game and the physical hardware

;It should also perform any frequency interpolation, between what the system
;is using and what the game requires.  The max frequency an Arc supports is
;31.25kHz
;
;Arc defaults are:
;	Output rate:	20.833kHz
;	Buffer period:	48uS / 0.9984cS
;
;	Channels	Bytes/buffer	Sample period
;	1		208		48uS
;	2		416		24uS
;	4		832		12uS
;	8		1664		8uS


#set audio_buffer_size = 13			;bit shift for: 2 * 4 * 1024

  STRUCT sound_config_struct
  {
    .channels			DCD 0
    .length			DCD 0
    .period			DCD 0
    .ChannelHandler		DCD 0		;address of handler in 26bit space
    .Scheduler			DCD 0		;address of handler in 26bit space
    .VIDC1_period		DCD 0
    .taskID			DCD 0
    .size
  }


 .sound_DA_address		DCD 0
 .circular_audio_ptr_in		DCD 0	;next free index
 .circular_audio_ptr_out	DCD 0	;first full index
 .sound_volume			DCD 0



;release_ChannelHandler
;----------------------
;hand the Channel Handler back to RISCOS

.release_ChannelHandler
 STMFD   R13!, {R0-R4, R11, R14}

 MOV     R3, #0				;no need to reset Channel Handler
 STRB    R3, [R12, #dont_trap_sound_configure - VARS]	;pass Sound_Configure to OS

 ADD     R11, R12, #os_sound_configure - VARS	;load previous values
 LDMIA   R11, {R0-R4}
 SWI     XSound_Configure		;PRM4-18
LDMFD   R13!, {R0-R4, R11, PC}




;claim_ChannelHandler
;--------------------
;takes over the RISCOS Channel Handler

.claim_ChannelHandler
 STMFD   R13!, {R0-R5, R11, R14}

 ADD     R11, R12, #os_sound_configure - VARS
 ADD     R5, R12, #hypervised_sound_configure - VARS
 MOV     R0, #0				;get current sound settings
 MOV     R1, #0
 MOV     R2, #0
 MOV     R3, #0
 MOV     R4, #0
 SWI     XSound_Configure		;PRM4-18
 TST     R3, #3				;is the handler legal?
 LDMNEFD R13!, {R0-R5, R11, PC}		;NO
 STMIA   R11, {R0-R4}			;store current values
 STMIA   R5, {R0-R4}			;store current values

 ADR     R0, our_channel_handler	;our channel handler
 LDR     R1, [R3, #8]			;get linear-to-log table
 STR     R1, [R0, #8]			;store in our handler
 LDR     R1, [R3, #12]			;get log-scale table
 STR     R1, [R0, #12]			;store in our handler
 MOV     R3, R0
 ADR     R4, our_scheduler		;our scheduler
 ADR     R0, sound_fill			;setup our channel handler with
 STR     R0, [R3, #0]			;..our fill code address
 STR     R0, [R3, #4]
 ADR     R0, sound_scheduler		;setup our scheduler
 STR     R0, [R4, #0]

 MOV     R0, #0				;take over
 MOV     R1, #0
 MOV     R2, #0
 SWI     XSound_Configure		;PRM4-18

 MOV     R14, #0
 TEMP    R5
 STR     R14, circular_audio_ptr_in	;reset our circular pointers
 STR     R14, circular_audio_ptr_out
 LOCK    R5

 ADD     R5, R12, #hypervised_sound_configure - VARS
 MOV     R0, #48			;default VIDC1 sample period
 STRB    R0, [R12, #dont_trap_sound_configure - VARS]	;handle Sound_Configure
 STR     R0, [R5, #sound_config_struct.VIDC1_period]
LDMFD   R13!, {R0-R5, R11, PC}




;Channel Handler fill entry
;if MEMC_Sptr > 0 then MEMC's sound DMA registers have been set
;   play sound from Sptr to SendN and then set Sptr to 0

;Entry: in IRQ with IRQ disabled
; R14 - Return address
; R12 - Start of sound buffer
; R11 - Interleave
; R10 - Buffer end
; R9  - Pointer to RISCOS level 1 handler
; R8  - Sample period (BUG: Sample length is passed on RO3.50-3.70)
; R6  - is -ve if updated Level0
; R4  - buffer length

;Exit:
; R0-R5, R7,R8 - Corrupted

.sound_fill
 STMFD   R13!, {R6, R10-R12, R14}
 #if Managed_ChannelHandler == 1 AND compile_RO > 400
   MOV     R0, R8			;RISCOS sound period
 #endif
 LDR     R8, hypervised_sound_configure + sound_config_struct.period
 #if Managed_ChannelHandler == 1 AND compile_RO == 400
   MOV     R0, R8			;fix bug in RO3.50 thru RO3.70
 #endif

._fill_return
 ;both level 1 and DMA fill return here
 ;check there's enough audio and call again if necessary
 LDR     R2, circular_audio_ptr_in
 LDR     R4, circular_audio_ptr_out

 SUB     R1, R10, R12			;R1 = SoundDMA buffer size
 SUBS    R3, R2, R4
 RSBMI   R3, R3, #1			;samples in circular buffer
 MULNE   R3, R8, R3			;R3 = time in circular buffer
 MUL     R5, R0, R1			;R5 = time in RISCOS buffer

 CMP     R3, R5				;are there enough samples?
 BHS     _fill_soundDMA_buffer		;YES, go straight to fill code

 ADR     R7, MEMC_registers
 #if Managed_ChannelHandler == 1 AND No_device_9_IRQ == 0
   LDR     R6, [R7, #IOC_SendN << 2]
   TEQ     R6, #0			;are we performing sound DMA?
   BLEQ    IOC_trigger_device_9		;NO, trigger device 9 IRQ
 #endif
 LDR     R1, [R7, #IOC_SendC << 2]	;check MEMC Sptr
 TEQ     R1, #0				;are we performing sound DMA?
 LDRNE   R6, [R7, #IOC_Sptr << 2]
 TEQNE   R6, #0
 BEQ     _call_channelhandler		;NO

 ;DMA must preserve R0, R7, R8, R10-R12
 ._DMA
 #if Managed_ChannelHandler == 1 AND JIT_WIMP_modules == 1
   BL      check_JIT_and_taskID		;is the task supported?
   LDMNEFD R13!, {R6, R10-R12, R14}	;NO, clear stack
   BNE     _flush_buffer		; |+ flush buffer and exit
 #endif
					;MEMC DMA emulation
					;-----------------------------
 STMFD   R13!, {R0, R4, R10-R12}
 LDR     R4, sound_DA_address				;circular buffer address
 MOV     R11, R2, LSL #32 - audio_buffer_size		;wrap buffer
 ADD     R11, R4, R11, LSR #32 - audio_buffer_size	;start of buffer
 ._DMA_fill_L1
   CMP     R6, R1			;have we hit the last DMA?
   BHS     _DMA_hit_SendC		;YES

   ADD     R3, R3, R8, LSL #4		;16 samples
   ADD     R2, R2, #16
   LDMIA   R6!, {R0, R4, R10, R12}
   STMIA   R11!, {R0, R4, R10, R12}

   CMP     R3, R5, LSL #1		;ensure two buffers worth
 BLO      _DMA_fill_L1

 STR     R6, [R7, #IOC_Sptr << 2]
 STR     R2, circular_audio_ptr_in	;buffer is filled to here
 LDMFD   R13!, {R0, R4, R10-R12}
B       _fill_soundDMA_buffer

._DMA_hit_SendC				;hit SendN
 STR     R2, circular_audio_ptr_in	;buffer is filled to here
 LDMFD   R13!, {R0, R4, R10-R12}

 MOV     R5, #0
 STR     R5, [R7, #IOC_SendN << 2]
 BL      IOC_trigger_device_9
 LDR     R5, [R7, #IOC_Sstart << 2]
 STR     R5, [R7, #IOC_Sptr << 2]	;swap to next DMA buffer
 LDR     R5, [R7, #IOC_SendN << 2]
 STR     R5, [R7, #IOC_SendC << 2]
B       _fill_return


;calls either the RISCOS or game Channel Handler, pointing it to our
;circular audio buffer

;Entry:
; R0 - Period used by SoundDMA
; R8 - Period used by game
; R11 - Interleave

._call_channelhandler
 ADR     R3, hypervised_sound_configure
 LDR     R6, sound_DA_address		;circular buffer address
 LDR     R1, [R3, #sound_config_struct.length] ;game's buffer length
 LDR     R12, circular_audio_ptr_in	;current circular buffer ptr
 MUL     R4, R11, R1			;R4 = Buffer length
 ADD     R2, R12, R4
 STR     R2, circular_audio_ptr_in	;update pointer

 MOV     R12, R12, LSL #32 - audio_buffer_size		;wrap buffer
 ADD     R12, R6, R12, LSR #32 - audio_buffer_size	;R12=start of buffer
 BIC     R12, R12, #%11			;word align
 ADD     R10, R12, R4			;R10=end of buffer

 BL      _flush_buffer

 LDR     R6, [R13]			;preserve R6
 STMFD   R13!, {R0, R8}			;preserve R0 and R8

 LDR     R9, [R3, #sound_config_struct.ChannelHandler]
 LDR     R0, [R9, #0]			;R0 = fill entry

 #if Managed_ChannelHandler == 1 AND compile_RO <= 400
   STR     PC, [R13, #-4]!		;store IRQ state for return
 #endif
 #if Managed_ChannelHandler == 1 AND compile_RO > 400
   MRS     R1, CPSR
   STR     R1, [R13, #-4]!		;store IRQ state for return
 #endif

 ADR     R14, _fill_return2
 LDR     R1, hypervised_sound_configure + sound_config_struct.taskID
 TEQ     R1, #0				;does a JIT task have the handler?
 #if Managed_ChannelHandler == 1 AND compile_RO <= 400
   STMFD   R13!, {R1, R14}		;stack return address
   BEQ     _fill_call_direct		;NO, call direct
   CMP     R0, #jit_appspace_end	;is it in JIT space?
   BHS     _fill_call_direct		;NO, call direct
 #endif
 #if Managed_ChannelHandler == 1 AND compile_RO > 400
   MOVEQ   PC, R0			;NO, call direct
   CMP     R0, #jit_appspace_end	;is it in JIT space?
   MOVHS   PC, R0			;NO, call direct
   STMFD   R13!, {R1, R14}		;stack return address
 #endif

 ; if we got here, the handler is running under a JIT task
 #if Managed_ChannelHandler == 1 AND JIT_WIMP_modules == 1
   BL      check_JIT_and_taskID		;is the task in memory?
;@@@ NEEDS to page in the channel handler
   ADDNE   R13, R13, #4 + 2 * 4		;NO, drop IRQ state from stack
   LDMNEFD R13!, {R0, R8}		; |  preserve rsgisters
   LDMNEFD R13!, {R6, R10-R12, PC}	; |+ and don't call handler
 #endif

 STR     R1, [R13, #-4]!		;make space for managed return
 ADD     R0, R0, #jit_code_start	;correct entry address
 LDR     R14, jit_managed_ldr_pc_r13	;return inside JIT space

 ._fill_call_direct
 STR     R0, [R13, #4]			;stack entry

 #if Managed_ChannelHandler == 1 AND compile_RO <= 400
   MOV     R1, PC
   AND     R1, R1, #&FC000003
 #endif
 #if Managed_ChannelHandler == 1 AND compile_RO > 400
   MRS     R0, CPSR
   AND     R1, R0, #&F0000003
   AND     R0, R0, #%11 << 6
   ORR     R1, R1, R0, LSL #20
 #endif
 ORR     R14, R14, R1
LDMFD   R13!, {R1, PC}			;call level 1 fill

._fill_return2
;@@@ NEEDS to page out the channel handler
 LDR     R0, [R13], #4			;restore IRQ state
 #if Managed_ChannelHandler == 1 AND compile_RO <= 400
   TEQP    R0, #0
 #endif
 #if Managed_ChannelHandler == 1 AND compile_RO > 400
   MSR     CPSR_c, R0
 #endif
 LDMFD   R13!, {R0, R8}			;preserve R0 and R8
 LDMFD   R13, {R6, R10-R12}		;preserve R6, R10-R12
B       _fill_return




 ;copy data from the circular buffer to the actual audio buffer
 ;current_sound_configure contains the current OS config
 ;hypervised_sound_configure contains the setting the game thinks its using

;Entry:
; R0 - Period used by RISCOS
; R4 - circular_audio_ptr_out
; R8 - Period used by game
; R12 - Start of sound buffer
; R11 - Interleave
; R10 - Buffer end

._fill_soundDMA_buffer
 LDMFD   R13!, {R6, R10-R12}		;restore RISCOS values

 CMP     R8, R0				;are the sample periods the same?
 BHI     _fill_1toN
 BLO     _fill_Nto1

 SUB     R1, R10, R12			;R1 = size of SoundDMA buffer
 ADD     R1, R4, R1
 STR     R1, circular_audio_ptr_out

 LDR     R5, sound_DA_address		;circular buffer address
 MOV     R4, R4, LSL #32 - audio_buffer_size		;wrap buffer
 ADD     R4, R5, R4, LSR #32 - audio_buffer_size	;R4 = out ptr
 BIC     R4, R4, #%11			;word align

._fill_1to1_L1
   LDMIA   R4!, {R1-R3, R7}		;copy to sound buffer
   STMIA   R12!, {R1-R3, R7}

   CMP     R12, R10
 BLO     _fill_1to1_L1
LDR     PC, [R13], #4


._fill_1toN
 MOV     R5, R4				;R5 = circular_audio_ptr_out
 MOV     R3, R8				;phase counter

 LDR     R1, sound_DA_address		;circular buffer address
 MOV     R4, R4, LSL #32 - audio_buffer_size		;wrap buffer
 ADD     R4, R1, R4, LSR #32 - audio_buffer_size	;R4 = out ptr

 ._fill_1toN_L1
   MOV     R2, R11			;no of channels
   ._fill_1toN_L2
     LDRB    R1, [R4], #1		;read each channel
     STRB    R1, [R12], #1

     SUBS    R2, R2, #1
   BNE     _fill_1toN_L2

   SUBS    R3, R3, R0			;decrease phase by actual sample period
   SUBGT   R4, R4, R11			;dont go to next sample
   ADDLE   R3, R3, R8			;correct phase
   ADDLE   R5, R5, R11			;update circular_audio_ptr_out

   CMP     R12, R10
 BLO     _fill_1toN_L1

 STR     R5, circular_audio_ptr_out
LDR     PC, [R13], #4


._fill_Nto1
 MOV     R5, R4				;R5 = circular_audio_ptr_out
 MOV     R3, R0				;phase counter

 LDR     R1, sound_DA_address		;circular buffer address
 MOV     R4, R4, LSL #32 - audio_buffer_size		;wrap buffer
 ADD     R4, R1, R4, LSR #32 - audio_buffer_size	;R4 = out ptr

 ._fill_Nto1_L1
   MOV     R2, R11			;no of channels
   ._fill_Nto1_L2
     LDRB    R1, [R4], #1		;read each channel
     STRB    R1, [R12], #1

     SUBS    R2, R2, #1
   BNE     _fill_Nto1_L2
   ADD     R5, R5, R11			;go to next sample

   ._fill_Nto1_L3
     SUBS    R3, R3, R8			;increase phase by actual sample period
     ADDGT   R4, R4, R11		;go to next sample
     ADDGT   R5, R5, R11		;update circular_audio_ptr_out
   BGT     _fill_Nto1_L3
   ADD     R3, R3, R0			;correct phase

   CMP     R12, R10
 BLO     _fill_Nto1_L1

 STR     R5, circular_audio_ptr_out
LDR     PC, [R13], #4


._flush_buffer
 MOV     R9, #0
 MOV     R1, #0
 MOV     R2, #0
 MOV     R6, #0
 MOV     R5, R12
 ._fill_clear_L1			;clear the buffer
   STMIA   R5!, {R1-R2, R6, R9}

   CMP     R5, R10
 BLO     _fill_clear_L1
MOV     PC, R14



;Scheduler
;---------
;Manages entry/exit to the current Sound Scheduler
;
;Entry: in IRQ with IRQ disabled
; R12 - RISCOS Scheduler entry
;
;Exit:
; R12 - Game Schedule entry

.sound_scheduler
 LDR     R12, hypervised_sound_configure + sound_config_struct.Scheduler
 STR     R14, [R13, #-4]!
 STMFD   R13!, {R0, R1, R12, R14}	;R14 will be overwritten later
 LDR     R1, hypervised_sound_configure + sound_config_struct.taskID
 TEQ     R1, #0				;does a JIT task have the handler?
 LDR     R1, [R12]			;R1 = scheduler
 STREQ   R1, [R13, #4 * 4]		;NO, put entry on stack
 LDMEQFD R13!, {R0, R1, R12, R14, PC}	; |+ and call directly

 CMP     R1, #jit_appspace_end		;is it in JIT space?
 STRHS   R1, [R13, #4 * 4]		;NO, put entry on stack
 LDMHSFD R13!, {R0, R1, R12, R14, PC}	; |+ and call directly

 ; if we got here, the handler is running under a JIT task
 BL      check_JIT_and_taskID		;is the task supported?
;@@@ NEEDS to page in the sound scheduler
 LDMNEFD R13!, {R0, R1, R12, R14, PC}	;NO, exit

 ADD     R1, R1, #jit_code_start	;correct entry address
 LDR     R14, jit_managed_ldr_pc_r13	;return inside JIT space
 STR     R1, [R13, #3 * 4]		;overwrite 2nd R14 with new PC
 #if Managed_ChannelHandler == 1 AND compile_RO <= 400
   MOV     R12, PC
   AND     R0, R12, #&FC000003
 #endif
 #if Managed_ChannelHandler == 1 AND compile_RO > 400
   MRS     R12, CPSR
   AND     R0, R12, #&F0000003
   AND     R12, R12, #%11 << 6
   ORR     R14, R14, R12, LSL #20
 #endif
 ORR     R14, R14, R0
LDMFD   R13!, {R0, R1, R12, PC}		;call scheduler




;reset_sound_configure
;---------------------
;puts Sound_Configure back to OS default

.reset_sound_configure
 STMFD   R13!, {R0-R5, R12, R14}

 ADR     R5, MEMC_registers		;reset MEMC registers
 MOV     R0, #0
 STR     R0, [R5, #IOC_Sptr << 2]	;Sound DMA
 STR     R0, [R5, #IOC_SendN << 2]
 STR     R0, [R5, #IOC_Sptr << 2]

 ADR     R12, os_sound_configure
 ADR     R5, hypervised_sound_configure
 LDMIA   R12, {R0-R4}
 STMIA   R5, {R0-R4}			;store current values
 MOV     R3, #0
 MOV     R4, #0
 BL      pass_SWI_on
 SWI     XSound_Configure		;PRM4-18

 LDR     R12, sound_DA_address
 MOV     R14, #1 << audio_buffer_size
 MOV     R0, #0
 MOV     R1, #0
 MOV     R2, #0
 MOV     R3, #0
 ._L1
   STMIA   R12!, {R0-R3}
   SUBS    R14, R14, #4 * 4
 BNE     _L1

 LDR     R0, linear_handler_entry
 TEQ     R0, #0				;is there a linear handler?
 MOVNE   R0, #1				;YES,
 MOVNE   R1, #0				; |
 MVNNE   R2, #0				; |
 SWINE   XSound_LinearHandler		; |+ release it

LDMFD   R13!, {R0-R5, R12, PC}




;reset_audio
;-------------
;resets the audio system

.reset_audio
 STMFD   R13!, {R0-R3, R14}
 MOV     R0, #5
 ADR     R1, _reinit_soundDMA		;trigger a callback
 ADR     R2, VARS
 SWI     XOS_CallAfter			;PRM1-441
LDMFD   R13!, {R0-R3, PC}

 ._reinit_soundDMA
 STMFD   R13!, {R0-R3, R14}
 ADR     R0, _reinit_soundDMA2		;trigger a callback
 MOV     R1, R12
 SWI     XOS_AddCallBack		;PRM1-324
LDMFD   R13!, {R0-R3, PC}

 ._reinit_soundDMA2
 STMFD   R13!, {R0-R3, R14}
 MOV     R14, #&FF
 STRB    R14, [R12, #soundDMA_restarting - VARS]
 BL      reset_sound_configure
 MOV     R0, #Module_ReInit
 ADR     R1, _soundDMA
 BL      pass_SWI_on
 SWI     XOS_Module			;PRM1-234
LDMFD   R13!, {R0-R3, PC}

 ._soundDMA DCB "SoundDMA", 0
 ALIGN




;reset_sound_modules
;-------------------
;reset default sounds

.reset_sound_modules
 STMFD   R13!, {R0-R3, R14}

 MOV     R0, #Module_Delete		;RMReInit sound Modules
 ADR     R1, _wavesynth
 SWI     XOS_Module			;PRM1-234
 MOV     R0, #Module_Delete
 ADR     R1, _stringlib
 SWI     XOS_Module			;PRM1-234
 MOV     R0, #Module_Delete
 ADR     R1, _percussion
 SWI     XOS_Module			;PRM1-234

 MOV     R0, #Module_ReInit
 ADR     R1, _wavesynth
 SWI     XOS_Module			;PRM1-234
 MOV     R0, #Module_ReInit
 ADR     R1, _stringlib
 SWI     XOS_Module			;PRM1-234
 MOV     R0, #Module_ReInit
 ADR     R1, _percussion
 SWI     XOS_Module			;PRM1-234

 MOV     R0, #1				;*ChannelVoice 1 WaveSynth-Beep
 MOV     R1, #1
 SWI     XSound_AttachVoice

 MOV     R0, #8
 MOV     R1, #0
 ._L1
   SWI     XSound_AttachVoice
   SUB     R0, R0, #1
   TEQ     R0, #1
 BNE     _L1
LDMFD   R13!, {R0-R3, PC}

 ._wavesynth		DCB "WaveSynth", 0
 ._stringlib		DCB "StringLib", 0
 ._percussion		DCB "Percussion", 0
 ALIGN
