*** 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	4
REVISION	EQU	2
DATE	MACRO	
		dc.b	"17.4.97"
	ENDM
VERS	MACRO
		dc.b	"paula 4.2"
	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	lvo/ahi_sub_lib.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

 * 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_Parallel			;TRUE if pport allocated
	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_Input

*** 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
	FPTR	p_PreTimer
	FPTR	p_PostTimer
	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
PB_STEREO	EQU	AHIACB_STEREO		;=2
PF_STEREO	EQU	AHIACF_STEREO

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
envVar:		dc.b	"AHIpaulaFilterFreq",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 enhancements, 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 available.
*
*       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 everything 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 should then
*       apply the Fx on the wet buffer, and post-mix the two buffers before
*       you send the samples to the DAC. (V4)
*
*   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
*       always 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	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)

* 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

	move.l	pb_DosLib(a5),a6
	moveq	#1,d1
	call	Delay

* test if mode supports recording
	btst	#PB_14BIT,p_Flags(a3)
	bne	.dontgetsampler			;no record if 14 bit mode

* try to allocate parallel port
	clr.b	p_Parallel(a3)
	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	.no_parrallel
	moveq	#MR_PARALLELPORT,d0
	lea	IDString(pc),a1
	jsr	MR_ALLOCMISCRESOURCE(a6)
	move.l	d0,p_ParPortUser(a3)
	bne	.no_parrallel

	move.b	#TRUE,p_Parallel(a3)
	move.b	#0,_ciaa+ciaddrb			;make PB0-PB7 inputs
.no_parrallel

* allocate Aura sampler
	clr.l	p_AuraAddress(a3)
	move.l	pb_CardResource(a5),d0
	beq	.no_aura
	move.l	d0,a6
	call	GetCardMap
	tst.l	d0
	beq	.no_aura
	move.l	d0,a0
	move.l	cmm_IOMemory(a0),d2
	beq	.no_aura

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

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

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

	move.l	d2,p_AuraAddress(a3)
.no_aura
.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)
	clr.l	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

* Check the AHIpaulaFilterFreq variable.
* If the mixing frequency is higher than this one, set disable the filter,
* else enable it.
	moveq	#0,d5				;Default freq
	move.l	pb_DosLib(a5),a6
	subq.l	#8,sp				;local label
	move.w	#("0"<<8)|0,(sp)		;Initialize as "0"
	lea	envVar(pc),a0
	move.l	a0,d1
	move.l	sp,d2
	moveq.l	#8,d3
	moveq.l	#0,d4
	call	GetVar
	cmp.l	#-1,d0
	beq	.gotfreq
	move.l	sp,d1
	pea.l	0.w
	move.l	sp,d2
	call	StrToLong
	move.l	(sp)+,d5
.gotfreq
	addq.l	#8,sp
; d5 is now the freq

	cmp.l	ahiac_MixFreq(a2),d5
	bls	.no_filter
	bclr	#1,$bfe001			;turn audio filter on
	bra	.filter_set
.no_filter
	bset	#1,$bfe001			;turn audio filter off
.filter_set

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

.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. The buffer must
*          be long aligned!
*       B) Create/start an interrupt or task that will do 1-6 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 ahiac_PreTimer function. If it returns TRUE (Z will be
*          cleared so you don't have to test d0), skip step 3 and 4. This
*          is used to avoid overloading the CPU. This step is optional.
*          A2 is assumed to point to struct AHIAudioCtrlDrv. All registers
*          except d0 are preserved.  (V4)]
*
*       3) 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 always 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 interleaved:
*           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 available 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.
*
*       4) 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.
*
*       5) [Call the ahiac_PostTimer function. A2 is assumed to point to
*          struct AHIAudioCtrlDrv. All registers are preserved.  (V4)]
*
*       6) 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, immediately 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. (V4)
*
*     - 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 buffer must
*       be long aligned, and it's size must be evenly divisible by four.
*       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_14BIT,p_Flags(a3)		;Sanity check...
	bne	.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.w	p_Input(a3),d0
	beq	.parsampler
	cmp.w	#1,d0
	beq	.aurasampler
	bra	.error_unknown
.parsampler
	tst.b	p_Parallel(a3)			;Parrallel port allocated?
	beq	.error_unknown
	lea	p_RecIntData(a3),a1
	move.l	a1,IS_DATA+p_RecInt(a3)
	move.l	#RecordInterrupt,IS_CODE+p_RecInt(a3)
	bra	.setrecint
.aurasampler
	tst.l	p_AuraAddress(a3)		;Aura sampler allocated?
	beq	.error_unknown
	lea	p_RecIntDataAura(a3),a1
	move.l	a1,IS_DATA+p_RecInt(a3)
	move.l	#RecordInterruptAura,IS_CODE+p_RecInt(a3)
.setrecint
	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)

	move.l	ahiac_PreTimer(a2),p_PreTimer(a3)
	move.l	ahiac_PostTimer(a2),p_PostTimer(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_14BIT,p_Flags(a3)		;Sanity check...
	bne	.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 function 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 recording.
*
*           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	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_Paula14Bit,d0
	moveq	#FALSE,d1
	move.l	a3,a0
	call	GetTagData
	tst.l	d0
	bne	.2

* Only for AHIDB_Paula14Bit FALSE (== recording available)
	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	#2,d0
	bra	.exit
.not_inputs
	cmp.l	#AHIDB_Input,d0
	bne.b	.not_input
	lsl.l	#2,d1
	move.l	.inputs(pc,d1.w),d0
	bra	.exit
.not_input
	bra	.3
.inputs:
	dc.l	.input0
	dc.l	.input1

.2
* Only for AHIDB_Paula14Bit TRUE (== no recording)
	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_14bit)

	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	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
.input0		dc.b	"Parallel port sampler",0
.input1		dc.b	"Aura sampler",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
	cmp.l	#AHIC_Input,d0
	bne.b	.dontsetinput
	move.l	ahiac_DriverData(a2),a1
	move.w	d1,p_Input(a1)
	bra.b	.exit
.dontsetinput
	cmp.l	#AHIC_Input_Query,d0
	bne.b	.dontgetinput
	move.l	ahiac_DriverData(a2),a1
	moveq	#0,d0
	move.w	p_Input(a1),d0
	bra.b	.quit
.dontgetinput
	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/a4/a6
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3/a4/a6
	jsr	(a3)				;call Player Hook
	jsr	(a4)				;call PreTimer
	bne	.exit
	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
.exit
	jsr	(a6)				;call PostTimer, a2 ok
	popm	a2/a3/a4/a6
	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/a4/a6
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3/a4/a6
	jsr	(a3)				;call Player Hook
	jsr	(a4)				;call PreTimer
	bne	.exit
	movem.l	(a5),d0/a0/a1/a2/a3/a5
	jsr	(a3)				;call Mixer Hook

* convert and transfer buffer
	movem.l	(a5),a0/a3
.loop
 REPT	2
;left
	move.b	(a1),(a3)+
;right
	move.b	2(a1),(a0)+
	addq.l	#4,a1
 ENDR
	dbf	d0,.loop
.exit
	jsr	(a6)				;call PostTimer, a2 ok
	popm	a2/a3/a4/a6
	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/a4/a6
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3/a4/a6
	jsr	(a3)				;call Player Hook
	jsr	(a4)				;call PreTimer
	bne	.exit
	movem.l	(a5),d0/a0/a1/a2/a3/a5
	jsr	(a3)				;call Mixer Hook

* convert and transfer buffer
	movem.l	(a5),a0/a3/a4/a5		;a3/a4 unused
.loop
 REPT	2
	move.b	(a1)+,(a0)+
	move.b	(a1)+,d1
	lsr.b	#2,d1
	move.b	d1,(a5)+
 ENDR
	dbf	d0,.loop
.exit
	jsr	(a6)				;call PostTimer, a2 ok
	popm	a2/a3/a4/a6
	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/a6
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3/a4/a6
	jsr	(a3)				;call Player Hook
	jsr	(a4)				;call PreTimer
	bne	.exit
	movem.l	(a5),d0/a0/a1/a2/a3/a4/a5
	jsr	(a3)				;call Mixer Hook

* convert and transfer buffer, using the table
	move.l	(a4),a0
	move.l	12(a4),a4
	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
.exit
	jsr	(a6)				;call PostTimer, a2 ok
	popm	a2/a3/a4/a6
	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/a4/a6
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3/a4/a6
	jsr	(a3)				;call Player Hook
	jsr	(a4)				;call PreTimer
	bne	.exit
	movem.l	(a5),d0/a0/a1/a2/a3/a5
	jsr	(a3)				;call Mixer Hook
	push	a2
* convert and transfer buffer
* TODO: Use a table!
	movem.l	(a5),a0/a2/a3/a5
.loop
 REPT	2
;left
	move.b	(a1)+,(a2)+
	move.b	(a1)+,d1
	lsr.b	#2,d1
	move.b	d1,(a3)+
;right
	move.b	(a1)+,(a0)+
	move.b	(a1)+,d1
	lsr.b	#2,d1
	move.b	d1,(a5)+
 ENDR
	dbf	d0,.loop
	pop	a2
.exit
	jsr	(a6)				;call PostTimer, a2 ok
	popm	a2/a3/a4/a6
	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/a6
	move.l	a1,a5
	movem.l	(a5)+,a0/a1/a2/a3/a4/a6
	jsr	(a3)				;call Player Hook
	jsr	(a4)				;call PreTimer
	bne	.exit
	movem.l	(a5),d0/a0/a1/a2/a3/a4/a5
	jsr	(a3)				;call Mixer Hook
	push	a2
* 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,(a3)+			;low byte
 	lsr.w	#8,d1
 	move.b	d1,(a2)+			;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,(a4)+			;low byte
 	lsr.w	#8,d1
 	move.b	d1,(a0)+			;high byte
 ENDR

	dbf	d0,.loop
	pop	a2
.exit
	jsr	(a6)				;call PostTimer
	popm	a2/a3/a4/a6
	move.w	#DMAF_SETCLR|DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3,CUSTOM+DMACON
	rts
EndCode:
