*** ScR ***

*** NOTES ***

*  There is still room for improvements in the softint's. For example,
* try if reducing chip memory accesses increases speed. Not high priority, though.
*  This driver should probably have a DMA play mode for 1-4 stereo channels.
*
*  The sampler routines are just for fun. Since interrupts must not be disabled,
* there are lots of clicks. But still, I have tried to sample at 22kHz while down-
* loading files from a BBS on my A4000/040, and it worked without any serial errors.

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

VERSION		EQU	2
REVISION	EQU	4
DATE	MACRO	
		dc.b	"11.10.96"
	ENDM
VERS	MACRO
		dc.b	"paula 2.4"
	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

	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	lvo/cardres_lib.i

	include	devices/ahi.i
	include	libraries/ahi_sub.i

	include	resources/misc.i
	include	resources/card.i
	include	macros.i

_ciaa		EQU	$bfe001
_ciab		EQU	$bfd000

PALFREQ		EQU	3546895
NTSCFREQ	EQU	3579545
MINPER		EQU	62

RECORDSAMPLES	EQU	1024

 IFD	BARFLY
  IFD	MC020
	MC68020
  ENDC
 ELSE
  IFD	MC020
	auto	wo AHI:user/devs/ahi/paula.audio\
  ELSE	
	auto	wo AHI:user/devs/ahi/paula.audio.000\
  ENDC
 ENDC	* BARFLY


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

RECORD_PARALLELMONO	EQU	1
RECORD_AURA		EQU	2

 * 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
	APTR	pb_MiscResource
	APTR	pb_CardResource
	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
	APTR	p_DMAbuffer
	APTR	p_ParBitsUser
	APTR	p_ParPortUser
	APTR	p_CardHandle

	STRUCT	p_PlayInt,IS_SIZE
	STRUCT	p_PlaySoftInt,IS_SIZE
	STRUCT	p_RecInt,IS_SIZE
	STRUCT	p_RecSoftInt,IS_SIZE

	UWORD	p_MonitorVolume
	UWORD	p_Pad2

*** IS_DATA for Play Interrupt (do not change order!)
	LABEL	p_PlayIntData
	APTR	p_PlaySoftIntPtr
	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 Play Software Interrupt (do not change order!)
	LABEL	p_PlaySoftIntData
	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

*** IS_DATA for Aura Record Interrupt (do not change order!)
	LABEL	p_RecIntDataAura
	APTR	p_AuraAddress
*** IS_DATA for Paula Record Interrupt (do not change order!)
	LABEL	p_RecIntData
	APTR	p_RecFillPtr
	UWORD	p_RecFillCount
	UWORD	p_Pad3
	APTR	p_RecBuffer1
	APTR	p_RecBuffer2
	APTR	p_RecSoftIntPtr

	LABEL	p_RecordMessage
	ULONG	p_rmType
	APTR	p_rmBuffer
	ULONG	p_rmLength

	STRUCT	p_CalibrationArray,256
	LABEL	paula_SIZEOF

* p_Flags
	BITDEF	P,14BIT,0
	BITDEF	P,RECORD,1
PB_STEREO	EQU	AHIACB_STEREO		;=2
PF_STEREO	EQU	AHIACF_STEREO
	BITDEF	P,AURA,3

Start:
	moveq	#-1,d0
	rts

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
miscName:	MISCNAME
cardName:	dc.b	"card.resource",0
	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	AHIsub_HardwareControl
	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	.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	.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	.exit
.dosOK
	lea	miscName(pc),a1
	call	OpenResource
	move.l	d0,pb_MiscResource(a5)
	bne.b	.miscOK
	ALERT	AG_OpenRes!AO_MiscRsrc
	moveq	#0,d0
	bra	.exit
.miscOK
	lea	cardName(pc),a1
	call	OpenResource
	move.l	d0,pb_CardResource(a5)		;Don't fail on error

	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	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/--background-- ***************************************
*
*   OVERVIEW
*
*       AUDIO ID NUMBERS
*
*       Just some notes about selecting ID numbers for different modes:
*       It is up to the driver programmer to chose which modes should be
*       available to the user. Take care when selecting.
*       The upper word is the hardware ID, and can only be allocated by
*       Martin Blom <lcs@lysator.liu.se>. The lower word is free, but in
*       order to allow enhanchments, please only use bit 0 to 3 for modes!
*       Set the remaining bits to zero.
*       The ID is currently used by AHI_BestAudioIDA() to select a mode.
*       In order for this to work, you have to follow some rules.
*       · Modes using tables should have higher IDs than modes that uses more
*         CPU power.
*       · Mono modes should have higher IDs than stereo modes
*       · Stereo modes should have higher IDs than stereo modes with panning.
*       Use AHI:Developer/Support/ScanAudioModes to have a look at the modes
*       currently avalable.
*
*       I do reserve the right to change the rules if I find them incorrect!
*
*****************************************************************************
*
*

******* [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 AHIAudioCtrlDrv * );
*
*   IMPLEMENTATION
*       Allocate and initialize the audio hardware. Decide if and how you
*       wish to use the mixing routines provided by 'ahi.device', by looking
*       in the AHIAudioCtrlDrv 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 the hardware can't play samples with any length, use this
*           alternative and provide timing yourself. The buffer must
*           take less than about 20 ms to play, preferable less than 10!
*           · 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
*           Alternatively, you can use the first method and call the
*           mixing hook several times in a row to fill up a buffer.
*           In that case, AHIsub_GetAttr(AHIDB_MaxPlaySamples) should
*           return the size of the buffer plus AudioCtrl->ahiac_MaxBuffSamples.
*           If the buffer is so large that it takes more than (approx.) 10 ms to
*           play it for high sample frequencies, AHIsub_GetAttr(AHIDB_Realtime)
*           should return FALSE.
*       3) Don't use mixing routines:
*           If your hardware can handle everyting without using the CPU to
*           mix the channels, you tell 'ahi.device' this by not setting
*           neither the AHISB_MIXING nor the AHISB_TIMING bit.
*
*       If you can handle stereo output from the mixing routines, also set
*       bit AHISB_KNOWSTEREO.
*
*       If you can handle hifi (32 bit) output from the mixing routines,
*       set bit AHISB_KNOWHIFI.
*
*       If this driver can be used to record samples, set bit AHISB_CANRECORD,
*       too (regardless if you use the mixing routines in AHI or not).
*
*       If the sound card has hardware to do DSP effects, you can set the
*       AHISB_CANPOSTPROCESS bit. The output from the mixing routines will 
*       then be two separate buffers, one wet and one dry. You sould then
*       apply the Fx on the wet buffer, and post-mix the two buffers before
*       you send the sampels to the DAC. (V3)
*
*   INPUTS
*       tags - pointer to a taglist.
*       audioctrl - pointer to an AHIAudioCtrlDrv structure.
*
*   TAGS
*       The tags are from the audio database (AHIDB_#? in <devices/ahi.h>),
*       NOT the tag list the user called ahi.device/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_Start()
*
*****************************************************************************
*
*

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

	move.l	a1,d3

* Allocate the 'paula' structure (our variables)
	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	.error_nopaula
	move.l	d0,a3

* Initialize some fields...
	move.l	#-1,p_audiodev(a3)
	move.l	#-1,p_ParBitsUser(a3)
	move.l	#-1,p_ParPortUser(a3)
	move.l	a2,p_AudioCtrlP(a3)		;player Hook
	move.l	a2,p_AudioCtrlM(a3)		;mixer Hook
	lea	p_PlaySoftInt(a3),a0
	move.l	a0,p_PlaySoftIntPtr(a3)
	lea	p_RecSoftInt(a3),a0
	move.l	a0,p_RecSoftIntPtr(a3)
	move.l	#AHIST_S16S,p_rmType(a3)
	move.l	#RECORDSAMPLES,p_rmLength(a3)

* Translate tags to flags
	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	#AHIDB_PaulaRecord,d0
	moveq	#0,d1
	move.l	d3,a0				;tag list
	call	GetTagData
	tst.l	d0
	beq.b	.norecord
	or.b	#PF_RECORD,d2
	cmp.l	#RECORD_AURA,d0
	bne.b	.noAura
	or.b	#PF_AURA,d2
.noAura
.norecord
	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_AURA|PF_STEREO|PF_RECORD|PF_14BIT),d1
	or.b	d0,d1
	move.b	d1,p_Flags(a3)

* Check if a table should be used (14 bit calibration)
	move.l	#AHIDB_PaulaTable,d0
	moveq	#0,d1
	move.l	d3,a0				;tag list
	call	GetTagData
	tst.l	d0
	beq	.notable

* Load 'ENV:CyberSound/SoundDrivers/14Bit_Calibration', allocate
* and initialize the table.
* FIXIT: The calibration file should move to a special chunk 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	.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	.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.w	_CreateTable
.notable


* allocate audio.device
	move.l	pb_SysLib(a5),a6
	call	CreateMsgPort
	move.l	d0,p_audioport(a3)
	beq	.error_noport
	moveq	#ioa_SIZEOF,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_audioreq(a3)
	beq	.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	.error_noaudiodev		;somebody already owns the hardware (could be us!)
	move.l	p_audioreq(a3),a1
	move.w	#CMD_RESET,IO_COMMAND(a1)
	bsr.w	BeginIO				;clear attach, stop sound.
	move.l	p_audioport(a3),a0
	call	WaitPort
	move.l	p_audioport(a3),a0
	call	GetMsg

* test if mode supports recording
	btst	#PB_RECORD,p_Flags(a3)
	beq	.dontgetsampler
	btst	#PB_AURA,p_Flags(a3)
	bne	.getAura
* allocate parallel port
	move.l	pb_MiscResource(a5),a6
	moveq	#MR_PARALLELBITS,d0
	lea	IDString(pc),a1
	jsr	MR_ALLOCMISCRESOURCE(a6)
	move.l	d0,p_ParBitsUser(a3)
	bne	.error_noparbits
	moveq	#MR_PARALLELPORT,d0
	lea	IDString(pc),a1
	jsr	MR_ALLOCMISCRESOURCE(a6)
	move.l	d0,p_ParPortUser(a3)
	bne	.error_noparport

	move.b	#0,_ciaa+ciaddrb			;make PB0-PB7 inputs
	bra	.gotSampler

* allocate Aura sampler
.getAura
	moveq	#0,d2
	move.l	d2,p_AuraAddress(a3)

	move.l	pb_CardResource(a5),d0
	beq	.error_nocardres
	move.l	d0,a6
	call	GetCardMap
	tst.l	d0
	beq	.error_nocardmap
	move.l	d0,a0
	move.l	cmm_IOMemory(a0),d2
	beq	.error_nocardiomem

	base	exec
	moveq	#CardHandle_SIZEOF,d0
	move.l	#MEMF_PUBLIC|MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,p_CardHandle(a3)
	beq	.error_nocardhandle

	move.l	pb_CardResource(a5),a6
	move.l	d0,a1
	move.l	#IDString,LN_NAME(a1)
	move.b	#CARDF_RESETREMOVE|CARDF_IFAVAILABLE,cah_CardFlags(a1)
	call	OwnCard
	tst.l	d0
	bne	.error_nocardavailable

	move.l	p_CardHandle(a3),a1
	call	BeginCardAccess
	tst.l	d0
	beq	.error_nocardavailable

	move.l	d2,p_AuraAddress(a3)
.gotSampler
.dontgetsampler


* initialize interrupts (Only dummy function pointers at this time)

 * p_PlayInt (the main playback interrupt)
	move.b	#NT_INTERRUPT,LN_TYPE+p_PlayInt(a3)
	move.l	#LibName,LN_NAME+p_PlayInt(a3)
	move.l	#Interrupt_Dummy,IS_CODE+p_PlayInt(a3)
	lea	p_PlayIntData(a3),a1
	move.l	a1,IS_DATA+p_PlayInt(a3)

 * p_PlaySoftInt (caused by p_PlayInt, here are the mixing and conversion done)
	move.b	#NT_INTERRUPT,LN_TYPE+p_PlaySoftInt(a3)
	move.l	#LibName,LN_NAME+p_PlaySoftInt(a3)
	move.l	#SoftInt_Dummy,IS_CODE+p_PlaySoftInt(a3)
	lea	p_PlaySoftIntData(a3),a1
	move.l	a1,IS_DATA+p_PlaySoftInt(a3)

 * p_RecInt (the interrupt used for recording)
	move.b	#NT_INTERRUPT,LN_TYPE+p_RecInt(a3)
	move.l	#LibName,LN_NAME+p_RecInt(a3)
	move.l	#Interrupt_Dummy,IS_CODE+p_RecInt(a3)
	lea	p_RecIntData(a3),a1
	move.l	a1,IS_DATA+p_RecInt(a3)

 * p_RecSoftInt (caused by p_RecInt when the record buffer has been filled)
	move.b	#32,LN_PRI+p_RecSoftInt(a3)
	move.b	#NT_INTERRUPT,LN_TYPE+p_RecSoftInt(a3)
	move.l	#LibName,LN_NAME+p_RecSoftInt(a3)
	move.l	#RecordSoftInt,IS_CODE+p_RecSoftInt(a3)
	move.l	a3,IS_DATA+p_RecSoftInt(a3)

* Make sure no interrupts occur until AHIsub_Start() is called
	move.w	#INTF_AUD0!INTF_AUD1!INTF_AUD2!INTF_AUD3,CUSTOM+INTENA

* Update ahiac_MixFreq to what the mixing/sampling frequency really is
	move.l	ahiac_MixFreq(a2),d1
	bsr	calcperiod
	move.l	d0,ahiac_MixFreq(a2)		;store actual freq

	bset	#1,$bfe001			;turn audio filter off

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

.error_nocardavailable
.error_nocardhandle
.error_nocardiomem
.error_nocardmap
.error_nocardres
.error_noparport
.error_noparbits
.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
	even
.cardhandle:
	dc.l	0,0		;ln_Succ, ln_Pred
	dc.b	0		;ln_Type
	dc.b	0		;ln_Pri
	dc.l	IDString	;ln_Name
	dc.l	0,0,0		;cah_CardRemoved, cah_CardInserted, cah_CardStatus
	dc.b	(CARDF_RESETREMOVE|CARDF_IFAVAILABLE)
	even

;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
	move.l	d2,d0
	move.l	d1,d3
	move.l	pb_UtilLib(a5),a1
	jsr	_LVOUDivMod32(a1)
	lsl.l	#1,d1
	cmp.l	d3,d1
	bmi.b	.3
	addq.l	#1,d0
.3
	move.w	d0,d4
* d4 is now period. Check if is it a valid one, depending on current display mode
	bsr	checkvideo
	moveq	#MINPER,d1
	tst.l	d0
	bne.b	.5
	moveq	#MINPER*2,d1
.5
	cmp.w	d1,d4
	bhs.b	.6
	move.w	d1,d4
.6
	moveq	#0,d1
	move.w	d4,d1
	move.l	d2,d0
	move.l	d1,d3
	jsr	_LVOUDivMod32(a1)
	lsl.l	#1,d1
	cmp.l	d3,d1
	bmi.b	.4
	addq.l	#1,d0
.4
	move.w	d4,d1
	popm	d2-d4
	rts

;in:
* a5	paulaBase
;out:
* d0	TRUE if current mode is double
;description:
*	Checks if the current screen mode is doublescan.
*	This routine is *pretty* ugly, but it does get the job
*	done, even if a graphic card is used.
checkvideo:
	move.l	pb_GfxLib(a5),a0
	cmp.w	#39,LIB_VERSION(a0)
	beq.b	.known
	cmp.w	#40,LIB_VERSION(a0)
	beq.b	.known
	bra.b	.nodbl
.known
	move.l	gb_copinit(a0),a0
	cmp.w	#$01FC,copinit_fm0(a0)		;Security check (test if really FMODE)
	bne.b	.nodbl
	move.w	copinit_fm0+2(a0),d0
	and.w	#$c000,d0			;Mask sprite and bitplane double bit
	beq.b	.nodbl
	moveq	#TRUE,d0
	rts
.nodbl
	moveq	#FALSE,d0
	rts


;in:
* d0	Frequency
;out:
* d0	Closest frequency
* d1	Index
findfreq:
	lea	freqlist(pc),a0
	cmp.l	(a0),d0
	bls.b	.2
.findfreq
	cmp.l	(a0)+,d0
	bhi.b	.findfreq
	move.l	-4(a0),d1
	sub.l	d0,d1
	sub.l	-8(a0),d0
	cmp.l	d1,d0
	bhs.b	.1
	subq.l	#4,a0
.1
	subq.l	#4,a0
.2
	move.l	(a0),d0
	move.l	a0,d1
	sub.l	#freqlist,d1
	lsr.l	#2,d1
	rts

freqlist:
	dc.l	4410					; CD/10
	dc.l	4800					; DAT/10
	dc.l	5513					; CD/8
	dc.l	6000					; DAT/8
	dc.l	7350					; CD/6
	dc.l	8000					; µ- and A-Law, DAT/6
	dc.l	9600					; DAT/5
	dc.l	11025					; CD/4
	dc.l	12000					; DAT/4
	dc.l	14700					; CD/3
	dc.l	16000					; DAT/3
	dc.l	17640					; CD/2.5
	dc.l	18900
	dc.l	19200					; DAT/2.5
	dc.l	22050					; CD/2
	dc.l	24000					; DAT/2
	dc.l	27429
FREQUENCIES_OCS		EQU	(*-freqlist)/4
	dc.l	29400					; CD/1.5
	dc.l	32000					; DAT/1.5
	dc.l	33075
	dc.l	37800
	dc.l	44100					; CD
	dc.l	48000					; DAT
FREQUENCIES		EQU	(*-freqlist)/4
	dc.l	-1

* _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 AHIAudioCtrlDrv * );
*
*   IMPLEMENTATION
*       Deallocate the audio hardware and other resources allocated in
*       AHIsub_AllocAudio(). AHIsub_Stop() will always be called by
*       'ahi.device' before this call is made.
*
*   INPUTS
*       audioctrl - pointer to an AHIAudioCtrlDrv 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	.nopaula
	move.l	d0,a3

	move.l	p_CalibrationTable(a3),d0
	beq.b	.notable
	move.l	d0,a1
	call	FreeVec
.notable
	tst.l	p_audiodev(a3)
	bne.b	.noaudiodev
	move.l	p_audioreq(a3),a1
	move.w	#CMD_RESET,IO_COMMAND(a1)	;Restore audio interrupts
	bsr.w	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
	call	FreeVec
.noaudioreq
	move.l	p_audioport(a3),d0
	beq.b	.noaudioport
	move.l	d0,a0
	call	DeleteMsgPort
.noaudioport
	move.l	pb_CardResource(a5),d0
	beq	.noaura
	move.l	d0,a6
	move.l	p_CardHandle(a3),d0
	beq	.noaura
	move.l	d0,a1
	call	EndCardAccess
	move.l	p_CardHandle(a3),a1
	moveq	#CARDF_REMOVEHANDLE,d0
	call	ReleaseCard
.noaura
	move.l	pb_SysLib(a5),a6
	move.l	p_CardHandle(a3),d0
	beq	.nocardhandle
	move.l	d0,a1
	call	FreeVec
.nocardhandle
	move.l	pb_MiscResource(a5),a6
	tst.l	p_ParPortUser(a3)
	bne.b	.noparport
	moveq	#MR_PARALLELPORT,d0
	jsr	MR_FREEMISCRESOURCE(a6)
.noparport
	tst.l	p_ParBitsUser(a3)
	bne.b	.noparbits
	moveq	#MR_PARALLELBITS,d0
	jsr	MR_FREEMISCRESOURCE(a6)
.noparbits
	move.l	pb_SysLib(a5),a6
	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 AHIAudioCtrlDrv * );
*
*   IMPLEMENTATION
*       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 AHIAudioCtrlDrv 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)
	move.w	#INTF_AUD0,CUSTOM+INTENA
	pop	a3
	rts

******* [driver].audio/AHIsub_Enable ****************************************
*
*   NAME
*       AHIsub_Enable -- Turn on audio interrupt/task
*
*   SYNOPSIS
*       AHIsub_Enable( audioctrl );
*                      A2
*
*       void AHIsub_Enable( struct AHIAudioCtrlDrv * );
*
*   IMPLEMENTATION
*       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 AHIAudioCtrlDrv 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
	move.w	#INTF_SETCLR!INTF_AUD0,CUSTOM+INTENA
.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 AHIAudioCtrlDrv * );
*
*   IMPLEMENTATION
*       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 AHIAudioCtrlDrv *)
*                  A1 - Set to NULL.
*
*       2) Call the mixing Hook (ahiac_MixerFunc) with the following
*          parameters:
*                  A0 - (struct Hook *)           - The Hook itself
*                  A2 - (struct AHIAudioCtrlDrv *)
*                  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.
*
*          For AHI V2, the type of buffer is also avalable in ahiac_BuffType.
*          It is suggested that you use this value instead. ahiac_BuffType
*          can be one of AHIST_M16S, AHIST_S16S, AHIST_M32S and AHIST_S32S.
*
*       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 with the AHISB_CANPOSTPROCESS bit set,
*       ahiac_BuffSize is large enough to hold two buffers. The mixing buffer
*       will be filled with the wet buffer first, immedeately followed by the
*       dry buffer. I.e., ahiac_BuffSamples sample frames wet data, then
*       ahiac_BuffSamples sample frames dry data. The DSP fx should only be
*       applied to the wet buffer, and the two buffers should then be added
*       together. (V3)
*
*     - 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 with neither the AHISB_MIXING nor
*       the AHISB_TIMING bit set, then just 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_MixerFunc, ahiac_BuffSamples, ahiac_MinBuffSamples,
*       ahiac_MaxBuffSamples and ahiac_BuffSize are undefined. ahiac_MixFreq
*       is the frequency the user wants to use for recording, if you support
*       that.
*
*     * 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 - it is suggested that RecordFunc is called
*       at least 4 times/second for the lowers sampling rate, and more often
*       for higher rates), 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 AHIAudioCtrlDrv *)
*           A1 - (struct AHIRecordMessage *)
*       The message should be filled as follows:
*           ahirm_Type - Set to AHIST_S16S.
*           ahirm_Buffer - A pointer to the filled buffer.
*           ahirm_Samples - How many sample frames stored.
*       You must not destroy the buffer until next time the Hook is called.
*
*       Repeat until AHIsub_Stop() is called.
*
*     * Note that both bits may be set when this function is called.
*
*   INPUTS
*       flags - See <libraries/ahi_sub.h>.
*       audioctrl - pointer to an AHIAudioCtrlDrv structure.
*
*   RESULT
*       Returns AHIE_OK if successful, else an error code as defined
*       in <devices/ahi.h>. AHIsub_Stop() will always be called, even
*       if this call failed.
*
*   NOTES
*       The driver must be able to handle multiple calls to this routine
*       without preceding calls to AHIsub_Stop().
*
*   SEE ALSO
*       AHIsub_Update(), AHIsub_Stop()
*
*****************************************************************************
*
*
*

AHIsub_Start:
	pushm	d2-d7/a2-a6

	move.l	d0,d7
	lea	CUSTOM,a4
	move.l	ahiac_DriverData(a2),a3

	btst	#AHISB_PLAY,d0
	beq	.dont_play

**
*** AHISB_PLAY
**
	moveq	#AHISF_PLAY,d0
	call	AHIsub_Stop			;Stop current playback if any.
	call	AHIsub_Update			;fill variables

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

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

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

* The init*bit? routines allocates p_DMAbuffer, sets up p_AudPtr, sets the volume,
* stores the correct interrupt routines in p_PlayInt's and p_PlaySoftInt's IS_CODE.

	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	.exit

* Install play interrupt
	lea	p_PlayInt(a3),a1
	moveq	#INTB_AUD0,d0
	call	SetIntVector
	clr.w	p_DisableCount(a3)

	move.w	#INTF_SETCLR!INTF_AUD0,INTENA(a4)	;enable
	move.w	#INTF_SETCLR!INTF_AUD0,INTREQ(a4)	;start

	move.l	a5,a6


.dont_play
	btst	#AHISB_RECORD,d7
	beq	.dont_record
**
*** AHISB_RECORD
**
	btst	#PB_RECORD,p_Flags(a3)		;Sanity check...
	beq	.error_unknown

	moveq	#AHISF_RECORD,d0
	call	AHIsub_Stop			;Stop current recording if any.

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

	move.l	#RECORDSAMPLES*4,d0
	move.l	#MEMF_PUBLIC,d1
	call	AllocVec
	move.l	d0,p_RecBuffer1(a3)
	beq	.error_nomem

	move.l	d0,p_RecFillPtr(a3)
	move.w	#RECORDSAMPLES,p_RecFillCount(a3)

	move.l	#RECORDSAMPLES*4,d0
	move.l	#MEMF_PUBLIC,d1
	call	AllocVec
	move.l	d0,p_RecBuffer2(a3)
	beq	.error_nomem

	move.l	ahiac_MixFreq(a2),d1
	bsr.w	calcperiod
	lsr.w	#1,d1				;Period/2 => Frequency·2
	move.w	d1,AUD2PER(a4)
	move.w	d1,AUD3PER(a4)
	move.w	p_MonitorVolume(a3),AUD2VOL(a4)
	move.w	p_MonitorVolume(a3),AUD3VOL(a4)

* Install record interrupt
	move.l	#RecordInterrupt,IS_CODE+p_RecInt(a3)
	btst	#PB_AURA,p_Flags(a3)
	beq.b	.parsampler
	tst.l	p_AuraAddress(a3)
	beq	.error_unknown
	lea	p_RecIntDataAura(a3),a1
	move.l	a1,IS_DATA+p_RecInt(a3)
	move.l	#RecordInterruptAura,IS_CODE+p_RecInt(a3)
.parsampler
	lea	p_RecInt(a3),a1
	moveq	#INTB_AUD3,d0
	call	SetIntVector

	move.w	#DMAF_AUD2!DMAF_AUD3,DMACON(a4)		;disable DMA
	move.w	#INTF_SETCLR!INTF_AUD3,INTENA(a4)	;enable
	move.w	#INTF_SETCLR!INTF_AUD3,INTREQ(a4)	;start

.dont_record
.return
	moveq	#AHIE_OK,d0
.exit
	popm	d2-d7/a2-a6
	rts
.error_nomem
	moveq	#AHIE_NOMEM,d0
	bra.b	.exit
.error_unknown
	moveq	#AHIE_UNKNOWN,d0
	bra.b	.exit


;in:
* a2	AudioCtrl
* a3	paula
* a4	CUSTOM
* a5	paulaBase
* a6	ExecBase
init8bitM:
	move.l	#AudioInterrupt2,IS_CODE+p_PlayInt(a3)
	move.l	#SoftInt_8bitM,IS_CODE+p_PlaySoftInt(a3)

	move.l	ahiac_MaxBuffSamples(a2),d0	;Max. # of 8 bit samples
	addq.l	#1,d0
	bclr	#0,d0				;force even
	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	.nomem

	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
.nomem
	moveq	#AHIE_NOMEM,d0
	rts

;in:
* a2	AudioCtrl
* a3	paula
* a5	paulaBase
* a6	ExecBase
init8bitS:
	move.l	#AudioInterrupt2,IS_CODE+p_PlayInt(a3)
	move.l	#SoftInt_8bitS,IS_CODE+p_PlaySoftInt(a3)

	move.l	ahiac_MaxBuffSamples(a2),d0	;Max. # of 8 bit samples
	addq.l	#1,d0
	bclr	#0,d0				;force even
	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	.nomem

	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
.nomem
	moveq	#AHIE_NOMEM,d0
	rts

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

	move.l	ahiac_MaxBuffSamples(a2),d0	;Max. # of 16 bit samples
	addq.l	#1,d0
	bclr	#0,d0				;force even
	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	.nomem

	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
.nomem
	moveq	#AHIE_NOMEM,d0
	rts

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

	move.l	ahiac_MaxBuffSamples(a2),d0	;Max. # of 16 bit samples
	addq.l	#1,d0
	bclr	#0,d0				;force even
	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	.nomem

	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
.nomem
	moveq	#AHIE_NOMEM,d0
	rts

******* [driver].audio/AHIsub_Update ****************************************
*
*   NAME
*       AHIsub_Update -- Update some variables
*
*   SYNOPSIS
*       AHIsub_Update( flags, audioctrl );
*                      D0     A2
*
*       void AHIsub_Update(ULONG, struct AHIAudioCtrlDrv * );
*
*   IMPLEMENTATION
*       All you have to do is to update some variables:
*       Mixing & timing: ahiac_PlayerFunc, ahiac_MixerFunc, ahiac_SamplerFunc,
*       ahiac_BuffSamples (and perhaps ahiac_PlayerFreq if you use it).
*       Mixing only: ahiac_PlayerFunc, ahiac_MixerFunc, ahiac_SamplerFunc and
*           ahiac_PlayerFreq.
*       Nothing: ahiac_PlayerFunc, ahiac_SamplerFunc and ahiac_PlayerFreq.
*
*   INPUTS
*       flags - Currently no flags defined.
*       audioctrl - pointer to an AHIAudioCtrlDrv 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 AHIAudioCtrlDrv * );
*
*   IMPLEMENTATION
*       Stop playback and/or recording, remove all resources allocated by
*       AHIsub_Start().
*
*   INPUTS
*       flags - See <libraries/ahi_sub.h>.
*       audioctrl - pointer to an AHIAudioCtrlDrv 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

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

	push	d0
	btst	#AHISB_PLAY,d0
	beq	.dontplay

**
*** AHISB_PLAY
**
	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	#INTF_AUD0,INTENA(a4)
	move.w	#INTF_AUD0,INTREQ(a4)		;Clear any waiting interrupts
	move.l	#Interrupt_Dummy,IS_CODE+p_PlayInt(a3)
	lea	p_PlayInt(a3),a1
	moveq	#INTB_AUD0,d0
	call	SetIntVector

	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

.dontplay
	pop	d0
	btst	#AHISB_RECORD,d0
	beq	.dontrecord

**
*** AHISB_RECORD
**
	btst	#PB_RECORD,p_Flags(a3)		;Sanity check...
	beq	.dontrecord

	move.w	#INTF_AUD3,INTENA(a4)
	move.w	#INTF_AUD3,INTREQ(a4)		;Clear any waiting interrupts

	move.l	#Interrupt_Dummy,IS_CODE+p_RecInt(a3)
	lea	p_RecInt(a3),a1
	moveq	#INTB_AUD3,d0
	call	SetIntVector

	move.w	#0,AUD2VOL(a4)
	move.w	#0,AUD3VOL(a4)

	move.l	p_RecBuffer1(a3),d0
	beq.b	.norecmem1
	move.l	d0,a1
	clr.l	p_RecBuffer1(a3)
	call	FreeVec
.norecmem1
	move.l	p_RecBuffer2(a3),d0
	beq.b	.norecmem2
	move.l	d0,a1
	clr.l	p_RecBuffer2(a3)
	call	FreeVec
.norecmem2
.dontrecord
.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.device'.
*
*   IMPLEMENTATION
*       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.device' if the driver returned AHIS_UNKNOWN. This way it is
*       possible to add effects that the sound card handles on its own, like
*       filter and echo effects.
*
*       For what each funtion does, see the autodocs for 'ahi.device'.
*
*   INPUTS
*       See functions in 'ahi.device'.
*
*   NOTES
*       See functions in 'ahi.device'.
*
*   SEE ALSO
*       ahi.device/AHI_SetEffect(), ahi.device/AHI_SetFreq(),
*       ahi.device/AHI_SetSound(), ahi.device/AHI_SetVol(),
*       ahi.device/AHI_LoadSound(), ahi.device/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 AHIAudioCtrlDrv * );
*
*   IMPLEMENTATION
*       Return the attribute based on a tag list and an AHIAudioCtrlDrv
*       structure, which are the same that will be passed to
*       AHIsub_AllocAudio() by 'ahi.device'. 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.
*           AHIDB_Record - Are you a sampler, too? Return TRUE or FALSE.
*           AHIDB_FullDuplex - Return TRUE or FALSE.
*           AHIDB_Realtime - Return TRUE or FALSE.
*           AHIDB_MaxPlaySamples - Normally, return the default. See
*               AHIsub_AllocAudio(), section 2.
*           AHIDB_MaxRecordSamples - Return the size of the buffer you fill
*               when recoring.
*
*           The following are associated with AHIsub_HardwareControl() and are
*           new for V2.
*           AHIDB_MinMonitorVolume
*           AHIDB_MaxMonitorVolume - Return the lower/upper limit for
*               AHIC_MonitorVolume. If unsupported but always 1.0, return
*               1.0 for both.
*           AHIDB_MinInputGain
*           AHIDB_MaxInputGain - Return the lower/upper limit for
*               AHIC_InputGain. If unsupported but always 1.0, return 1.0 for
*               both.
*           AHIDB_MinOutputVolume
*           AHIDB_MaxOutputVolume - Return the lower/upper limit for
*               AHIC_OutputVolume.
*           AHIDB_Inputs - Return how many inputs you have.
*           AHIDB_Input - Return a short string describing the argument:th
*               input. Number 0 should be the default one. Example strings
*               can be "Line 1", "Mic", "Optical" or whatever.
*           AHIDB_Outputs - Return how many outputs you have.
*           AHIDB_Output - Return a short string describing the argument:th
*               output. Number 0 should be the default one. Example strings
*               can be "Line 1", "Headphone", "Optical" or whatever.
*       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 AHIAudioCtrlDrv structure that eventually
*           will be fed to AHIsub_AllocAudio(), or NULL.
*
*   NOTES
*
*   SEE ALSO
*       AHIsub_AllocAudio(), AHIsub_HardwareControl(),
*       ahi.device/AHI_GetAudioAttrsA()
*
*****************************************************************************
*
*

AHIsub_GetAttr:
	pushm	d3/a3/a5-a6
	move.l	a6,a5
	move.l	pb_UtilLib(a5),a6

 IFD	MC020
	tst.l	a1
 ELSE
 	cmp.l	#0,a1
 ENDC
	beq	.notaglist			;no tag list!

	move.l	a1,a3

	pushm	d0-d1

	move.l	#AHIDB_PaulaRecord,d0
	moveq	#FALSE,d1
	move.l	a3,a0
	call	GetTagData
	move.l	d0,d3
	beq	.2

* Only for AHIDB_PaulaRecord TRUE
	popm	d0-d1
	cmp.l	#AHIDB_Record,d0
	bne.b	.not_record1
	moveq	#TRUE,d0
	bra	.exit
.not_record1
	cmp.l	#AHIDB_FullDuplex,d0
	bne.b	.not_fullduplex1
	moveq	#TRUE,d0
	bra	.exit
.not_fullduplex1
	cmp.l	#AHIDB_MaxRecordSamples,d0
	bne.b	.not_mrs1
	move.l	#RECORDSAMPLES,d0
	bra	.exit
.not_mrs1
	cmp.l	#AHIDB_MinMonitorVolume,d0
	bne.b	.not_minmonvol1
	moveq	#0,d0
	bra	.exit
.not_minmonvol1
	cmp.l	#AHIDB_MaxMonitorVolume,d0
	bne.b	.not_maxmonvol1
	move.l	#$10000,d0
	bra	.exit
.not_maxmonvol1
	cmp.l	#AHIDB_MinInputGain,d0
	bne.b	.not_mingain1
	move.l	#$10000,d0
	bra	.exit
.not_mingain1
	cmp.l	#AHIDB_MaxInputGain,d0
	bne.b	.not_maxgain1
	move.l	#$10000,d0
	bra	.exit
.not_maxgain1
	cmp.l	#AHIDB_Inputs,d0
	bne.b	.not_inputs
	moveq	#1,d0
	bra	.exit
.not_inputs
	cmp.l	#AHIDB_Input,d0
	bne.b	.not_input
	lea	.input(pc),a0
	move.l	a0,d0
	cmp.l	#RECORD_AURA,d3
	bne	.exit
	lea	.input_Aura(pc),a0
	move.l	a0,d0
	bra	.exit
.not_input
	bra	.3


.2
* Only for AHIDB_PaulaRecord FALSE
	popm	d0-d1
	cmp.l	#AHIDB_Record,d0
	bne.b	.not_record2
	moveq	#FALSE,d0
	bra	.exit
.not_record2
	cmp.l	#AHIDB_FullDuplex,d0
	bne.b	.not_fullduplex2
	moveq	#FALSE,d0
	bra	.exit
.not_fullduplex2

.3
* Common tags (AHIDB_PaulaRecord does not matter)

	cmp.l	#AHIDB_Bits,d0
	bne.b	.not_bits
	move.l	#AHIDB_Paula14Bit,d0
	moveq	#FALSE,d1
	move.l	a3,a0
	call	GetTagData
	tst.l	d0
	beq.b	.no14bit
	moveq	#14,d0
	skipw
.no14bit
	moveq	#8,d0
	bra	.exit
.not_bits
.notaglist
	cmp.l	#AHIDB_Frequencies,d0
	bne.b	.not_freqs
	bsr	checkvideo
	tst.l	d0
	beq.b	.f
	move.l	#FREQUENCIES,d0
	bra	.exit
.f
	move.l	#FREQUENCIES_OCS,d0
	bra	.exit
.not_freqs

	cmp.l	#AHIDB_Frequency,d0
	bne.b	.not_freq
	add.w	d1,d1
	add.w	d1,d1
	lea	freqlist(pc),a0
;	move.l	(a0,d1.w),d1
;	bsr	calcperiod
	move.l	(a0,d1.w),d0
	bra	.exit
.not_freq
	cmp.l	#AHIDB_Index,d0
	bne.b	.not_index
	move.l	d1,d0
	bsr	findfreq
	move.l	d1,d0
	bra	.exit
.not_index
	cmp.l	#AHIDB_Author,d0
	bne.b	.not_author
	lea	.author(pc),a0
	move.l	a0,d0
	bra	.exit
.not_author
	cmp.l	#AHIDB_Copyright,d0
	bne.b	.not_copyright
	lea	.copyright(pc),a0
	move.l	a0,d0
	bra	.exit
.not_copyright
	cmp.l	#AHIDB_Version,d0
	bne.b	.not_version
	lea	IDString(pc),a0
	move.l	a0,d0
	bra	.exit
.not_version
	cmp.l	#AHIDB_Annotation,d0
	bne.b	.not_anno
	lea	.anno(pc),a0
	move.l	a0,d0
	bra	.exit
.not_anno
	cmp.l	#AHIDB_Realtime,d0
	bne.b	.not_realtime
	moveq	#TRUE,d0
	bra	.exit
.not_realtime
	cmp.l	#AHIDB_Outputs,d0
	bne.b	.not_outputs
	moveq	#1,d0
	bra	.exit
.not_outputs
	cmp.l	#AHIDB_Output,d0
	bne.b	.not_output
	lea	.output(pc),a0
	move.l	a0,d0
	bra	.exit
.not_output

* Unknown attribute, return default.
	move.l	d2,d0
.exit
	popm	d3/a3/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
.input		dc.b	"Parallel port",0
.input_Aura	dc.b	"Aura",0
.output		dc.b	"Line",0
	even


******* [driver].audio/AHIsub_HardwareControl *******************************
*
*   NAME
*       AHIsub_HardwareControl -- Modify sound card settings
*
*   SYNOPSIS
*       AHIsub_HardwareControl( attribute,  argument, audioctrl );
*       D0                      D0          D1        A2
*
*       LONG AHIsub_HardwareControl( ULONG, LONG, struct AHIAudioCtrlDrv * );
*
*   IMPLEMENTATION
*       Set or return the state of a particular hardware component. AHI uses
*       AHIsub_GetAttr() to supply the user with limits and what tags are
*       available.
*
*   INPUTS
*       attribute - Is really a Tag and can be one of the following:
*           AHIC_MonitorVolume - Set the input monitor volume to argument.
*           AHIC_MonitorVolume_Query - Return the current input monitor
*               volume (argument is ignored).
*
*           AHIC_InputGain - Set the input gain to argument. (V2)
*           AHIC_InputGain_Query (V2)
*
*           AHIC_OutputVolume - Set the output volume to argument. (V2)
*           AHIC_OutputVolume_Query (V2)
*
*           AHIC_Input - Use the argument:th input source (default is 0). (V2)
*           AHIC_Input_Query (V2)
*
*           AHIC_Output - Use the argument:th output destination (default
*               is 0). (V2)
*           AHIC_Output_Query (V2)
*
*       argument - What value attribute should be set to.
*       audioctrl - Pointer to an AHIAudioCtrlDrv structure.
*
*   RESULT
*       Return the state of selected attribute. If you were asked to set
*       something, return TRUE. If attribute is unknown to you or unsupported,
*       return FALSE.
*
*   NOTES
*       This call must be safe from interrupts.
*
*   SEE ALSO
*       ahi.device/AHI_ControlAudioA(), AHIsub_GetAttr()
*
*****************************************************************************
*
*

AHIsub_HardwareControl:
	cmp.l	#AHIC_MonitorVolume,d0
	bne.b	.dontsetmonvol
	move.l	ahiac_DriverData(a2),a1
	lsr.l	#8,d1
	lsr.l	#2,d1
	move.w	d1,p_MonitorVolume(a1)
	bra.b	.exit
.dontsetmonvol
	cmp.l	#AHIC_MonitorVolume_Query,d0
	bne.b	.dontgetmonvol
	move.l	ahiac_DriverData(a2),a1
	moveq	#0,d0
	move.w	p_MonitorVolume(a1),d0
	lsl.l	#8,d0
	lsl.l	#2,d0
	bra.b	.quit
.dontgetmonvol
	moveq	#FALSE,d0
.quit
	rts
.exit
	moveq	#TRUE,d0
	rts







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

Interrupt_Dummy:
	move.w	#INTF_AUD0!INTF_AUD1!INTF_AUD2!INTF_AUD3,INTREQ(a0)
SoftInt_Dummy:
	rts

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

* This function will be executed up to 28000 times per second - that's once per
* rasterline! It has to be as fast as possible.

	move.w	#INTF_AUD2!INTF_AUD3,INTREQ(a0)	;Clear the interrupt flags
	moveq	#0,d0
	move.b	_ciaa+ciaprb,d0			;read parallel port
 IFD	MC020
	move.l	convtable(pc,d0.w*4),d0		;1 unsigned byte -> 2 signed words
 ELSE
	add.w	d0,d0
	add.w	d0,d0
	move.l	convtable(pc,d0.w),d0
 ENDC
	move.w	d0,AUD2DAT(a0)			;right
	move.w	d0,AUD3DAT(a0)			;left

	move.l	(a1),a5				;p_RecFillPtr
	move.l	d0,(a5)+			;store sample in buffer
	move.l	a5,(a1)+			;update pointer
	subq.w	#1,(a1)				;p_ReqFillCount
	beq	ri_Filled			;branch if buffer filled
	rts

convtable
CNT	SET	0
	REPT	256
	dc.b	CNT-128,CNT-128,CNT-128,CNT-128
CNT	SET	CNT+1
	ENDR

;in:
* d0	scratch
* d1	INTENAR && INTREQR
* a0	CUSTOM
* a1	&(paulaBase->p_RecIntDataAura)
* a5	&RecordInterrupt
* a6	ExecBase
RecordInterruptAura:
	move.w	#INTF_AUD2!INTF_AUD3,INTREQ(a0)	;Clear the interrupt flags
	move.l	(a1)+,a5
	move.l	(a5),d0				;read aura sampler
	eor.l	#$80008000,d0
	move.l	(a1),a5				;p_RecFillPtr
	move.l	d0,(a5)+			;store sample in buffer
	move.l	a5,(a1)+			;update pointer

	move.w	d0,d1
	lsr.w	#8,d0
	move.b	d0,d1

	move.w	d1,AUD2DAT(a0)			;right
	move.w	d1,AUD3DAT(a0)			;left

	subq.w	#1,(a1)				;p_ReqFillCount
	beq	ri_Filled			;branch if buffer filled
	rts

ri_Filled:

* This part is only executed every RECORDSAMPLES:th time... No need to hurry.

	move.l	8(a1),d0			;p_RecBuffer2->
	move.l	4(a1),8(a1)			;p_RecBuffer1->p_RecBuffer2
	move.l	d0,4(a1)			;            ->p_RecBuffer1
	move.l	d0,-4(a1)			;p_RecFillPtr
	move.w	#RECORDSAMPLES,(a1)		;p_ReqFillCount
	move.l	12(a1),a1			;p_RecSoftIntPtr
	jmp	_LVOCause(a6)


;in:
* d0	scratch
* d1	scratch
* a0	scratch
* a1	paulaBase
* a5	scratch
RecordSoftInt:

* This function is not executed many times per second and is therefore not
* fully optimized... ;)
	push	a2
	move.w	p_MonitorVolume(a1),CUSTOM+AUD2VOL
	move.w	p_MonitorVolume(a1),CUSTOM+AUD3VOL
	move.l	p_RecBuffer2(a1),p_rmBuffer(a1)
	move.l	p_AudioCtrlP(a1),a2
	lea	p_RecordMessage(a1),a1
	move.l	ahiac_SamplerFunc(a2),a0
	move.l	h_Entry(a0),a5
	jsr	(a5)
	pop	a2
	rts

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

AudioInterrupt2:				;Two hardware channels used
	movem.l	(a1)+,d0/d1			;p_PlaySoftIntPtr,p_AudLen/p_AudPer
	move.l	d1,AUD0LEN(a0)
	move.l	d1,AUD1LEN(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	d0,a1				;p_PlaySoftInt
	move.w	#INTF_AUD0,INTREQ(a0)		;Clear the interrupt 
	jmp	_LVOCause(a6)			;start PlaySoftInt

AudioInterrupt4:				;Four hardware channels used
	movem.l	(a1)+,d0/d1			;p_PlaySoftIntPtr,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_PlaySoftInt
	move.w	#INTF_AUD0,INTREQ(a0)		;Clear the interrupt 
	jmp	_LVOCause(a6)			;start PlaySoftInt

;in:
* d0	scratch
* d1	scratch
* a0	scratch
* a1	&(paulaBase->p_PlaySoftIntData)
* 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_PlaySoftIntData)
* 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_PlaySoftIntData)
* 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
	lsr.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_PlaySoftIntData)
* 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
 IFD	MC020
	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_PlaySoftIntData)
* 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
	lsr.b	#2,d1
	move.b	d1,(a5)+
;right
	move.b	(a1)+,(a2)+
	move.b	(a1)+,d1
	lsr.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_PlaySoftIntData)
* 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
 IFD	MC020
	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
 IFD	MC020
	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:
