	SECTION	AudCode,CODE

CUSTOM	equ $DFF000
dmaconw equ $DFF096
dmaconr equ $DFF002
intreq  equ $DFF09C
intreqr equ $DFF01E
intena  equ $DFF09A
intenar equ $DFF01C
INTREQ	equ $09C
INTENA	equ $09A
DMACONW	equ $096
ADKCONW	equ $09E

_LVOSetIntVector equ -162
INSTALLED	 equ 0

;**************** Setup the Amiga Audio Channels Initially ***************
; Adds our "Block Done" audio interrupt handlers, saving any handlers we're
; replacing (ie could be the audio device's) so that we can restore them
; later. Makes sure that the 4 channels' interrupts are disabled now.
; This is safe to be called repeatedly.

	XDEF	_SetupAudio,SetupAudio
_SetupAudio	;the entry point for C programs
SetupAudio	;the entry point for assembly programs
	movem.l	d2/d3/d4/a6,-(sp)
	;---Get Exec base
S1	movea.l	$00000004,a6
	;---disable any audio interrupts
S2	move.w	#$0780,intena
	;---Add "audio block done" interrupt handlers for all 4 channels
S3	moveq	#7,d0		;chan 0
	lea	int0,a1
	jsr	_LVOSetIntVector(a6)
	move.l	d0,d2		;we replaced you
S4	moveq	#8,d0		;chan 1
	lea	int1,a1
	jsr	_LVOSetIntVector(a6)
	move.l	d0,d3
S5	moveq	#9,d0		;chan 2
	lea	int2,a1
	jsr	_LVOSetIntVector(a6)
	move.l	d0,d4
S6	moveq	#10,d0		;chan 3
	lea	int3,a1
	jsr	_LVOSetIntVector(a6)
	;---Set INSTALLED bit of audFlags. If already set, don't save return
	;   addresses from SetIntVector
S7	Bset.b	#INSTALLED,audFlags
	bne.s	S8
	lea	AudioB,a0
	move.l	d2,(a0)+
	move.l	d3,(a0)+
	move.l	d4,(a0)+
	move.l	d0,(a0)
S8	movem.l	(sp)+,d2/d3/d4/a6
	rts

;********************* Free the Amiga Audio Channels *********************
; Removes our "Block Done" audio interrupt handlers if we had installed them,
; restoring any handlers we replaced. Makes sure that the 4 channels' inter-
; rupts are disabled now, and channels are OFF. Makes sure that no channels
; are modifying other channels via the ADKCON register.
; This is safe to be called repeatedly.

	XDEF	_FreeAudio,FreeAudio
_FreeAudio	;the entry point for C programs
FreeAudio	;the entry point for assembly programs
	;---indicate original Block Done returned
F1	Bclr.b	#INSTALLED,audFlags
	beq.s	F10		;Hey! We must have already freed the audio
	movem.l	d2/d3/a2/a6,-(sp)
	;---Get Exec base
F2	movea.l	$00000004,a6
	;---Shut down the hardware
F3	lea	CUSTOM,a0
F4	moveq	#$0F,d0
	move.w	d0,DMACONW(a0)  ;stop all chans
F5	move.b	#$FF,d0
	move.w	d0,ADKCONW(a0)	;remove any modification of audio chans by other chans
F6	move.w	#$0780,INTENA(a0) ;disable any audio interrupts installed
	;---re-install the original "Block Done" handlers
F7	lea	AudioB,a2
	moveq	#7,d3		;chan 0's bit #
	moveq	#4-1,d2		;do 4 chans
F8	move.l	(a2)+,d0
	beq.s	F9		;Hey! Don't re-install a 0 address unless you want to see the GURU
	movea.l	d0,a1
	move.l	d3,d0
	jsr	_LVOSetIntVector(a6)
F9	addq.b	#1,d3
	Dbra	d2,F8
	movem.l	(sp)+,d2/d3/a2/a6
F10	rts


;***************************************************************************
; PlayNote(ChanNum,oneShotLen,oneShotAddr,LoopLen,LoopAddr,Volume,samplePeriod)
;	      d0	d1	  a0	    d2	     a1	     d3		d4
;
;Note: if passed oneShotAddr=0, then this just turns the channel off.

	XDEF	_PlayNote,_StopChan
	XDEF	PlayNote,StopChan
_PlayNote	;the entry point for C programs
	movem.l	d2/d3/d4,-(sp)
	movem.l	16(sp),d0/d1/a0
	movem.l	28(sp),d2/a1
	movem.l	36(sp),d3/d4
	bsr.s	PlayNote
	movem.l	(sp)+,d2/d3/d4
	rts

_StopChan	;the entry point for C programs
	move.l	4(sp),d0
StopChan	;the entry point for assembly programs
	suba.l	a0,a0
PlayNote	;the entry point for assembly programs
	movem.l	d2/a2-a4,-(sp)
	;---get base of custom chips
P1	lea	CUSTOM,a4
	;---get the passed channel's LoopStruct
P2	lsl.w	#4,d0		;multiply chanNum by 16 (the size of a LoopStruct)
	lea	loopStruct0,a2
	adda.w	d0,a2
	;---get the passed channels' hardware addresses (from the loopStruct)
P3	movea.l	(a2)+,a3
	;---get the channel's mask for dmaconw register
	move.w	(a2)+,d0	;chan mask
	;---Turn off the channel's volume in case it is playing back
	;   This helps eliminate that annoying "audio click" when a channel
	;   is arbitrarily stopped during playback
P4	lsr.w	8(a3)		;lower volume by half
	clr.w	8(a3)		;turn off volume (to avoid a click)
	;---STOP channel (now we won't have to worry about a block done
	;   interrupt occuring while we setup the hardware for this channel)
P5	move.w	d0,DMACONW(a4)
	;---Set POINTER register to our oneShot start address
P6	move.l	a0,(a3)+
	beq.s	OFF		;if no oneShot, just leave chan off
	;---Set LENGTH register to our oneShotLen
P7	lsr.l	#1,d1		;in WORDS, not BYTES! So, divide by 2
	move.w	d1,(a3)+
	;---Set PERIOD to the passed value
P8	move.w	d4,(a3)+
	;---Set VOLUME register
	move.w	d3,(a3)
	;---Get mask for INTENA and INTREQ registers
P9	move.w	(a2)+,d1
	;---Set up "block done"'s loopStruct
P10	move.l	a1,(a2)+
	lsr.l	#1,d2		;Note that this clears bit #31 as well
	move.l	d2,(a2)+
;======= Start Channel =========
	;---DMA control enabled, start channel bit set for this channel
P11	or.w	#$8200,d0
	;---clear any current interrupt request
P12	move.w	d1,INTREQ(a4)
	;---enable "block done"
P13	Bset.l	#15,d1
	move.w	d1,INTENA(a4)
	;---Start the channel
P14	move.w	d0,DMACONW(a4)
OFF	movem.l	(sp)+,d2/a2-a4
	rts


;*********************************************************************
;sampleRate = PeriodToRate(nanoPeriod)
;	d0			d1
;
; This takes the passed sample period in nanoseconds and converts it to a
; sample rate in hertz.


;*********************************************************************
;nanoPeriod = RateToPeriod(rate)
;	d0		    d1
;
; This takes the passed sample rate in hertz and converts it to a sample period
; for the Amiga hardware in microsecs (DMA) to play the wave at its original
; pitch.

	XDEF	RateToPeriod,_RateToPeriod
	XDEF	PeriodToRate,_PeriodToRate
_RateToPeriod:
_PeriodToRate:
	move.l	4(sp),d1
RateToPeriod:
PeriodToRate:
	move.l	FACTOR,d0
	move.l	d2,a0
	move.l	d3,a1
	moveq	#0,d2
	moveq	#31,d3
_1	asl.l	#1,d0
	roxl.l	#1,d2
	cmp.l	d1,d2
	bcs.s	_2
	sub.l	d1,d2
	addq.l	#1,d0
_2	Dbra	d3,_1
	move.l	d2,d1
	move.l	a1,d3
	move.l	a0,d2
	rts


;***************************************************************************
;amigaPeriod = TransposePeriod(steps,origPeriod)
;	d0			d0	d1
;
; This takes the passed origPeriod (in nanoseconds) and transposes it by the
; passed number of half-steps, returning an amiga sample period in micros. The
; number of half-steps can be from 12 to -12. If the resulting period is beyond
; the range of the audio hardware (with DMA) then a zero is returned. (Sets
; z-flag)
;
; Note that passing steps=0 yields the period value to play the wave at its
; original pitch.

	XDEF	_TransposePeriod,TransposePeriod
_TransposePeriod:
	movem.l	4(sp),d0/d1
TransposePeriod:
	movem.l	d4/d5,-(sp)
	;---Check that offset is not more than +/- 12 steps from ORIGPITCH
	lea	ORIGPITCH,a1	;conversion table for Amiga hardware
	moveq	#-12,d4
	cmp.l	d4,d0
	blt.s	limiT
	moveq	#12,d4
	cmp.l	d4,d0
	bge.s	limiT
	add.l	d0,d0		;2x (to access a WORD within conversion table)
	adda.l	d0,a1
	;---Divide period by proper conversion table step
	divu.w	(a1),d1		;Amiga Period (in micros)
	;---round the value up or down
	moveq	#0,d0
	move.w	d1,d0
	move.w	d1,d4
	lsr.w	#1,d1
	swap	d4
	sub.w	d1,d4
	bcs.s	rnd
	addq.w	#1,d0
	;---Check that period is within Amiga hardware limits for DMA
rnd	cmp.w	#500,d0
	bhi.s	limiT
	cmp.w	#126,d0
	bhi.s	olim
	;---If out of range, set period value to 0
	;   A player program should interpret this as "don't play this"
limiT	moveq	#0,d0
olim	movem.l	(sp)+,d4/d5
	rts


;******************* "Block Done" audio interrupt handler *******************
; passed the address of appropriate loopStruct's loopLength field in a1.
; CUSTOM_CHIPS in a0. SysBase in a6.

audioInt:
	;---Get the LoopStruct's loopLength. If bit #31 set, stop the channel
I1	move.l	(a1),d1
	bmi.s	I4
	;---If LoopStruct's loopLength is not 0, then we have a looping por-
	;   tion of the waveform. We'll set up a "forever" loop by disabling
	;   the "block done" interrupt for this channel. Now, the looping
	;   portion will continue looping until the application calls Stop-
	;   Audio() or PlayAudio() for this channel.
	beq.s	I3
I2	movea.l	-12(a1),a5	;channel's hardware
	move.l	-(a1),(a5)+	;store POINTER register
	move.w	d1,(a5)		;store LENGTH
	move.w	-(a1),d1	;this chan's mask for INTENA and INTREQ
	move.w	d1,INTENA(a0)	;disable this interrupt so we don't call audio0int anymore
	move.w	d1,INTREQ(a0)	;clear this chan's interrupt
	rts
	;---We have a oneShot only (no looping portion). Set bit #31 of the
	;   LoopStruct's loopLength so that upon the next interrupt for chan
	;   0, we stop the channel
I3	Bset.b	#7,(a1)
	subq.l	#4,a1
	move.w	-(a1),INTREQ(a0) ;clear this chan's interrupt
	rts
	;---Stop playback. We must be done with the oneShot portion and we
	;   have no looping portion.
I4	movea.l	-12(a1),a5	;channel's hardware
	Bclr.b	#7,(a1)		;clear bit #31 of loopLength
	subq.l	#8,a1
	clr.w	8(a5)		;turn off volume so we that we don't get a "audio click"
	move.w	(a1)+,DMACONW(a0) ;stop chan
	move.w	(a1)+,INTREQ(a0)  ;clear this chan's interrupt
	rts

	SECTION	audData,DATA

;Interrupt Structures for audio "Block Done" interrupts
int0 dc.l 0,0
     dc.b 2,0
     dc.l 0
     dc.l loopStruct0+12 ;IS_DATA (points to chan 0's LoopStruct's loopLength)
     dc.l audioInt	 ;IS_CODE
int1 dc.l 0,0
     dc.b 2,0
     dc.l 0
     dc.l loopStruct1+12,audioInt
int2 dc.l 0,0
     dc.b 2,0
     dc.l 0
     dc.l loopStruct2+12,audioInt
int3 dc.l 0,0
     dc.b 2,0
     dc.l 0
     dc.l loopStruct3+12,audioInt

;------------------------- LoopStructs for 4 channels ---------------------
;If loopLength is 0, then the "block done" will set bit #31 of loopLength.
;Upon the next interrupt, this will indicate that the channel is to be
;stopped (ie no looping portion of the wave). If loopLength is not 0 and bit
;#31 is clear, then we loop on the data whose address is contained in the
;loopStruct's loopAddress.

loopStruct0
	dc.l $DFF0A0	;Pointer reg (first of 5 registers) for channel #0
	dc.w 1		;channel mask for DMACON register
	dc.w $0080	;channel mask for INTENA and INTREQ registers
	dc.l 0		;start address of the looping portion (loopAddress)
	dc.l 0		;length of the looping portion in WORDS (loopLength)
loopStruct1
	dc.l $DFF0B0
	dc.w 2
	dc.w $0100
	dc.l 0,0
loopStruct2
	dc.l $DFF0C0
	dc.w 4
	dc.w $0200
	dc.l 0,0
loopStruct3
	dc.l $DFF0D0
	dc.w 8
	dc.w $0400
	dc.l 0,0

;========= for calculating the transposed period to play a musical scale ======
;Assume western scale (distance between each step is the 12th root of 2).
;When we take the period in nanoseconds and divide by each entry in this
;table, we get the proper value for the Amiga hardware period. For example,
;if we have some 8SVX wave sampled at 18000 khz sampling rate, in order to play
;the wave at its original pitch, we place the value 199 in the amiga PERIOD
;register. Here's how we arrive at that. We divide 18000 into 1000000000 to get
;the period in nanoseconds. Then, we divide by the ORIGPITCH entry in this
;table (279) which yields 199.

	dc.w	140
	dc.w	148
	dc.w	157
	dc.w	166
	dc.w	176
	dc.w	186
	dc.w	198
	dc.w	209
	dc.w	222
	dc.w	235
	dc.w	249
	dc.w	264	;.279365 * .943874312 * 1000  (goes down 1/2 step)
ORIGPITCH:
	dc.w	279	;.279365 * 1.0 * 1000  (the original pitch)
	dc.w	296	;.279365 * 1.059463094 * 1000 (goes up 1/2 step)
	dc.w	313
	dc.w	332
	dc.w	352
	dc.w	373
	dc.w	395
	dc.w	419
	dc.w	443
	dc.w	470
	dc.w	498
	dc.w	527
	dc.w	559

FACTOR	dc.l	1000000000	;for converting sample rate in hertz to period
				;in nanoseconds

;the audio block done interrupt handlers that we replaced
AudioB		dc.l	0,0,0,0
;certain bits are for various functions
audFlags	dc.b	0
