*** ScR ***

*** NOTES ***

* MAN! The non-table 14 bit routines sound terrible!
* There is still room for improvements in the softint's. For example,
* try if reducing chip memory accesses increase speed. Not high priority, though.
* This driver should probably have a DMA play mode for 1-4 stereo channels.
* Sampler routines should be added.
* MINPER should not be fixed! Examine if the current scan mode is doublescan.

PALFREQ		EQU	3546895
NTSCFREQ	EQU	3579545

MINFREQ		EQU	7000

MAXPER		EQU	PALFREQ/MINFREQ
MINPER		EQU	62


;------------

	auto	wo AHI:libs/ahi/paula.audio\

	incdir	include:

	include	hardware/all.i

	include	exec/exec.i
	include	graphics/gfxbase.i
	include	utility/utility.i
	include	utility/hooks.i
	include	devices/audio.i
	include	dos/dos.i

	include	lvo/exec_lib.i
	include	lvo/utility_lib.i
	include	lvo/dos_lib.i

	include	libraries/ahi.i
	include	libraries/ahi_sub.i
	include	macros.i


* paula.audio extra tags
AHIDB_Paula14Bit EQU	AHIDB_UserBase+0	* Boolean
AHIDB_PaulaTable EQU	AHIDB_UserBase+1	* Boolean

MODE	EQU	A4000!MC040

INTF_AUDIO	EQU	INTF_AUD0!INTF_AUD1!INTF_AUD2!INTF_AUD3

AUDINT_OFF	MACRO
	move.w	#INTF_AUDIO,CUSTOM+INTENA
	ENDM
AUDINT_ON	MACRO
	move.w	#INTF_SETCLR!INTF_AUD0,CUSTOM+INTENA
	ENDM


 * paulaBase (private)
	STRUCTURE paulaBase,LIB_SIZE
	UBYTE	pb_Flags
	UBYTE	pb_Pad1
	UWORD	pb_Pad2
	APTR	pb_SysLib
	ULONG	pb_SegList
	APTR	pb_GfxLib
	APTR	pb_UtilLib
	APTR	pb_DosLib
	LABEL	paulaBase_SIZEOF

 * paula (private) ahiac_DriverData points to this structure.
	STRUCTURE paula,0
	UBYTE	p_Flags
	UBYTE	p_Pad
	UWORD	p_DisableCount
	APTR	p_audioport
	APTR	p_audioreq
	ULONG	p_audiodev
;	UWORD	p_OldDMACON
;	UWORD	p_OldINTENA
	APTR	p_Interrupt
	APTR	p_NoInterrupt
;	APTR	p_OldInterrupt
	APTR	p_DMAbuffer

*** IS_DATA for Audio Interrupt (do not change order!)
	LABEL	p_AudIntData
	APTR	p_SoftInt
	UWORD	p_AudLen			;don't change order
	UWORD	p_AudPer			;
	ULONG	p_DoubleBufferFlag
	APTR	p_AudPtr1A
	APTR	p_AudPtr2A
	APTR	p_AudPtr3A
	APTR	p_AudPtr4A
	APTR	p_AudPtr1B
	APTR	p_AudPtr2B
	APTR	p_AudPtr3B
	APTR	p_AudPtr4B

*** IS_DATA for Software Interrupt (do not change order!)
	LABEL	p_SoftIntData

	APTR	p_PlayerHook
	ULONG	p_Reserved
	APTR	p_AudioCtrlP
	FPTR	p_PlayerEntry			;p_PlayerHook->h_Entry

	ULONG	p_LoopTimes
	APTR	p_MixHook
	APTR	p_Mixbuffer
	APTR	p_AudioCtrlM
	FPTR	p_MixEntry			;p_MixHook->h_Entry
	APTR	p_AudPtrs
	APTR	p_CalibrationTable

	STRUCT	p_CalibrationArray,256
	LABEL	paula_SIZEOF

* p_Flags
;	BITDEF	P,INT,7				;interrup handler added
	BITDEF	P,14BIT,0
PB_STEREO	EQU	AHIACB_STEREO		;=2
PF_STEREO	EQU	AHIACF_STEREO

Start:
	moveq	#-1,d0
	rts

VERSION		EQU	1
REVISION	EQU	5
DATE	MACRO	
		dc.b	"14.3.96"
	ENDM
VERS	MACRO
		dc.b	"paula 1.5"
	ENDM
VSTRING	MACRO
		VERS
		dc.b	" ("
		DATE
		dc.b	")",13,10,0
	ENDM
VERSTAG	MACRO
		dc.b	0,"$VER: "
		VERS
		dc.b	" ("
		DATE
		dc.b	")",0
	ENDM

RomTag:
	DC.W	RTC_MATCHWORD
	DC.L	RomTag
	DC.L	EndCode
	DC.B	RTF_AUTOINIT
	DC.B	VERSION				;version
	DC.B	NT_LIBRARY
	DC.B	0				;pri
	DC.L	LibName
	DC.L	IDString
	DC.L	InitTable

LibName:	dc.b	"paula.audio",0
IDString:	VSTRING
gfxName:	GRAPHICSNAME
utilName:	UTILITYNAME
dosName:	DOSNAME
	cnop	0,2

InitTable:
	DC.L	paulaBase_SIZEOF
	DC.L	funcTable
	DC.L	dataTable
	DC.L	initRoutine

funcTable:
	dc.l	Open
	dc.l	Close
	dc.l	Expunge
	dc.l	Null
*
	dc.l	AHIsub_AllocAudio
	dc.l	AHIsub_FreeAudio
	dc.l	AHIsub_Disable
	dc.l	AHIsub_Enable
	dc.l	AHIsub_Start
	dc.l	AHIsub_Update
	dc.l	AHIsub_Stop
	dc.l	AHIsub_SetVol
	dc.l	AHIsub_SetFreq
	dc.l	AHIsub_SetSound
	dc.l	AHIsub_SetEffect
	dc.l	AHIsub_LoadSound
	dc.l	AHIsub_UnLoadSound
	dc.l	AHIsub_GetAttr
	dc.l	-1

dataTable:
	INITBYTE	LN_TYPE,NT_LIBRARY
	INITLONG	LN_NAME,LibName
	INITBYTE	LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
	INITWORD	LIB_VERSION,VERSION
	INITWORD	LIB_REVISION,REVISION
	INITLONG	LIB_IDSTRING,IDString
	DC.L		0

initRoutine:
	movem.l	d1/a0/a1/a5/a6,-(sp)
	move.l	d0,a5
	move.l	a6,pb_SysLib(a5)
	move.l	a0,pb_SegList(a5)
	lea	gfxName(pc),a1
	moveq	#0,d0
	call	OpenLibrary
	move.l	d0,pb_GfxLib(a5)
	bne.b	.gfxOK
	ALERT	AG_OpenLib!AO_GraphicsLib
	moveq	#0,d0
	bra.b	.exit
.gfxOK
	lea	utilName(pc),a1
	moveq	#0,d0
	call	OpenLibrary
	move.l	d0,pb_UtilLib(a5)
	bne.b	.utilOK
	ALERT	AG_OpenLib!AO_UtilityLib
	moveq	#0,d0
	bra.b	.exit
.utilOK
	lea	dosName(pc),a1
	moveq	#0,d0
	call	OpenLibrary
	move.l	d0,pb_DosLib(a5)
	bne.b	.dosOK
	ALERT	AG_OpenLib!AO_DOSLib
	moveq	#0,d0
	bra.b	.exit
.dosOK
	move.l	a5,d0
.exit
	movem.l	(sp)+,d1/a0/a1/a5/a6
	rts

Open:
	moveq	#0,d0
	addq.w	#1,LIB_OPENCNT(a6)
	bclr.b	#LIBB_DELEXP,pb_Flags(a6)
	move.l	a6,d0
.exit
	rts

Close:
	moveq	#0,d0
	subq.w	#1,LIB_OPENCNT(a6)
	bne.b	.exit
	btst.b	#LIBB_DELEXP,pb_Flags(a6)
	beq.b	.exit
	bsr.b	Expunge
.exit
	rts

Expunge:
	movem.l	d1/d2/a0/a1/a5/a6,-(sp)
	move.l	a6,a5
	move.l	pb_SysLib(a5),a6
	tst.w	LIB_OPENCNT(a5)
	beq.b	.notopen
	bset.b	#LIBB_DELEXP,pb_Flags(a5)
	moveq	#0,d0
	bra.b	.Expunge_end
.notopen
	move.l	pb_GfxLib(a5),a1
	call	CloseLibrary

	move.l	pb_UtilLib(a5),a1
	call	CloseLibrary

	move.l	pb_DosLib(a5),a1
	call	CloseLibrary

	move.l	pb_SegList(a5),d2
	move.l	a5,a1
	call	Remove

	moveq	#0,d0
	move.l	a5,a1
	move.w	LIB_NEGSIZE(a5),d0
	sub.l	d0,a1
	add.w	LIB_POSSIZE(a5),d0
	call	FreeMem
	move.l	d2,d0
.Expunge_end
	movem.l	(sp)+,d1/d2/a0/a1/a5/a6
	rts

Null:
	moveq	#0,d0
	rts

* BeginIO(ioRequest)(a1) (From amiga.lib)
BeginIO:
	move.l	a1,a0		;probably not neccesary
	push	a6
	move.l	IO_DEVICE(a1),a6
	jsr	-30(a6)
	pop	a6
	rts

******* <driver>.audio/AHIsub_AllocAudio ************************************
*
*   NAME
*       AHIsub_AllocAudio -- Allocates and initializes the audio hardware.
*
*   SYNOPSIS
*       result = AHIsub_AllocAudio( tags, audioctrl);
*       D0                          A1    A2
*
*       ULONG AHIsub_AllocAudio( struct TagItem *, struct AHIAudioCtrl * );
*
*   DO-IT-YOURSELF
*       Allocate and initialize the audio hardware. Decide if and how you
*       wish to use the mixing routines provided by 'ahi.library', by looking
*       in the AHIAudioCtrl structure and parsing the tag list for tags
*       you support;
*
*       1) Use mixing routines with timing:
*           You will need to be able to play any number of samples from
*           about 80 up to 65535 with low overhead.
*           · Update AudioCtrl->ahiac_MixFreq to nearest value that your
*             hardware supports.
*           · Return AHISF_MIXING|AHISF_TIMING.
*       2) Use mixing routines without timing:
*           If there is much overhead involved with starting playback,
*           or the hardware can't play samples with any length, use this
*           alternative and provide timing yourself.
*           · Update AudioCtrl->ahiac_MixFreq to nearest value that your
*             hardware supports.
*           · Store the number of samples to mix each pass in
*             AudioCtrl->ahiac_BuffSamples.
*           · Return AHISF_MIXING
*       3) Don't use mixing routines:
*           If your hardware can handle everying without using the CPU to
*           mix the channels, you tell 'ahi.library' this by returning NULL.
*
*       If you can handle stereo output from the mixing routines, or you
*       have a DMA/DSP stereo card, also set bit AHISB_KNOWSTEREO.
*       If you can handle hifi (32 bit) output from the mixing routines, if
*       you have a 12 bit or better DMA card, or if a DSP card which uses
*       non-lossy mixing routines (perhaps with interpolation etc), also set
*       bit AHISB_KNOWHIFI.
*
*   INPUTS
*       tags - pointer to a taglist.
*       audioctrl - pointer to an AHIAudioCtrl structure.
*
*   TAGS
*       The tags are from the audio database (AHIDB_#? in <libraries/ahi.h>),
*       NOT the tag list the user called ahi.library/AHI_AllocAudio() with.
*
*   RESULT
*       Flags, defined in <libraries/ahi_sub.h>.
*
*   EXAMPLE
*
*   NOTES
*       You don't have to clean up on failure, AHIsub_FreeAudio() will
*       allways be called.
*
*   BUGS
*
*   SEE ALSO
*       AHIsub_FreeAudio()
*
*****************************************************************************
*
*

AHIsub_AllocAudio:
	pushm	d2-d7/a2-a6
	move.l	a6,a5

	move.l	a1,d3

	move.l	pb_SysLib(a5),a6
	move.l	#paula_SIZEOF,d0
	move.l	#MEMF_PUBLIC|MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,ahiac_DriverData(a2)
	beq.w	.error_nopaula
	move.l	d0,a3

	move.l	a2,p_AudioCtrlP(a3)		;player Hook
	move.l	a2,p_AudioCtrlM(a3)		;mixer Hook

	moveq	#0,d2
	move.l	pb_UtilLib(a5),a6
	move.l	#AHIDB_Paula14Bit,d0
	moveq	#0,d1
	move.l	d3,a0				;tag list
	call	GetTagData
	tst.l	d0
	beq.b	.no14bit
	moveq	#PF_14BIT,d2
.no14bit
	move.l	ahiac_Flags(a2),d0
	and.b	#PF_STEREO,d0			;same as AHIACF_STEREO
	or.b	d2,d0
	move.b	p_Flags(a3),d1
	and.b	#~(PF_STEREO|PF_14BIT),d1
	or.b	d0,d1
	move.b	d1,p_Flags(a3)

	move.l	#AHIDB_PaulaTable,d0
	moveq	#0,d1
	move.l	d3,a0				;tag list
	call	GetTagData
	tst.l	d0
	beq.w	.notable

* Load 'ENV:CyberSound/SoundDrivers/14Bit_Calibration', allocate
* and initialize the table.
* FIXIT: The calibration file should move to a special hunk in
* 'DEVS:AudioModes/PAULA'.
	move.l	pb_DosLib(a5),a6
	lea	.calibname(pc),a0
	move.l	a0,d1
	move.l	#MODE_OLDFILE,d2
	call	Open
	move.l	d0,d4
	beq.b	.nocalib
	move.l	d0,d1
	lea	p_CalibrationArray(a3),a0
	move.l	a0,d2
	move.l	#256,d3
	call	Read
	cmp.l	d0,d3
	beq.b	.tableloaded
.nocalib
; Fill defaults
	lea	p_CalibrationArray(a3),a0
	move.w	#254-1,d0
.initcalib
	move.b	#$55,(a0)+
	dbf	d0,.initcalib
	move.b	#$7f,(a0)+
.tableloaded
	move.l	d4,d1
	beq.b	.nofile
	call	Close
.nofile
	move.l	pb_SysLib(a5),a6
	move.l	#65536*2,d0
	move.l	#MEMF_PUBLIC,d1
	call	AllocVec
	move.l	d0,p_CalibrationTable(a3)
	beq.b	.notable
	move.l	d0,a0			;table
	lea	p_CalibrationArray(a3),a1
	bsr	_CreateTable
.notable


* allocate audio.device
	move.l	pb_SysLib(a5),a6
	call	CreateMsgPort
	move.l	d0,p_audioport(a3)
	beq.w	.error_noport
	moveq	#ioa_SIZEOF,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_audioreq(a3)
	beq.w	.error_noreqmem
	move.l	d0,a0
	move.l	p_audioport(a3),MN_REPLYPORT(a0)
	clr.w	ioa_AllocKey(a0)
	move.b	#127,LN_PRI(a0)			;steal it!
	lea	.audiochannelarray(pc),a1
	move.l	a1,ioa_Data(a0)
	move.l	#1,ioa_Length(a0)
	lea	.audioname(pc),a0
	moveq	#0,d0
	move.l	p_audioreq(a3),a1
	moveq	#0,d1
	call	OpenDevice
	move.l	d0,p_audiodev(a3)
	bne.w	.error_noaudiodev		;somebody already owns the hardware (could be we!)
	move.l	p_audioreq(a3),a1
	move.w	#CMD_RESET,IO_COMMAND(a1)
	bsr	BeginIO				;clear attach, stop sound.
	move.l	p_audioport(a3),a0
	call	WaitPort
	move.l	p_audioport(a3),a0
	call	GetMsg

;	move.w	#DMAF_AUDIO,DMACON+CUSTOM	;turn audio off
;	move.w	#$00ff,ADKCON+CUSTOM		;no modulation

* allocate interrupts
	moveq	#IS_SIZE,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_Interrupt(a3)
	beq.w	.error_nointmem
	move.l	d0,a0
	move.b	#NT_INTERRUPT,LN_TYPE(a0)
	lea	LibName(pc),a1
	move.l	a1,LN_NAME(a0)
	lea	AudioInterrupt(pc),a1
	move.l	a1,IS_CODE(a0)
	lea	p_AudIntData(a3),a1
	move.l	a1,IS_DATA(a0)

	moveq	#IS_SIZE,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_NoInterrupt(a3)
	beq.b	.error_nointmem
	move.l	d0,a0
	move.b	#NT_INTERRUPT,LN_TYPE(a0)
	lea	LibName(pc),a1
	move.l	a1,LN_NAME(a0)
	lea	Interrupt_Dummy(pc),a1
	move.l	a1,IS_CODE(a0)
	clr.l	IS_DATA(a0)

	exg.l	a5,a6
	call	AHIsub_Disable
	exg.l	a5,a6

	move.l	p_NoInterrupt(a3),a1
	moveq	#INTB_AUD0,d0
	call	SetIntVector

	moveq	#IS_SIZE,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_SoftInt(a3)
	beq.b	.error_nointmem

	move.l	d0,a0
	move.b	#NT_INTERRUPT,LN_TYPE(a0)
	lea	LibName(pc),a1
	move.l	a1,LN_NAME(a0)
	lea	SoftInt_Dummy(pc),a1
	move.l	a1,IS_CODE(a0)
	clr.l	IS_DATA(a0)

	move.l	ahiac_MixFreq(a2),d1
	bsr	calcperiod
	move.l	d0,ahiac_MixFreq(a2)		;store actual freq
	move.w	d1,p_AudPer(a3)

	moveq	#AHISF_KNOWSTEREO|AHISF_MIXING|AHISF_TIMING,d0
.exit
	popm	d2-d7/a2-a6
	rts

.error_nointmem
.error_noaudiodev
.error_noreqmem
.error_noport
.error_nopaula
	moveq	#AHISF_ERROR,d0
	bra.b	.exit

.audiochannelarray
	dc.b	1+2+4+8
.audioname
	AUDIONAME
.calibname
	dc.b	"ENV:CyberSound/SoundDrivers/14Bit_Calibration",0

;in:
* d1	MixFreq
* a5	paulaBase
;out:
* d0	New MixFreq
* d1.w	Period
;description:
*	Calculate and return the best period and the actual frequency.
calcperiod:
	pushm	d2-d4
	move.l	#PALFREQ,d2			;PAL
	move.l	pb_GfxLib(a5),a0
	move.w	gb_DisplayFlags(a0),d0
	btst	#REALLY_PALn,d0
	bne.b	.1
	move.l	#NTSCFREQ,d2			;NTSC
.1
	cmp.l	#MINFREQ,d1
	bhi.b	.2
	move.l	#MINFREQ,d1
.2
	move.l	d2,d0
	move.l	d1,d3
	move.l	pb_UtilLib(a5),a0
	jsr	_LVOUDivMod32(a0)
	lsl.l	#1,d1
	cmp.l	d3,d1
	bmi.b	.3
	addq.l	#1,d0
.3
	move.w	d0,d4
	moveq	#0,d1
	move.w	d0,d1
	move.l	d2,d0
	move.l	d1,d3
	jsr	_LVOUDivMod32(a0)
	lsl.l	#1,d1
	cmp.l	d3,d1
	bmi.b	.4
	addq.l	#1,d0
.4
	move.w	d4,d1
	popm	d2-d4
	rts

* _CreateTable directly stolen from Christian Buchner's CyberSound
* audio sub system (with permission).

* _CreateTable **************************************************************

		; Parameters

		; a0 = Table address
		; (MUST have enough space for 65536 UWORDS)
		; a1 = Additive Array
		; 256 UBYTEs
		;
		; the table is organized as follows:
		; 32768 UWORDS positive range, ascending order
		; 32768 UWORDS negative range, ascending order
		; access: (a0,d0.l*2)
		; where d0.w is signed word sample data
		; and the upper word of d0.l is *cleared!*


_CreateTable	movem.l	a2/d2-d6,-(sp)

		lea	128(a1),a2

		move.l	a2,a1			; count the number of steps
		moveq	#128-1,d0		; in the positive range
		moveq	#0,d5
.countpositive	move.b	(a1)+,d1
		ext.w	d1
		ext.l	d1
		add.l	d1,d5
		dbra	d0,.countpositive	; d5=number of steps
		move.l	#32768,d6		; reset stretch counter
		
		move.l	a2,a1			; middle value in calibdata
		move.w	#32768-1,d0		; number of positive values -1
		moveq	#0,d1			; HI value
		moveq	#0,d2			; LO value
		moveq	#0,d3			; counter
.fetchnext2	move.b	(a1)+,d4		; add calibtable to counter
		ext.w	d4
		add.w	d4,d3
.outerloop2	tst.w	d3
		bgt.s	.positive2
.negative2	addq.w	#1,d1			; increment HI value
		sub.w	d4,d2			; reset LO value
		bra.s	.fetchnext2
.positive2	move.b	d1,(a0)+		; store HI and LO value
		move.b	d2,(a0)+
		sub.l	d5,d6			; stretch the table
		bpl.s	.repeat2		; to 32768 entries
		add.l	#32768,d6
		addq.w	#1,d2			; increment LO value
		subq.w	#1,d3			; decrement counter
.repeat2	dbra	d0,.outerloop2

		move.l	a2,a1			; count the number of steps
		moveq	#128-1,d0		; in the negative range
		moveq	#0,d5
.countnegative	move.b	-(a1),d1
		ext.w	d1
		ext.l	d1
		add.l	d1,d5
		dbra	d0,.countnegative	; d5=number of steps
		move.l	#32768,d6		; reset stretch counter
		
		add.l	#2*32768,a0		; place at the end of the table
		move.l	a2,a1			; middle value in calibdata
		move.w	#32768-1,d0		; number of negative values -1
		moveq	#-1,d1			; HI value
		moveq	#-1,d2			; LO value
		moveq	#0,d3			; counter
.fetchnext1	move.b	-(a1),d4		; add calibtable to counter
		ext.w	d4
		add.w	d4,d3
		add.w	d4,d2			; maximize LO value
.outerloop1	tst.w	d3
		bgt.s	.positive1
.negative1	subq.w	#1,d1
		bra.s	.fetchnext1
.positive1	move.b	d2,-(a0)		; store LO and HI value
		move.b	d1,-(a0)
		sub.l	d5,d6			; stretch the table
		bpl.s	.repeat1		; to 32768 entries
		add.l	#32768,d6
		subq.w	#1,d2			; decrement lo value
		subq.w	#1,d3			; decrement counter
.repeat1	dbra	d0,.outerloop1

		movem.l	(sp)+,a2/d2-d6
		rts


******* <driver>.audio/AHIsub_FreeAudio *************************************
*
*   NAME
*       AHIsub_FreeAudio -- Deallocates the audio hardware.
*
*   SYNOPSIS
*       AHIsub_FreeAudio( audioctrl );
*                         A2
*
*       void AHIsub_FreeAudio( struct AHIAudioCtrl * );
*
*   DO-IT-YOURSELF
*       Deallocate the audio hardware and other resources allocated in
*       AHIsub_AllocAudio().
*
*   INPUTS
*       audioctrl - pointer to an AHIAudioCtrl structure.
*
*   NOTES
*       It must be safe to call this routine even if AHIsub_AllocAudio()
*       was never called, failed or called more than once.
*
*   SEE ALSO
*       AHIsub_AllocAudio()
*
*****************************************************************************
*
*

AHIsub_FreeAudio:
	pushm	d2-d7/a2-a6

	move.l	a6,a5
	move.l	pb_SysLib(a5),a6

	move.l	ahiac_DriverData(a2),d0
	beq.w	.nopaula
	move.l	d0,a3

	move.l	p_CalibrationTable(a3),d0
	beq.b	.notable
	move.l	d0,a1
	clr.l	p_CalibrationTable(a3)
	call	FreeVec
.notable
	move.l	p_Interrupt(a3),d0
	beq.b	.nointstruct
	move.l	d0,a1
	clr.l	p_Interrupt(a3)
	call	FreeVec
.nointstruct
	move.l	p_NoInterrupt(a3),d0
	beq.b	.nointstruct2
	move.l	d0,a1
	clr.l	p_NoInterrupt(a3)
	call	FreeVec
.nointstruct2
	move.l	p_SoftInt(a3),d0
	beq.b	.nosoftintstruct
	move.l	d0,a1
	clr.l	p_SoftInt(a3)
	call	FreeVec
.nosoftintstruct
	tst.l	p_audiodev(a3)
	bne.b	.noaudiodev
	move.l	p_audioreq(a3),a1
	move.w	#CMD_RESET,IO_COMMAND(a1)	;Restore interrupts
	bsr	BeginIO
	move.l	p_audioport(a3),a0
	call	WaitPort
	move.l	p_audioport(a3),a0
	call	GetMsg
	move.l	p_audioreq(a3),a1
	subq.l	#1,p_audiodev(a3)
	call	CloseDevice
.noaudiodev
	move.l	p_audioreq(a3),d0
	beq.b	.noaudioreq
	move.l	d0,a1
	clr.l	p_audioreq(a3)
	call	FreeVec
.noaudioreq
	move.l	p_audioport(a3),d0
	beq.b	.noaudioport
	move.l	d0,a0
	clr.l	p_audioport(a3)
	call	DeleteMsgPort
.noaudioport
	move.l	a3,a1
	clr.l	ahiac_DriverData(a2)
	call	FreeVec
.nopaula
	moveq	#0,d0
	popm	d2-d7/a2-a6
	rts


******* <driver>.audio/AHIsub_Disable ***************************************
*
*   NAME
*       AHIsub_Disable -- Temporary turn off audio interrupt/task
*
*   SYNOPSIS
*       AHIsub_Disable( audioctrl );
*                       A2
*
*       void AHIsub_Disable( struct AHIAudioCtrl * );
*
*   DO-IT-YOURSELF
*       If you are lazy, then call exec.library/Disable().
*       If you are smart, only disable your own interrupt or task.
*
*   INPUTS
*       audioctrl - pointer to an AHIAudioCtrl structure.
*
*   NOTES
*       This call should be guaranteed to preserve all registers.
*       This call nests.
*
*   SEE ALSO
*       AHIsub_Enable(), exec.library/Disable()
*
*****************************************************************************
*
*

AHIsub_Disable:
	push	a3
	move.l	ahiac_DriverData(a2),a3
	addq.w	#1,p_DisableCount(a3)
	AUDINT_OFF
	pop	a3
	rts

******* <driver>.audio/AHIsub_Enable ****************************************
*
*   NAME
*       AHIsub_Enable -- Turn on audio interrupt/task
*
*   SYNOPSIS
*       AHIsub_Enable( audioctrl );
*                      A2
*
*       void AHIsub_Enable( struct AHIAudioCtrl * );
*
*   DO-IT-YOURSELF
*       If you are lazy, then call exec.library/Enable().
*       If you are smart, only enable your own interrupt or task.
*
*   INPUTS
*       audioctrl - pointer to an AHIAudioCtrl structure.
*
*   NOTES
*       This call should be guaranteed to preserve all registers.
*       This call nests.
*
*   SEE ALSO
*       AHIsub_Disable(), exec.library/Enable()
*
*****************************************************************************
*
*

AHIsub_Enable:
	push	a3
	move.l	ahiac_DriverData(a2),a3
	subq.w	#1,p_DisableCount(a3)
	bne.b	.exit
	AUDINT_ON
.exit
	pop	a3
	rts

******* <driver>.audio/AHIsub_Start *****************************************
*
*   NAME
*       AHIsub_Start -- Starts playback or recording
*
*   SYNOPSIS
*       error = AHIsub_Start( flags, audioctrl );
*       D0                    D0     A2
*
*       ULONG AHIsub_Start(ULONG, struct AHIAudioCtrl * );
*
*   DO-IT-YOURSELF
*       What to do depends what you returned in AHIsub_AllocAudio().
*
*     * First, assume bit AHISB_PLAY in flags is set. This means that you
*       should begin playback.
*
*     - AHIsub_AllocAudio() returned AHISF_MIXING|AHISF_TIMING:
*
*       A) Allocate a mixing buffer of ahiac_BuffSize bytes.
*       B) Create/start an interrupt or task that will do 1-4 over and over
*          again until AHIsub_Stop() is called. Note that it is not a good
*          idea to do the actual mixing and conversion in a real hardware
*          interrupt. Signal a task or create a Software Interrupt to do
*          the number crunching.
*
*       1) Call the user Hook ahiac_PlayerFunc with the following parameters:
*                  A0 - (struct Hook *)
*                  A2 - (struct AHIAudioCtrl *)
*                  A1 - Set to NULL.
*       2) Call the mixing Hook (ahiac_MixerFunc) with the following
*          parameters:
*                  A0 - (struct Hook *)           - The Hook itself
*                  A2 - (struct AHIAudioCtrl *)
*                  A1 - (WORD *[])                - The mixing buffer.
*          Note that ahiac_MixerFunc preserves ALL registers.
*          The user Hook ahiac_SoundFunc will be called by the mixing
*          routine when a sample have been processed, so you don't have to
*          worry about that.
*          How the buffer will be filled is indicated by ahiac_Flags.
*          It is allways filled with signed 16-bit (32 bit if AHIDBB_HIFI in
*          in ahiac_Flags is set) words, even if playback is 8 bit. If
*          AHIDBB_STEREO is set (in ahiac_Flags), data for left and right
*          channel are interleved:
*           1st sample left channel,
*           1st sample right channel,
*           2nd sample left channel,
*           ...,
*           ahiac_BuffSamples:th sample left channel,
*           ahiac_BuffSamples:th sample right channel.
*          If AHIDBB_STEREO is cleared, the mono data is stored:
*           1st sample,
*           2nd sample,
*           ...,
*           ahiac_BuffSamples:th sample.
*          Note that neither AHIDBB_STEREO nor AHIDBB_HIFI will be set if
*          you didn't report that you understand these flags when
*          AHI_AllocAudio() was called.
*       3) Convert the buffer if needed and feed it to the audio hardware.
*          Note that you may have to clear CPU caches if you are using DMA
*          to play the buffer, and the buffer is not allocated in non-
*          cachable RAM.
*       4) Wait until the whole buffer has been played, then repeat.
*
*       Use double buffering if possible!
*
*       You may DECREASE ahiac_BuffSamples slightly, for example to force an
*       even number of samples to be mixed. By doing this you will make
*       ahiac_PlayerFunc to be called at wrong frequency so be careful!
*       Even if ahiac_BuffSamples is defined ULONG, it will never be greater
*       than 65535.
*
*       ahiac_BuffSize is the largest size of the mixing buffer that will be
*       needed until AHIsub_Stop() is called.
*
*       ahiac_MaxBuffSamples is the maximum number of samples that will be
*       mixed (until AHIsub_Stop() is called). You can use this value if you
*       need to allocate DMA buffers.
*
*       ahiac_MinBuffSamples is the minimum number of samples that will be
*       mixed. Most drivers will ignore it.
*
*
*     - If AHIsub_AllocAudio() returned AHISF_MIXING, do as described above,
*       except calling ahiac_PlayerFunc. ahiac_PlayerFunc should be called
*       ahiac_PlayerFreq times per second, clocked by timers on your sound
*       card or by using 'realtime.library'. No other Amiga resources may
*       be used for timing (like direct CIA timers).
*       ahiac_MinBuffSamples and ahiac_MaxBuffSamples are undefined if
*       AHIsub_AllocAudio() returned AHISF_MIXING (AHISB_TIMING bit not set).
*
*     - If AHIsub_AllocAudio() returned NULL, start playback. Don't forget to
*       call ahiac_PlayerFunc ahiac_PlayerFreq times per second. Only your
*       own timing hardware or 'realtime.library' may be used.
*       Note that ahiac_MixFreq, ahiac_MixerFunc, ahiac_BuffSamples,
*       ahiac_MinBuffSamples, ahiac_MaxBuffSamples and ahiac_BuffSize are
*       undefined if AHIsub_AllocAudio() returned NULL.
*
*
*     * Second, assume bit AHISB_RECORD in flags is set. This means that you
*       should start to sample. Create a interrupt or task that does the
*       following:
*
*       Allocate a buffer (you chose size, but try to keep it reasonable
*       small to avoid delays), and fill it with the sampled data. The format
*       should always be AHIST_S16S (even with 8 bit mono samplers), which
*       means:
*           1st sample left channel,
*           1st sample right channel (same as prev. if mono),
*           2nd sample left channel,
*           ... etc.
*       Each sample is a signed word (WORD). The sample rate should be equal
*       to the mixing rate.
*
*       Call the ahiac_SamplerFunc Hook with the following parameters:
*           A0 - (struct Hook *)           - The Hook itself
*           A2 - (struct AHIAudioCtrl *)
*           A1 - (struct AHIRecordMessage *)
*       The message should be filled as follows:
*           ahism_Buffer - A pointer to the filled buffer.
*           ahism_Samples - How many sample frames stored.
*           ahism_Type - Set to AHIST_S16S.
*       You must not destroy the buffer until next time the Hook is called.
*
*       Repeat until AHIsub_Stop() is called.
*
*   INPUTS
*       flags - See <libraries/ahi_sub.h>.
*       audioctrl - pointer to an AHIAudioCtrl structure.
*
*   RESULT
*       Returns AHIE_OK if successful, else an error code as defined
*       in <libraries/ahi.h>. AHIsub_Stop() will always be called, even
*       if this call failed.
*
*   NOTES
*
*   SEE ALSO
*       AHIsub_Update(), AHIsub_Stop()
*
*****************************************************************************
*
*
*

AHIsub_Start:
	pushm	d2-d7/a2-a6

	btst	#AHISB_PLAY,d0
	beq.w	.return

	call	AHIsub_Update			;fill variables

	lea	CUSTOM,a4
	move.l	a6,a5
	move.l	pb_SysLib(a5),a6
	move.l	ahiac_DriverData(a2),a3

	move.l	p_SoftInt(a3),a0		;initXXX will fill IS_CODE
	lea	p_SoftIntData(a3),a1
	move.l	a1,IS_DATA(a0)

	pea	.1(pc)
	move.b	p_Flags(a3),d0
	and.b	#PF_STEREO!PF_14BIT,d0
	beq.w	init8bitM
	cmp.b	#PF_STEREO,d0
	beq.w	init8bitS
	cmp.b	#PF_14BIT,d0
	beq.w	init14bitM
	bra.w	init14bitS
.1
	tst.l	d0
	bne.b	.exit

	move.l	ahiac_BuffSize(a2),d0
	move.l	#MEMF_PUBLIC|MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_Mixbuffer(a3)
	beq.b	.error_nomixmem

	move.l	ahiac_MixFreq(a2),d1
	bsr	calcperiod
	move.w	d1,p_AudPer(a3)

;	bset.b	#PB_INT,p_Flags(a3)

;	move.w	DMACONR(a4),d0
;	and.w	#DMAF_AUDIO,d0
;	or.w	#DMAF_SETCLR,d0
;	move.w	d0,p_OldDMACON(a3)

;	move.w	INTENAR(a4),d0
;	and.w	#INTF_AUDIO,d0
;	or.w	#INTF_SETCLR,d0
;	move.w	d0,p_OldINTENA(a3)

	move.l	p_Interrupt(a3),a1
	moveq	#INTB_AUD0,d0
	call	SetIntVector
	clr.w	p_DisableCount(a3)
	AUDINT_ON

	move.w	#INTF_SETCLR!INTF_AUD0,INTREQ(a4) ;start
.return
	moveq	#AHIE_OK,d0
.exit
	popm	d2-d7/a2-a6
	rts
.error_nomixmem
	moveq	#AHIE_NOMEM,d0
	bra.b	.exit


init_error_nodmamem
	moveq	#AHIE_NOMEM,d0
	rts

;in:
* a2	AudioCtrl
* a3	paula
* a4	CUSTOM
* a5	paulaBase
* a6	ExecBase
init8bitM:
	move.l	p_SoftInt(a3),a0
	lea	SoftInt_8bitM(pc),a1
	move.l	a1,IS_CODE(a0)

	move.l	ahiac_MaxBuffSamples(a2),d0	;Max. # of 8 bit samples
	move.l	d0,d2				;d2 = channel size

	lsl.l	#1,d0				;Double buffer
	move.l	#MEMF_CHIP!MEMF_PUBLIC|MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_DMAbuffer(a3)
	beq.b	init_error_nodmamem

	move.l	d0,p_AudPtr1A(a3)
	move.l	d0,p_AudPtr2A(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr1B(a3)
	move.l	d0,p_AudPtr2B(a3)

	move.w	#64,AUD0VOL(a4)
	move.w	#64,AUD1VOL(a4)

	moveq	#0,d0
	rts

;in:
* a2	AudioCtrl
* a3	paula
* a5	paulaBase
* a6	ExecBase
init8bitS:
	move.l	p_SoftInt(a3),a0
	lea	SoftInt_8bitS(pc),a1
	move.l	a1,IS_CODE(a0)

	move.l	ahiac_MaxBuffSamples(a2),d0	;Max. # of 8 bit samples
	move.l	d0,d2				;d2 = channel size

	lsl.l	#2,d0				;Double buffer + Stereo
	move.l	#MEMF_CHIP!MEMF_PUBLIC|MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_DMAbuffer(a3)
	beq.w	init_error_nodmamem

	move.l	d0,p_AudPtr1A(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr2A(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr1B(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr2B(a3)

	move.w	#64,AUD0VOL(a4)
	move.w	#64,AUD1VOL(a4)

	moveq	#0,d0
	rts

;in:
* a2	AudioCtrl
* a3	paula
* a5	paulaBase
* a6	ExecBase
init14bitM:
	move.l	p_SoftInt(a3),a0
	lea	SoftInt_14bitM(pc),a1
	tst.l	p_CalibrationTable(a3)
	beq.b	.nocalib
	lea	SoftInt_14CbitM(pc),a1
.nocalib
	move.l	a1,IS_CODE(a0)

	move.l	ahiac_MaxBuffSamples(a2),d0	;Max. # of 16 bit samples
	move.l	d0,d2				;d2 = channel size

	lsl.l	#2,d0				;Double buffer + 2×8 bit
	move.l	#MEMF_CHIP!MEMF_PUBLIC|MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_DMAbuffer(a3)
	beq.w	init_error_nodmamem

	move.l	d0,p_AudPtr1A(a3)
	move.l	d0,p_AudPtr2A(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr4A(a3)
	move.l	d0,p_AudPtr3A(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr1B(a3)
	move.l	d0,p_AudPtr2B(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr4B(a3)
	move.l	d0,p_AudPtr3B(a3)

	move.w	#64,AUD0VOL(a4)
	move.w	#64,AUD1VOL(a4)
	move.w	#1,AUD2VOL(a4)
	move.w	#1,AUD3VOL(a4)

	moveq	#0,d0
	rts

;in:
* a2	AudioCtrl
* a3	paula
* a5	paulaBase
* a6	ExecBase
init14bitS:
	move.l	p_SoftInt(a3),a0
	lea	SoftInt_14bitS(pc),a1
	tst.l	p_CalibrationTable(a3)
	beq.b	.nocalib
	lea	SoftInt_14CbitS(pc),a1
.nocalib
	move.l	a1,IS_CODE(a0)

	move.l	ahiac_MaxBuffSamples(a2),d0	;Max. # of 16 bit samples
	move.l	d0,d2				;d2 = channel size

	lsl.l	#3,d0				;Double buffer + 2×8 bit + Stereo
	move.l	#MEMF_CHIP!MEMF_PUBLIC|MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_DMAbuffer(a3)
	beq.w	init_error_nodmamem

	move.l	d0,p_AudPtr1A(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr2A(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr3A(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr4A(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr1B(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr2B(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr3B(a3)
	add.l	d2,d0
	move.l	d0,p_AudPtr4B(a3)

	move.w	#64,AUD0VOL(a4)
	move.w	#64,AUD1VOL(a4)
	move.w	#1,AUD2VOL(a4)
	move.w	#1,AUD3VOL(a4)

	moveq	#0,d0
	rts

******* <driver>.audio/AHIsub_Update ****************************************
*
*   NAME
*       AHIsub_Update -- Update some variables
*
*   SYNOPSIS
*       AHIsub_Update( flags, audioctrl );
*                      D0     A2
*
*       void AHIsub_Update(ULONG, struct AHIAudioCtrl * );
*
*   DO-IT-YOURSELF
*       All you have to do is to update some variables:
*       Mixing & timing: ahiac_PlayerFunc, ahiac_MixerFunc,
*       ahiac_BuffSamples (and perhaps ahiac_PlayerFreq if you use it).
*       Mixing only: ahiac_PlayerFunc, ahiac_MixerFunc and ahiac_PlayerFreq.
*       Nothing: ahiac_PlayerFunc and ahiac_PlayerFreq.
*
*   INPUTS
*       flags - Currently no flags defined.
*       audioctrl - pointer to an AHIAudioCtrl structure.
*
*   RESULT
*
*   NOTES
*       This call must be safe from interrupts.
*
*   SEE ALSO
*       AHIsub_Start()
*
*****************************************************************************
*
*
*

AHIsub_Update:
	pushm	d2-d7/a2-a6

	call	AHIsub_Disable		;make sure we don't get an interrupt
					;while updating our local variables
	move.l	ahiac_DriverData(a2),a3

	move.l	ahiac_PlayerFunc(a2),a0
	move.l	a0,p_PlayerHook(a3)
	move.l	h_Entry(a0),p_PlayerEntry(a3)

	move.l	ahiac_BuffSamples(a2),d0
	lsr.l	#1,d0			;length in words, force even # of samples
	move.w	d0,p_AudLen(a3)
	subq.l	#1,d0
	move.l	d0,p_LoopTimes(a3)		;See softints. (Unrolled)

	move.l	ahiac_MixerFunc(a2),a0
	move.l	a0,p_MixHook(a3)
	move.l	h_Entry(a0),p_MixEntry(a3)

	call	AHIsub_Enable
	moveq	#0,d0
	popm	d2-d7/a2-a6
	rts


******* <driver>.audio/AHIsub_Stop ******************************************
*
*   NAME
*       AHIsub_Stop -- Stops playback.
*
*   SYNOPSIS
*       AHIsub_Stop( flags, audioctrl );
*                    D0     A2
*
*       void AHIsub_Stop( ULONG, struct AHIAudioCtrl * );
*
*   DO-IT-YOURSELF
*       Stop playback and/or recording, remove all resources allocated by
*       AHIsub_Start().
*
*   INPUTS
*       flags - See <libraries/ahi_sub.h>.
*       audioctrl - pointer to an AHIAudioCtrl structure.
*
*   NOTES
*       It must be safe to call this routine even if AHIsub_Start() was never
*       called, failed or called more than once.
*
*   SEE ALSO
*       AHIsub_Start()
*
*****************************************************************************
*
*

AHIsub_Stop:
	pushm	d2-d7/a2-a6

	btst	#AHISB_PLAY,d0
	beq.b	.return
	move.l	a6,a5
	move.l	pb_SysLib(a5),a6
	move.l	ahiac_DriverData(a2),a3

	lea	CUSTOM,a4
	moveq	#0,d0
	move.w	d0,AUD0VOL(a4)
	move.w	d0,AUD1VOL(a4)
	move.w	d0,AUD2VOL(a4)
	move.w	d0,AUD3VOL(a4)
	move.w	#DMAF_AUDIO,DMACON(a4)		;disable audio DMA
;	move.w	p_OldDMACON(a3),DMACON(a4)	;restore audio DMA

;	bclr.b	#PB_INT,p_Flags(a3)
;	beq.b	.noint
	AUDINT_OFF
	move.w	#INTF_AUDIO,INTREQ(a4)		;Clear any waiting interrupts
	move.l	p_NoInterrupt(a3),a1
	moveq	#INTB_AUD0,d0
	call	SetIntVector
;	move.w	p_OldINTENA(a3),INTENA(a4)	;restore interrupts
.noint
	move.l	p_DMAbuffer(a3),d0
	beq.b	.nodmamem
	move.l	d0,a1
	clr.l	p_DMAbuffer(a3)
	call	FreeVec
.nodmamem
	move.l	p_Mixbuffer(a3),d0
	beq.b	.nomixmem
	move.l	d0,a1
	clr.l	p_Mixbuffer(a3)
	call	FreeVec
.nomixmem
.return
	moveq	#0,d0
	popm	d2-d7/a2-a6
	rts

******* <driver>.audio/AHIsub_#? ********************************************
*
*   NAME
*       AHIsub_SetEffect -- Set effect.
*       AHIsub_SetFreq -- Set frequency.
*       AHIsub_SetSound -- Set sound.
*       AHIsub_SetVol -- Set volume and stereo panning.
*       AHIsub_LoadSound -- Prepare a sound for playback.
*       AHIsub_UnLoadSound -- Discard a sound.
*
*   SYNOPSIS
*       See functions in 'ahi.library'.
*
*   DO-IT-YOURSELF
*       If AHIsub_AllocAudio() did not return with bit AHISB_MIXING set,
*       all user calls to these function will be routed to the driver.
*
*       If AHIsub_AllocAudio() did return with bit AHISB_MIXING set, the
*       calls will first be routed to the driver, and only handled by
*       'ahi.library' if the driver returned AHIS_UNKNOWN. This way it is
*       possible to add, for example, a master volume effect that takes
*       advantage of hardware volume control, and falls back to digital
*       volume control if that failed.
*
*       For what each funtion does, see the autodocs for 'ahi.library'.
*
*       If your hardware supports it, you are encouraged to handle all
*       AHIST_INPUT sounds yourself. The volume is calculated as
*       SUM(Volume)/Channels (linear) of all currently playing sounds of this
*       type. You need to intercept AHIsub_SetFreq, AHIsub_SetSound,
*       AHIsub_SetVol and AHIsub_LoadSound for this feature.
*
*   INPUTS
*       See functions in 'ahi.library'.
*
*   NOTES
*       See functions in 'ahi.library'.
*
*   SEE ALSO
*       ahi.library/AHI_SetEffect(), ahi.library/AHI_SetFreq(),
*       ahi.library/AHI_SetSound(), ahi.library/AHI_SetVol(),
*       ahi.library/AHI_LoadSound(), ahi.library/AHI_UnLoadSound()
*       
*
*****************************************************************************
*
*

AHIsub_SetVol:
AHIsub_SetFreq:
AHIsub_SetSound:
AHIsub_SetEffect:
AHIsub_LoadSound:
AHIsub_UnLoadSound:
	moveq	#AHIS_UNKNOWN,d0
	rts

******* <driver>.audio/AHIsub_GetAttr ***************************************
*
*   NAME
*       AHIsub_GetAttr -- Returns information about audio modes or driver
*
*   SYNOPSIS
*       AHIsub_GetAttr( attribute, argument, default, taglist, audioctrl );
*       D0              D0         D1        D2       A1       a2
*
*       LONG AHIsub_GetAttr( ULONG, LONG, LONG, struct TagItem *,
*                            struct AHIAudioCtrl * );
*
*   DO-IT-YOURSELF
*       Return the attribute based on a tag list and an AHIAudioCtrl
*       structure, which are the same that will be passed to
*       AHIsub_AllocAudio() by 'ahi.library'. If the attribute is
*       unknown to you, return the default.
*
*   INPUTS
*       attribute - Is really a Tag and can be one of the following:
*           AHIDB_Bits - Return how many output bits the tag list will
*               result in.
*           AHIDB_MaxChannels - Return the resulting number of channels.
*           AHIDB_Frequencies - Return how many mixing/sampling frequencies
*               you support
*           AHIDB_Frequency - Return the argument:th frequency
*               Example: You support 3 frequencies 32, 44.1 and 48 kHz.
*                   If argument is 1, return 44100.
*           AHIDB_Index - Return the index which gives the frequency closest
*               to argument.
*               Example: You support 3 frequencies 32, 44.1 and 48 kHz.
*                   If argument is 40000, return 1 (=> 44100)-
*           AHIDB_Author - Return pointer to name of driver author:
*               "Martin 'Leviticus' Blom"
*           AHIDB_Copyright - Return pointer to copyright notice, including
*               the '©' character: "© 1996 Martin Blom" or "Public Domain"
*           AHIDB_Version - Return pointer version string, normal Amiga
*               format: "paula 1.5 (18.2.96)\r\n"
*           AHIDB_Annotation - Return pointer to an annotation string, which
*               can be several lines.
*       argument - extra info for some attributes.
*       default - What you should return for unknown attributes.
*       taglist - Pointer to a tag list that eventually will be fed to
*           AHIsub_AllocAudio(), or NULL.
*       audioctrl - Pointer to an AHIAudioCtrl structure that eventually
*           will be fed to AHIsub_AllocAudio(), or NULL.
*
*   NOTES
*
*   SEE ALSO
*       AHIsub_AllocAudio()
*
*****************************************************************************
*
*

AHIsub_GetAttr:
	pushm	d2/a5-a6
	move.l	a6,a5
	tst.l	a1
	beq.b	.no14bit			;no tag list!
	cmp.l	#AHIDB_Bits,d0
	bne.b	.not_bits
	move.l	pb_UtilLib(a5),a6
	move.l	#AHIDB_Paula14Bit,d0
	moveq	#FALSE,d1
	move.l	a1,a0
	call	GetTagData
	tst.l	d0
	beq.b	.no14bit
	moveq	#14,d0
	skipw
.no14bit
	moveq	#8,d0
	bra.w	.exit
.not_bits
	cmp.l	#AHIDB_Frequencies,d0
	bne.b	.not_freqs
	move.l	#MAXPER-MINPER+1,d0
	bra.w	.exit
.not_freqs
	cmp.l	#AHIDB_Frequency,d0
	bne.b	.not_freq
	sub.w	#MAXPER,d1
	neg.w	d1

	move.l	#PALFREQ,d2			;PAL
	move.l	pb_GfxLib(a5),a0
	move.w	gb_DisplayFlags(a0),d0
	btst	#REALLY_PALn,d0
	bne.b	.1
	move.l	#NTSCFREQ,d2			;NTSC
.1
	divu.w	d1,d2
	moveq	#0,d0
	move.w	d2,d0
	bra.b	.exit
.not_freq
	cmp.l	#AHIDB_Index,d0
	bne.b	.not_index
	bsr	calcperiod
	sub.w	#MAXPER,d1
	neg.w	d1
	moveq	#0,d0
	move.w	d1,d0
	bra.b	.exit
.not_index
	cmp.l	#AHIDB_Author,d0
	bne.b	.not_author
	lea	.author(pc),a0
	move.l	a0,d0
	bra.b	.exit
.not_author
	cmp.l	#AHIDB_Copyright,d0
	bne.b	.not_copyright
	lea	.copyright(pc),a0
	move.l	a0,d0
	bra.b	.exit
.not_copyright
	cmp.l	#AHIDB_Version,d0
	bne.b	.not_version
	lea	IDString(pc),a0
	move.l	a0,d0
	bra.b	.exit
.not_version
	cmp.l	#AHIDB_Annotation,d0
	bne.b	.not_anno
	lea	.anno(pc),a0
	move.l	a0,d0
	bra.b	.exit
.not_anno

* Unknown attribute, return default.
	move.l	d2,d0
.exit
	popm	d2/a5-a6
	rts
.author
	dc.b	"Martin 'Leviticus' Blom",0
.copyright
	dc.b	"Public Domain",0
.anno
	dc.b	"14 bit routines by Christian Buchner.",0
	even

;----------------------------------------------------------------------------

Interrupt_Dummy:
	move.w	d1,INTREQ(a0)
SoftInt_Dummy:
	rts

;in:
* d0	scratch
* d1	INTENAR && INTREQR
* a0	CUSTOM
* a1	&(paulaBase->p_AudIntData)
* a5	&AudioInterrupt
* a6	ExecBase

AudioInterrupt:
	move.w	d1,INTREQ(a0)
	movem.l	(a1)+,d0/d1			;p_SoftInt,p_AudLen/p_AudPer
	move.l	d1,AUD0LEN(a0)
	move.l	d1,AUD1LEN(a0)
	move.l	d1,AUD2LEN(a0)
	move.l	d1,AUD3LEN(a0)
	not.l	(a1)+				;double buffering
	lea	p_AudPtrs-p_AudPtr1A(a1),a5
	beq.b	.1
	add.w	#4*4,a1
.1
	move.l	a1,(a5)
	move.l	(a1)+,AUD0LC(a0)
	move.l	(a1)+,AUD1LC(a0)
	move.l	(a1)+,AUD2LC(a0)
	move.l	(a1)+,AUD3LC(a0)
	move.l	d0,a1				;p_SoftInt
	jmp	_LVOCause(a6)			;start SoftInt

;in:
* d0	scratch
* d1	scratch
* a0	scratch
* a1	&(paulaBase->p_SoftIntData)
* a5	scratch

SoftInt_8bitM:
	pushm	a2/a3
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3
	jsr	(a3)				;call Player Hook
	movem.l	(a5),d0/a0/a1/a2/a3/a5
	jsr	(a3)				;call Mixer Hook

* convert and transfer buffer
	move.l	(a5),a0
.loop
	move.b	(a1),(a0)+
	addq.l	#2,a1

	move.b	(a1),(a0)+
	addq.l	#2,a1
	dbf	d0,.loop

	popm	a2/a3
	move.w	#DMAF_SETCLR|DMAF_AUD0|DMAF_AUD1,CUSTOM+DMACON
	rts

;in:
* d0	scratch
* d1	scratch
* a0	scratch
* a1	&(paulaBase->p_SoftIntData)
* a5	scratch

SoftInt_8bitS:
	pushm	a2/a3
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3
	jsr	(a3)				;call Player Hook
	movem.l	(a5),d0/a0/a1/a2/a3/a5
	jsr	(a3)				;call Mixer Hook

* convert and transfer buffer
	movem.l	(a5),a0/a2
.loop
 REPT	2
;left
	move.b	(a1),(a0)+
;right
	move.b	2(a1),(a2)+
	addq.l	#4,a1
 ENDR
	dbf	d0,.loop
	popm	a2/a3
	move.w	#DMAF_SETCLR|DMAF_AUD0|DMAF_AUD1,CUSTOM+DMACON
	rts

;in:
* d0	scratch
* d1	scratch
* a0	scratch
* a1	&(paulaBase->p_SoftIntData)
* a5	scratch

SoftInt_14bitM:
	pushm	a2/a3
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3
	jsr	(a3)				;call Player Hook
	movem.l	(a5),d0/a0/a1/a2/a3/a5
	jsr	(a3)				;call Mixer Hook

* convert and transfer buffer
	movem.l	(a5),a0/a2/a3/a5		;a2/a3 unused
.loop
 REPT	2
	move.b	(a1)+,(a0)+
	move.b	(a1)+,d1
	asr.b	#2,d1
	move.b	d1,(a5)+
 ENDR
	dbf	d0,.loop
	popm	a2/a3
	move.w	#DMAF_SETCLR|DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3,CUSTOM+DMACON
	rts

;in:
* d0	scratch
* d1	scratch
* a0	scratch
* a1	&(paulaBase->p_SoftIntData)
* a5	scratch

SoftInt_14CbitM:
	pushm	a2/a3/a4
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3
	jsr	(a3)				;call Player Hook
	movem.l	(a5),d0/a0/a1/a2/a3/a4/a5
	jsr	(a3)				;call Mixer Hook

* convert and transfer buffer, using the table
	movem.l	(a4),a0/a2/a3/a4		;a2/a3 unused
	moveq	#0,d1
.loop

 REPT	2
 IFNE	MC020p
	move.w	(a1)+,d1			;fetch 16 bit sample
	move.w	(a5,d1.l*2),d1
 ELSE
	moveq	#0,d1
	move.w	(a1)+,d1			;fetch 16 bit sample
 	add.w	d1,d1
 	move.w	(a5,d1.l),d1
 ENDC
 	move.b	d1,(a4)+			;low byte
 	lsr.w	#8,d1
 	move.b	d1,(a0)+			;high byte
 ENDR
	dbf	d0,.loop
	popm	a2/a3/a4
	move.w	#DMAF_SETCLR|DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3,CUSTOM+DMACON
	rts

;in:
* d0	scratch
* d1	scratch
* a0	scratch
* a1	&(paulaBase->p_SoftIntData)
* a5	scratch

SoftInt_14bitS:
	pushm	a2/a3
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3
	jsr	(a3)				;call Player Hook
	movem.l	(a5),d0/a0/a1/a2/a3/a5
	jsr	(a3)				;call Mixer Hook

* convert and transfer buffer
* TODO: Use a table!
	movem.l	(a5),a0/a2/a3/a5
.loop
 REPT	2
;left
	move.b	(a1)+,(a0)+
	move.b	(a1)+,d1
	asr.b	#2,d1
	move.b	d1,(a5)+
;right
	move.b	(a1)+,(a2)+
	move.b	(a1)+,d1
	asr.b	#2,d1
	move.b	d1,(a3)+
 ENDR
	dbf	d0,.loop
	popm	a2/a3
	move.w	#DMAF_SETCLR|DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3,CUSTOM+DMACON
	rts

;in:
* d0	scratch
* d1	scratch
* a0	scratch
* a1	&(paulaBase->p_SoftIntData)
* a5	scratch

SoftInt_14CbitS:
	pushm	a2/a3/a4
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3
	jsr	(a3)				;call Player Hook
	movem.l	(a5),d0/a0/a1/a2/a3/a4/a5
	jsr	(a3)				;call Mixer Hook

* convert and transfer buffer, using the table
	movem.l	(a4),a0/a2/a3/a4
	moveq	#0,d1
.loop
 REPT	2

;left
 IFNE	MC020p
	move.w	(a1)+,d1			;fetch 16 bit sample
	move.w	(a5,d1.l*2),d1
 ELSE
	moveq	#0,d1
	move.w	(a1)+,d1			;fetch 16 bit sample
 	add.w	d1,d1
 	move.w	(a5,d1.l),d1
 ENDC
 	move.b	d1,(a4)+			;low byte
 	lsr.w	#8,d1
 	move.b	d1,(a0)+			;high byte

;right
 IFNE	MC020p
	move.w	(a1)+,d1			;fetch 16 bit sample
	move.w	(a5,d1.l*2),d1
 ELSE
	moveq	#0,d1
	move.w	(a1)+,d1			;fetch 16 bit sample
 	add.w	d1,d1
 	move.w	(a5,d1.l),d1
 ENDC
 	move.b	d1,(a3)+			;low byte
 	lsr.w	#8,d1
 	move.b	d1,(a2)+			;high byte
 ENDR

	dbf	d0,.loop
	popm	a2/a3/a4
	move.w	#DMAF_SETCLR|DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3,CUSTOM+DMACON
	rts
EndCode:
