; ---------------------------------------------------------------------------
;	BMOD Module AHI Player by Tuomas Lukinmaa (Tumu/BFB Team)
;	Copyright © 1996-1997 Tuomas Lukinmaa
;	All rights reserved.
;
;	This production is SHAREWARE.
;	Use of this production in freeware productions is allowed without
;	a registration.
;	If you want to use this in your shareware or commercial productions,
;	or in commercial competitions, you will need to register BMOD.
;
;	If this archive does not contain BMOD.guide and files mentioned
;	there, you should delete this source and all related files,
;	and get a full archive from Aminet.
;
;	$VER: bmod.s 0.35ß (8.5.97) Copyright © 1996-1997 Tuomas Lukinmaa. All rights reserved.
;
;	-- Code --
;	Initializing		: Tumu
;	Player			: Tumu (& some fixes by Quaid)
;
;	Thanks to:
;			Jarkko Vatjus-Anttila
;			K-P Koljonen
;			All ppl I know personally in #Ksylitol.
;
;	-- Notes --
;
;	1.	Do NOT call the INTERNAL routines by yourself,
;		because those are only meant to be used by main
;		routines like InitPlayer and EndPlayer and
;		I might change those internal routines register
;		usage at anytimes.
;
;	2.	Use decent compiler to harness this beast.
;		Lousy compilers (like old versions of ASMone) will
;		screw up this source and your machine too.
;		If you want to compile this properly, use Samu Nuojua's
;		Macro Assembler (SNMA) found in Aminet.
;		
; ---------------------------------------------------------------------------

*****************************************************************************
CheckModule	equ	1	* Check that module is BMOD.
				* Think twice before setting this to NIL,
				* loading non-BMOD module might crash
				* your machine. (Default 1)
				* Disabling this disables version check also.
AcceptVersion	equ	0	* DONOTCHANGE!^n+1 , n>70*10^69
AcceptRevision	equ	3	* DONOTCHANGE!^n+1 , n>70*10^69
*****************************************************************************
UnpackDelta	equ	1	* Check&Unpack deltaed samples.
*****************************************************************************
InitialCIATempo	equ	125	* Initial CIA Tempo. 32-255 (Default 125)
InitialSpeed	equ	6	* Initial Speed. 1-31 (Default 6)
*DEBUG SECTION***************************************************************
Rastertiming	equ	0	* Enable this if you want to see approx.
				* processor time consuption. (Default 0)
DisableX	equ	0	* If set, disables eXtended commands.
Opti030		equ	0	* Include 030 optimizations. 020+ req.
NoFineTunes	equ	0	* Not yet supported, so hands off!
PreCalcRates	equ	1	* Use precalculated sample rates.
*****************************************************************************

	IFEQ	Opti030
	CPU	68000
	ELSE
	CPU	68020
	ENDC

	IFLE	InitialCIATempo-32
	FAIL	InitialCIATempo >= 32
	ENDC
	IFLE	InitialSpeed
	FAIL	InitialSpeed <= 0
	ENDC

	; Here comes the code. If you find a bug(s), please (e)mail to me.

	include	'dos/dos.i'
	include 'libraries/execxref.i'
	include 'exec/memory.i'
	include 'exec/types.i'
	include 'exec/execbase.i'
	include 'exec/io.i'
	include 'utility/hooks.i'
	include	'devices/ahi.i'
	include 'lvo/ahi_lib.i'
	include	'bmod.i'

ver	MACRO
	dc.b	'0.35ß (8.5.97)'
	ENDM

	section	"BMOD AHI Replayer",code

Start	bra.w	Init
	CNOP	0,4
End	bra.w	EndPlayer
	CNOP	0,4

Init	bsr	InitPlayer
	tst.l	d0
	bne	.Exit
	moveq	#TRUE,d0
	bsr	PlayControl
.Exit	rts

; ---------------------------------------------------------------------------
;	InitPlayer -- Initialize BMOD for playing module.
;
;	SYNOPSIS
;		error = InitPlayer(ModuleStructure)
;		D0.l               A0
;
;	FUNCTION
;		Initialises BMOD for playing module, and allocates
;		all needed resources. When all resources are allocated,
;		it starts the module to play.
;
;	INPUTS
;		Allocated ModuleStructure for initializing values
;		in bmod_PlayerStructure.
;
;	RESULT
;		error - NULL if all ok, else an error code mentioned in
;			bmod.i.
;
;		All necessary allocation and initializing have been done.
; ---------------------------------------------------------------------------
InitPlayer
	push	d2/a2/a5/a6
	move.l	a0,d0
	beq	ip_OwnError
	move.l	d0,a2

	move.l	$4.w,a6
	move.w	LIB_VERSION(a6),d0
	cmp.w	#37,d0
	blo	ip_OwnError

	moveq	#bmod_PlayerStructure_SIZE,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec			; allocate PlayerStructure
	tst.l	d0
	beq	ip_OwnError
	move.l	d0,a5
	move.l	a6,ps_Execbase(a5)
	lea	PStruc(pc),a0
	move.l	a5,(a0)

	move.l	ms_ModuleInfo(a2),ps_ModuleInfo(a5)
	move.l	ms_SampleInfo(a2),ps_SampleInfo(a5)
	move.l	ms_PatternInfo(a2),ps_PatternInfo(a5)
	move.l	ms_PatternArray(a2),ps_PatternArray(a5)
	move.l	ms_SampleArray(a2),ps_SampleArray(a5)

	IFNE	CheckModule
	move.l	ps_ModuleInfo(a5),a0
	cmpi.l	#"BMOD",(a0)			; is module even BMOD?
	bne	ip_OwnError

	move.w	mi_Version(a0),d0
	beq	ip_OwnError
	cmp.b	#AcceptRevision,d0
	bne	ip_OwnError
	lsr.w	#8,d0
	cmp.b	#AcceptVersion,d0
	bne	ip_OwnError
	ENDC

	moveq	#0,d0
	move.w	mi_Patterns(a0),d0
	lsl.l	#2,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,ps_PatternPointers(a5)
	beq	ip_OwnError

	bsr	GetPatternLengths

	IFNE	PreCalcRates
	move.l	#36*2*16,d0
	moveq	#MEMF_PUBLIC,d1
	call	AllocVec
	move.l	d0,ps_SampleRates(a5)
	beq	ip_OwnError

	bsr	MakeSampleRates
	ENDC

	move.l	#bmod_ChannelTemp_SIZE*4,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	tst.l	d0
	beq	ip_OwnError
ip_Set_CT
	lea	ps_Channel1Temp(a5),a0
	moveq	#4-1,d1
	moveq	#bmod_ChannelTemp_SIZE,d2
.sctloop
	move.l	d0,(a0)+
	add.l	d2,d0
	dbra	d1,.sctloop

ip_Init_CT
	lea	ps_Channel1Temp(a5),a0
	moveq	#1,d0
	moveq	#0,d1
.ictloop
	move.l	(a0)+,a1
	move.b	d0,ct_ChannelBit(a1)
	move.b	#1,ct_GlissControl(a1)
	lsl.b	#1,d0
	addq.b	#1,d1
	cmpi.b	#4,d1
	bne	.ictloop

	bsr	AllocateAudio
	tst.l	d0
	bne	ip_CleanUp

	bsr	InitializeAudio
	tst.l	d0
	bne	ip_CleanUp

	move.b	#InitialSpeed,ps_Speed(a5)
	move.w	#InitialCIATempo,ps_CIATempo(a5)
	move.b	#1,ps_PlayCounter(a5)
	clr.b	ps_Row(a5)

	move.w	#TRUE,ps_UpdatePtrs(a5)

	moveq	#IP_OK,d0

ip_Exit	pull	d2/a2/a5/a6
	rts

ip_OwnError
	moveq	#IP_IP,d0

ip_CleanUp
	bsr	EndPlayer
	bra	ip_Exit

; ---------------------------------------------------------------------------
;	EndPlayer -- Stops player and frees all resources used by player.
;
;	SYNOPSIS
;		EndPlayer()
;
;	FUNCTION
;		Stops playing module and frees reserved AHI resources and
;		other resources.
;
;	RESULT
;		All used resources have been freed.
;		This routine does not free the module or ModuleStructure.
; ---------------------------------------------------------------------------
EndPlayer
	push	all
	move.l	PStruc(pc),d0
	beq	ep_NoPlayerStructure
	move.l	d0,a5

	bsr	DeallocateAudio

ep_NoAudio
	move.l	ps_Execbase(a5),a6

	move.l	ps_Channel1Temp(a5),d0
	beq	ep_NotAllocatedCT
	move.l	d0,a1
	call	FreeVec
ep_NotAllocatedCT

	move.l	ps_PatternPointers(a5),d0
	beq	ep_NoPatternPointers

	move.l	d0,a1
	call	FreeVec
ep_NoPatternPointers

	IFNE	PreCalcRates
	move.l	ps_SampleRates(a5),d0
	beq	ep_NoSampleRates

	move.l	d0,a1
	call	FreeVec
ep_NoSampleRates
	ENDC

	move.l	a5,a1
	call	FreeVec
	lea	PStruc(pc),a0
	clr.l	(a0)

ep_NoPlayerStructure
	pull	all
	rts

; ---------------------------------------------------------------------------
;	PlayControl -- Control BMOD player.
;
;	SYNOPSIS
;		error = PlayControl(Play)
;		D0.l                D0
;
;	FUNCTION
;		Use this function to activate/deactivate player.
;		
;	INPUTS
;		Play - TRUE to activate player, FALSE to stop player.
;
;	RESULT
;		error - NULL if all ok, else -1.
; ---------------------------------------------------------------------------
PlayControl
	push	d1-d2/a0-a6
	move.l	PStruc(pc),d1
	beq	pc_Error
	move.l	d1,a5

	move.l	ps_AHIbase(a5),d1
	beq	pc_Error
	move.l	d1,a6

	move.l	ps_AudioCtrl(a5),d1
	beq	pc_Error
	move.l	d1,a2
	
	move.l	#TAG_DONE,-(sp)
	move.l	d0,-(sp)
	move.l	#AHIC_Play,-(sp)
	move.l	sp,a1
	call	AHI_ControlAudioA
	lea	3*4(sp),sp

	moveq	#0,d0
pc_Exit	pull	d1-d2/a0-a6
	rts

pc_Error
	moveq	#-1,d0
	bra	pc_Exit


; ---------------------------------------------------------------------------
;	InitializeAudio (INTERNAL)
;
;	Initialises samples for AHI.
;	Preserves all registers.
;
;	Input:	A5 = PlayerStructure
;	Output:	D0 = 0 if all ok, else -3.
;
;	Result:	Samples are initialized for AHI.
; ---------------------------------------------------------------------------
InitializeAudio
	push	d1-d4/a0-a6

	; depack samples

	move.l	ps_SampleArray(a5),a2
	IFNE	UnpackDelta
	move.l	sa_Length(a2),d0
	ENDC
	lea	sa_Array(a2),a2

	IFNE	UnpackDelta
	move.l	ps_ModuleInfo(a5),a0
	bclr.b	#INFB_DELTA,mi_Info(a0)
	beq	ia_NoDelta
	bsr	unpack
ia_NoDelta
	ENDC

	move.l	a2,a3

	move.l	ps_Execbase(a5),a6
	moveq	#AHISampleInfo_SIZEOF,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,d4
	beq	ia_CleanUp

	move.l	ps_SampleInfo(a5),a4
	moveq	#0,d2
	move.b	si_NumberOfSampleInfos(a4),d2
	lea	si_SampleHeader(a4),a4

	move.l	ps_AHIbase(a5),a6
	move.l	ps_AudioCtrl(a5),a2
	moveq	#0,d3
ia_Loop
	moveq	#0,d1
	move.w	sh_Length(a4),d1
	beq	.NoSample

	move.l	d4,a0
	move.l	#AHIST_M8S,ahisi_Type(a0)
	move.l	a3,ahisi_Address(a0)
	add.l	d1,d1
	move.l	d1,ahisi_Length(a0)
	add.l	d1,a3
	moveq	#AHIST_SAMPLE,d1
	move.l	d3,d0
	call	AHI_LoadSound
	tst.l	d0
	bne	ia_CleanUp

.NoSample
	addq.w	#1,d3
	add.l	#bmod_SampleHeader_SIZE,a4
	dbra	d2,ia_Loop

	moveq	#0,d0
ia_Exit
	push	d0
	tst.l	d4
	beq	.NoAHISIMem
	move.l	ps_Execbase(a5),a6
	move.l	d4,a1
	call	FreeVec
.NoAHISIMem
	pull	d0

	pull	d1-d4/a0-a6
	rts

ia_CleanUp
	moveq	#IP_IA,d0
	bra	ia_Exit	

	; unpack
	; a2 - start of data
	; d0 - length of data

	IFNE	UnpackDelta
unpack	push	d1-d2/a2
	moveq	#0,d1 	; d1, old

unloop	move.b	(a2),d2
	add.b	d1,d2
	move.b	d2,d1
	move.b	d2,(a2)+

	subq.l	#1,d0
	bne.b	unloop
	pull	d1-d2/a2
	rts
	ENDC

; ---------------------------------------------------------------------------
;	GetPatternLengths (INTERNAL)
;
;	Pre-calculates all pattern pointers.
;	Preserves all registers.
;
;	Input:	D0 = ps_PatternPointers
;		A5 = PlayerStructure
;
;	Result: ps_PatternPointers table is filled with pointers.
; ---------------------------------------------------------------------------
GetPatternLengths
	push	d0-d2/a0-a2
	move.l	d0,a1

	move.l	ps_ModuleInfo(a5),a0
	moveq	#0,d1
	move.w	mi_Positions(a0),d1
	move.w	mi_Patterns(a0),d2
	subq.w	#1,d2				; because of DBRA

	cmp.w	#256,d1				; if positions>256
	bls	.UseBytes			; table contains words
	add.w	d1,d1
.UseBytes

	moveq	#pi_Array,d0
	add.l	ps_PatternInfo(a5),d0
	add.l	d1,d0

	moveq	#-2,d1
	addq.l	#1,d0
	and.l	d1,d0
	move.l	d0,a0

	move.l	ps_PatternArray(a5),a2
	moveq	#pa_Array,d0
	add.l	d0,a2

	moveq	#0,d0
.loop2
	move.w	(a0)+,d0
	move.l	a2,(a1)+
	add.l	d0,a2
	dbra	d2,.loop2

	pull	d0-d2/a0-a2
	rts

	IFNE	PreCalcRates
MakeSampleRates
	push	d0-d2/a0-a2
	move.l	d0,a1

	lea	is_PeriodTable(pc),a0
	move.l	#36*16-1,d2
.Loop
	move.w	(a0)+,d0
	move.l	#PALSpeed,d1
	divu.w	d0,d1
	move.w	d1,(a1)+
	dbra	d2,.Loop

	pull	d0-d2/a0-a2
	rts
	ENDC

; ---------------------------------------------------------------------------
;	AllocateAudio (INTERNAL)
;
;	Allocates ahi.device and audio hardware.
;	Preserves all registers.
;
;	Input:	A2 = bmod_ModuleStructure
;		A5 = PlayerStructure
;	Output:	D0 = 0 if all went ok, else -2.
;
;	Result:	ahi.device, MsgPort and IORequest have been allocated.
; ---------------------------------------------------------------------------
AllocateAudio
	push	d1/a0-a2/a6
	move.l	ps_Execbase(a5),a6

	call	CreateMsgPort
	move.l	d0,ps_MsgPort(a5)
	beq	aa_CleanUp

	move.l	d0,a0
	moveq	#AHIRequest_SIZEOF,d0
	call	CreateIORequest
	move.l	d0,ps_IORequest(a5)
	beq	aa_CleanUp

	move.l	d0,a1
	move.w	#4,ahir_Version(a1)		; requested version
	move.l	#AHI_NO_UNIT,d0			; unit
	moveq	#0,d1				; flags

	lea	AHIname(pc),a0
	call	OpenDevice
	tst.b	d0
	bne	aa_CleanUp

	move.l	ps_IORequest(a5),a0
	move.l	IO_DEVICE(a0),ps_AHIbase(a5)

	moveq	#h_SIZEOF*2,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,ps_AHIhooks(a5)
	beq	aa_CleanUp

	move.l	d0,a0
	lea	SoftIntServer(pc),a1
	move.l	a1,h_Entry(a0)
	move.l	a5,h_Data(a0)
	add.l	#h_SIZEOF,a0
	lea	is_SetLoops(pc),a1
	move.l	a1,h_Entry(a0)
	move.l	a5,h_Data(a0)

	moveq	#10*8,d0
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	call	AllocVec
	move.l	d0,ps_AHITags(a5)
	beq	aa_CleanUp

	move.l	d0,a1
	move.l	ps_SampleInfo(a5),a0
	moveq	#0,d0
	move.b	si_NumberOfSampleInfos(a0),d0
	addq.b	#1,d0
	lea	ahi_Tags(pc),a0
	move.l	(a0)+,(a1)+		; audioid
	move.l	#AHI_DEFAULT_ID,(a1)+
;	move.l	ms_AHIA_AudioID(a2),(a1)+
	move.l	(a0)+,(a1)+		; mixfreq
;	move.l	ms_AHIA_MixFreq(a2),(a1)+
	move.l	#AHI_DEFAULT_FREQ,(a1)+
	move.l	(a0)+,(a1)+		; channels
	move.l	#4,(a1)+
	move.l	(a0)+,(a1)+		; sounds
	move.l	d0,(a1)+
	move.l	(a0)+,(a1)+		; soundfunc
	move.l	ps_AHIhooks(a5),d0
	add.l	#h_SIZEOF,d0
	move.l	d0,(a1)+
	move.l	(a0)+,(a1)+		; playerfunc
	move.l	ps_AHIhooks(a5),d0
	move.l	d0,(a1)+
	move.l	(a0)+,(a1)+
	move.l	#(InitialCIATempo*2/5)<<16,(a1)+	; playerfreq
	move.l	(a0)+,(a1)+
	move.l	#(32*2/5)<<16,(a1)+	; minplayerfreq
	move.l	(a0)+,(a1)+
	move.l	#(255*2/5)<<16,(a1)+	; maxplayerfreq
	move.l	(a0)+,(a1)+		; TAG_DONE

	move.l	ps_AHITags(a5),a1
	move.l	ps_AHIbase(a5),a6
	call	AHI_AllocAudioA
	move.l	d0,ps_AudioCtrl(a5)
	beq	aa_CleanUp

	move.l	ps_AHITags(a5),a1
	move.l	ps_Execbase(a5),a6
	call	FreeVec
	clr.l	ps_AHITags(a5)

	moveq	#0,d0

aa_Exit	pull	d1/a0-a2/a6
	rts

aa_CleanUp
	moveq	#IP_AA,d0
	bra	aa_Exit


; ---------------------------------------------------------------------------
;	DeallocateAudio (INTERNAL)
;
;	Deallocates ahi.device, MsgPort, IOReq
;	Preserves all registers.
;
;	Input:	A5 = PlayerStructure
;
;	Result:	ahi.device, MsgPort and IORequest have been freed.
; ---------------------------------------------------------------------------
DeallocateAudio
	push	d0-d4/a0-a2/a6

	move.l	ps_Execbase(a5),a6

	move.l	ps_AHIbase(a5),d0
	beq	da_NoAHIOpened
	move.l	d0,a6

	move.l	ps_AudioCtrl(a5),d0
	beq	da_NoAudioCtrl
	move.l	d0,a2

	moveq	#FALSE,d0
	bsr	PlayControl

	moveq	#0,d2
	moveq	#0,d3
	moveq	#AHISF_IMM,d4
	moveq	#0,d0
	moveq	#AHI_NOSOUND,d1
	call	AHI_SetSound
	moveq	#1,d0
	moveq	#AHI_NOSOUND,d1
	call	AHI_SetSound
	moveq	#2,d0
	moveq	#AHI_NOSOUND,d1
	call	AHI_SetSound
	moveq	#3,d0
	moveq	#AHI_NOSOUND,d1
	call	AHI_SetSound

	call	AHI_FreeAudio
da_NoAudioCtrl

	move.l	ps_Execbase(a5),a6

	move.l	ps_AHITags(a5),d0
	beq	da_NoAHITags
	move.l	d0,a1
	call	FreeVec
da_NoAHITags

	move.l	ps_AHIhooks(a5),d0
	beq	da_NoAHIhooks
	move.l	d0,a1
	call	FreeVec
da_NoAHIhooks

	move.l	ps_IORequest(a5),a1
	call	CloseDevice
da_NoAHIOpened

	move.l	ps_IORequest(a5),d0
	beq	da_NoIOStructure

	move.l	d0,a0
	call	DeleteIORequest
da_NoIOStructure

	move.l	ps_MsgPort(a5),d0
	beq	da_NoMsgPort

	move.l	d0,a0
	call	DeleteMsgPort
da_NoMsgPort

	pull	d0-d4/a0-a2/a6
	rts

ahi_Tags	dc.l	AHIA_AudioID,AHIA_MixFreq,AHIA_Channels,AHIA_Sounds
		dc.l	AHIA_SoundFunc,AHIA_PlayerFunc,AHIA_PlayerFreq
		dc.l	AHIA_MinPlayerFreq,AHIA_MaxPlayerFreq,TAG_DONE
PStruc		dc.l	0
AHIname		dc.b	'ahi.device',0
		dc.b	'$VER: BMOD AHI Player '
		ver
		dc.b	' Copyright © 1996-1997 Tuomas Lukinmaa. '
		dc.b	'All rights reserved.',$a,0

; ---------------------------------------------------------------------------
;	SoftIntServer (INTERNAL)
;
;	The Real Thing (tm). Also the main player. =)
;
;	Input:	A0 = HookStructure
;		A2 = AHIAudioCtrl
;
;	Register usage:
;		A0 = scratch
;		A1 = scratch
;		A2 = AHIAudioCtrl
;		A3 = ps_ChannelXTemp
;		A4 = reserved for temporary scratch pointers
;		A5 = bmod_PlayerStructure
;		A6 = AHIbase
;
;		Data registers are mostly scratch regs, but I have tried
;		to use only D0-D3, because if I need to expand something,
;		it will be easy.
;		D0 = always scratch
;		D1 = scratch or ct_EffectValue (in is_Do(X)Effects)
;		D2 = always scratch
;		D3-D7 = routine dependant (do not depend on these)
;
;	Result:	Plays the BMOD. (if ps_PlayEnable <> 0)
; ---------------------------------------------------------------------------
	CNOP	0,4			; align to 32-bit boundary
SoftIntServer			; a1 = bmod_PlayerStructure, a6 = Execbase
	push	d2-d7/a2-a6		; preserve some
	move.l	h_Data(a0),a5
	move.l	ps_AHIbase(a5),a6

	IFNE	Rastertiming
	move.w	#$f00,$dff180
	ENDC

	subq.b	#1,ps_PlayCounter(a5)
	bne	is_Update_Efx

	bsr	is_ProcessNewData
	move.b	ps_Speed(a5),ps_PlayCounter(a5)

is_Update_Efx
	lea	ps_Channel4Temp+4(a5),a4
	moveq	#4-1,d4
.efxloop
	move.l	-(a4),a3
	IFEQ	DisableX
	tst.b	ct_XEffect(a3)
	beq	.noxeff
	bsr	is_DoXEffects
.noxeff
	ENDC
	tst.w	ct_Effect(a3)
	beq	.noefx
	bsr	is_DoEffects
.noefx
	dbra	d4,.efxloop

	move.b	ps_InitList(a5),d5
	beq	.NoInit
	bsr	is_InitChannels
.NoInit

	move.b	ps_PlayList(a5),d5
	beq	.NoPlay
	bsr	is_PlayChannels
.NoPlay

	IFNE	Rastertiming
	move.w	#0,$dff180
	ENDC

is_Exit	pull	d2-d7/a2-a6
	moveq	#0,d0
	rts

; ---------------------------------------------------------------------------
;	is_ProcessNewData (INTERNAL)
;
;	Updates PatternPointers and processes new channel-informations.
; ---------------------------------------------------------------------------
	CNOP	0,4			; align to 32-bit boundary
is_ProcessNewData
	move.b	ps_Row(a5),d2
	moveq	#0,d0
	move.w	ps_Position(a5),d0
	tst.w	ps_UpdatePtrs(a5)	; force update of pointers?
	bne	.UpdatePtrs

	addq.b	#1,d2
	and.b	#%00111111,d2
	bne	.NextRow

	moveq	#0,d2
	addq.w	#1,d0
.UpdatePtrs
	clr.w	ps_UpdatePtrs(a5)

	move.l	ps_ModuleInfo(a5),a0
	moveq	#0,d1
	move.w	mi_Positions(a0),d1
	cmp.w	d1,d0
	blo	.GetPattern
	move.w	mi_Restart(a0),d0	; restart module at?
.GetPattern
	move.l	ps_PatternInfo(a5),a0
	lea	pi_Array(a0),a0
	add.l	d0,a0
	move.w	d0,ps_Position(a5)
	moveq	#0,d0
	move.b	(a0),d0
	move.w	d0,ps_Pattern(a5)

	move.l	ps_PatternPointers(a5),a0
	IFNE	Opti030
	move.l	(a0,d0.l*4),ps_PatternPointer(a5)
	ELSE
	add.w	d0,d0
	add.w	d0,d0
	add.l	d0,a0
	move.l	(a0),ps_PatternPointer(a5)
	ENDC

.NextRow
	move.b	d2,ps_Row(a5)

	move.l	ps_PatternPointer(a5),a0

is_ReadChannels
	lea	ps_Channel1Temp(a5),a4
	moveq	#4-1,d4
rc_ChannelLoop
	move.l	(a4)+,a3

; ---------------------------------------------------------------------------
;	is_ReadChannel (INTERNAL)
;	Uses registers D0-D2/A0-A1
;
;	Processes patternchannelinformation
;
;	Input:	A0: ps_PatternPointer (does not scramble this)
;		A3: bmod_ChannelxTemp
; ---------------------------------------------------------------------------

is_ReadChannel
	lea	ct_Note(a3),a1
	moveq	#0,d0
	move.l	d0,(a1)				; init ct
	moveq	#4,d1
	move.b	d0,4(a1)

 	move.b	(a0)+,d0			; read compbyte or note
	beq	rc_NoNote

	bclr.b	#7,d0				; check if compbyte
	beq	rc_ReadData

.NormalByte
	bclr.b	#6,d0				; check for XEffect
	beq	.NoXEffectByte
	move.b	d0,(a1)+			; store ct_Note
	moveq	#%00001111,d0
	bra	.DoNormalRead
.NoXEffectByte
	move.b	d0,(a1)+			; store ct_Note
	moveq	#%00001110,d0
.DoNormalRead
	subq.b	#1,d1

rc_ReadData
	btst	d1,d0
	beq	.CheckNextInfo
	move.b	(a0)+,(a1)
.CheckNextInfo
	addq.l	#1,a1
	dbra	d1,rc_ReadData

	moveq	#0,d1
	move.b	ct_Sample(a3),d1
	beq	rc_NoSample			; use old values if no new

rc_FetchSampleData
	subq.b	#1,d1

	moveq	#bmod_SampleInfo_SIZE,d0
	moveq	#bmod_SampleHeader_SIZE,d2

	move.l	ps_SampleInfo(a5),a1
	tst.b	d1
	beq	.NoAdd
	mulu.w	d1,d2
	add.l	d2,a1
.NoAdd
	add.l	d0,a1

	clr.w	ct_Start(a3)
	move.w	sh_Length(a1),ct_Length(a3)
	move.w	sh_Repeat(a1),ct_Repeat(a3)
	move.w	sh_RepLen(a1),ct_RepLen(a3)
	move.b	sh_Volume(a1),d0
	ext.w	d0			; have to signext, may be negative
	move.w	d0,ct_Volume(a3)
	move.b	sh_FineTune(a1),ct_FineTune(a3)
rc_NoSample

	moveq	#0,d0
	move.b	ct_Note(a3),d0
	beq	rc_NoNote		; if no new note, don't retrigger

	move.w	d0,ct_NotePortamento(a3)
	moveq	#0,d1
	move.b	d0,d1

	subq.b	#1,d1
	IFEQ	NoFineTunes
	moveq	#0,d0
	move.b	ct_FineTune(a3),d0
	beq	rc_NoFT

	move.l	d0,d2			; mulu.w #36,d0
	lsl.l	#5,d0
	lsl.l	#2,d2
	add.l	d2,d0

	add.w	d0,d1
rc_NoFT
	ENDC

	IFNE	PreCalcRates	; use precalced samplerates if possible
	move.l	ps_SampleRates(a5),a1
	ENDC

	IFNE	Opti030
	move.w	is_PeriodTable(pc,d1.l*2),ct_Period(a3)
	IFNE	PreCalcRates
	move.w	(a1,d1.l*2),ct_SampleRate(a3)
	ENDC
	ELSE
	add.w	d1,d1
	IFNE	PreCalcRates
	add.l	d1,a1
	move.w	(a1),ct_SampleRate(a3)
	ENDC
	lea	is_PeriodTable(pc),a1
	add.l	d1,a1
	move.w	(a1),ct_Period(a3)
	ENDIF

	cmp.b	#$0e,ct_Effect(a3)
	bne	rc_TriggerNote
	move.b	ct_EffectValue(a3),d0
	lsr.b	#4,d0
	cmp.b	#$0d,d0				; note delay?
	beq	rc_NoNote

rc_TriggerNote
	move.b	ct_ChannelBit(a3),d0
	or.b	d0,ps_InitList(a5)
	or.b	d0,ps_PlayList(a5)
rc_NoNote
	clr.b	ct_RetriggerCounter(a3)

	; ---loop end---

	dbra	d4,rc_ChannelLoop

	move.l	a0,ps_PatternPointer(a5)
	rts

; ---------------------------------------------------------------------------
;	is_InitChannels (INTERNAL)
;	is_PlayChannels (INTERNAL)
;	Uses registers D0-D6/A0-A4
;
;	Initializes and starts samples.
;
;	Input:	D5: ps_PlayList(a5) / ps_InitList(a5)
;
; ---------------------------------------------------------------------------
	CNOP	0,4			; align to 32-bit boundary
is_InitChannels
	moveq	#4-1,d6

	lea	ps_Channel4Temp+4(a5),a4
.Loop
	move.l	-(a4),a3

	btst.b	d6,d5
	beq	.NoSound

	moveq	#0,d2
	move.w	ct_Start(a3),d2
	add.l	d2,d2

	moveq	#0,d3
	move.w	ct_Length(a3),d3
	beq	.NoSound
	add.l	d3,d3

	moveq	#0,d1
	move.b	ct_Sample(a3),d1
	beq	.NoSound
	move.b	d1,ct_PlayedSample(a3)
	subq.b	#1,d1

	move.l	d6,d0
	moveq	#AHISF_IMM,d4
	call	AHI_SetSound		**
.NoSound
	dbra	d6,.Loop
	clr.b	ps_InitList(a5)
	rts

	CNOP	0,4			; align to 32-bit boundary
is_PlayChannels
	moveq	#4-1,d6

	lea	ps_Channel4Temp+4(a5),a4
.Loop
	move.l	-(a4),a3

	btst.b	d6,d5
	beq	.NoSound

	moveq	#0,d1
	move.w	ct_Volume(a3),d1
	move.l	d6,d0
	lsl.l	#8,d1
	lsl.l	#2,d1

	IFNE	Opti030
	move.l	is_Pan(pc,d0.l*4),d2
	ELSE
	lea	is_Pan(pc),a0
	add.w	d0,d0
	add.w	d0,d0
	add.l	d0,a0
	move.l	d6,d0
	move.l	(a0),d2
	ENDC

	moveq	#AHISF_IMM,d3
	call	AHI_SetVol		**

	tst.b	ct_Sample(a3)
	beq	.NoSound

	IFNE	PreCalcRates
	moveq	#0,d1
	move.w	ct_SampleRate(a3),d1	; use precalc, if possible.
	bne	.FoundRate
	ENDC

	move.w	ct_Period(a3),d2

	move.l	#PALSpeed,d1		; PAL value, look in bmod.i.
	divu.w	d2,d1
	ext.l	d1
.FoundRate

	move.l	d6,d0
	moveq	#AHISF_IMM,d2
	call	AHI_SetFreq		**
.NoSound
	dbra	d6,.Loop

	clr.b	ps_PlayList(a5)
	rts


is_SetLoops
	push	d2-d5/a2-a6
	move.l	h_Data(a0),a5

	moveq	#0,d5
	move.w	ahism_Channel(a1),d5
	moveq	#0,d2
	lea	ps_Channel1Temp(a5),a0

	IFNE	Opti030
	move.l	(a0,d5.l*4),a3
	ELSE
	move.l	d5,d0
	add.w	d0,d0
	add.w	d0,d0
	add.l	d0,a0
	move.l	(a0),a3
	ENDC

	moveq	#0,d3

	moveq	#0,d1
	move.b	ct_PlayedSample(a3),d1	; last sample played in this channel
	beq	.NoSample
	subq.b	#1,d1

	move.w	ct_Repeat(a3),d2
	add.l	d2,d2
	move.w	ct_RepLen(a3),d3
	add.l	d3,d3
	bne	.FoundRepeat
.NoSample
	moveq	#AHI_NOSOUND,d1
.FoundRepeat

	move.l	ps_AHIbase(a5),a6
	move.l	d5,d0
	moveq	#0,d4
	call	AHI_SetSound

	pull	d2-d5/a2-a6
	rts


; ---------------------------------------------------------------------------
;	is_DoXEffects (INTERNAL)
;
;	Updates all eXtended effects.
;	Uses registers D0-D2/A0-A1
;
;	NOTE!: Following routines !MUST NOT! scramble any other registers
;	except mentioned above!
;
;	Input:	A3: bmod_ChannelxTemp
;
;	Updates;
;		Volume; $10-$50
; ---------------------------------------------------------------------------
	IFEQ	DisableX
	CNOP	0,4			; align to 32-bit boundary
is_DoXEffects
	moveq	#0,d2
	move.b	ct_XEffect(a3),d2

	sub.b	#$10,d2
	bmi	dxe_SubReturn

	lea	dxe_SubReturn(pc),a0	; for subroutine rts
	push	a0

	cmp.b	#$40,d2
	bls	de_VolumeChange

	rts
	nop
dxe_SubReturn
	tst.b	d2			; check clear request
	beq	.Exit
	clr.b	ct_XEffect(a3)
.Exit
	rts
	ENDC

; ---------------------------------------------------------------------------
;	is_DoEffects (INTERNAL)
;
;	Updates all effects.
;	Uses registers D0-D3/A0-A1
;
;	NOTE!: Following routines !MUST NOT! scramble any other registers
;	except mentioned above!
;
;	Input:	A3: bmod_ChannelxTemp
;
;	Routines input:	D2: ct_EffectValue(a3)
;
;	Output: D2: If set, clr.w ct_Effect(a3).
; ---------------------------------------------------------------------------
	CNOP	0,4			; align to 32-bit boundary
is_DoEffects
	moveq	#0,d0
	move.w	ct_Effect(a3),d0

	moveq	#0,d2
	move.b	d0,d2
	eor.b	d0,d0			; clear low 8 bits
	lsr.w	#7,d0
	move.w	de_JumpTable(pc,d0.w),d0
	jsr	de_JumpTable(pc,d0.w)

	tst.b	d2			; check clear request
	beq	.Return
	clr.w	ct_Effect(a3)
.Return
	rts
	CNUL	0,4
de_JumpTable
	dc.w	de_Arpeggio-de_JumpTable		; 0
	dc.w	de_PortaUp-de_JumpTable			; 1
	dc.w	de_PortaDown-de_JumpTable		; 2
;	dc.w	de_TonePortamento-de_JumpTable		; 3
	dc.w	de_Unused-de_JumpTable
;	dc.w	de_Vibrato-de_JumpTable			; 4
	dc.w	de_Unused-de_JumpTable
;	dc.w	de_TonePlusVolSlide-de_JumpTable	; 5
	dc.w	de_Unused-de_JumpTable
;	dc.w	de_VibratoPlusVolSlide-de_JumpTable	; 6
	dc.w	de_Unused-de_JumpTable
;	dc.w	de_Tremolo-de_JumpTable			; 7
	dc.w	de_Unused-de_JumpTable
	dc.w	de_Unused-de_JumpTable			; 8 unused
	dc.w	de_SampleOffset-de_JumpTable		; 9
	dc.w	de_VolumeSlide-de_JumpTable		; a
	dc.w	de_PositionJump-de_JumpTable		; b
	dc.w	de_VolumeChange-de_JumpTable		; c
	dc.w	de_PatternBreak-de_JumpTable		; d
	dc.w	is_E_Commands-de_JumpTable		; e
	dc.w	de_SetSpeed-de_JumpTable		; f
	dc.w	de_NewSampleOffset-de_JumpTable		; 10

is_E_Commands
	moveq	#0,d0
	moveq	#$0f,d1

	move.b	d2,d0
	and.b	d1,d2			; effect value
	not.b	d1			; %00001111 => %11110000
	and.b	d1,d0			; effect

	lsr.b	#3,d0
	move.w	ec_JumpTable(pc,d0.w),d0
	jmp	ec_JumpTable(pc,d0.w)
	CNUL	0,4
ec_JumpTable
	dc.w	ec_Unused-ec_JumpTable			; e0
	dc.w	ec_FinePortaUp-ec_JumpTable		; e1
	dc.w	ec_FinePortaDown-ec_JumpTable		; e2
	dc.w	ec_SetGlissControl-ec_JumpTable		; e3
	dc.w	ec_SetVibratoControl-ec_JumpTable	; e4
	dc.w	ec_SetFineTune-ec_JumpTable		; e5
	dc.w	ec_JumpLoop-ec_JumpTable		; e6
	dc.w	ec_SetTremoloControl-ec_JumpTable	; e7
	dc.w	ec_Unused-ec_JumpTable			; e8 unused
	dc.w	ec_RetriggerNote-ec_JumpTable		; e9
	dc.w	ec_FineVolumeSlideUp-ec_JumpTable	; ea
	dc.w	ec_FineVolumeSlideDown-ec_JumpTable	; eb
	dc.w	ec_NoteCut-ec_JumpTable			; ec
	dc.w	ec_NoteDelay-ec_JumpTable		; ed
	dc.w	ec_PatternDelay-ec_JumpTable		; ee
	dc.w	ec_Unused-ec_JumpTable			; ef

ec_SetVibratoControl
ec_SetFineTune
ec_SetTremoloControl
ec_FunkIt
ec_Unused
de_Vibrato
de_TonePlusVolSlide
de_VibratoPlusVolSlide
de_Tremolo
de_Unused
	rts

de_Arpeggio
	tst.b	d2
	bne	arp_Cont
	rts
arp_Cont
	moveq	#0,d1
	move.b	ps_PlayCounter(a5),d1
	divu	#3,d1
	swap	d1
	cmp.w	#2,d1
	beq	arp_First
	tst.w	d1
	bne	arp_Second
	bra	arp_Third
arp_First
	moveq	#0,d2
arp_Second
	lsr.b	#4,d2
arp_Third
	and.b	#%00001111,d2

arp_DoArpeggio
	moveq	#0,d0
	move.b	ct_Note(a3),d0
	add.b	d2,d0			; d0 = arpeggio note

	moveq	#0,d1
	move.b	ct_FineTune(a3),d1
	beq	arp_NoFineTune

	move.l	d1,d3			; mulu.w #36,d1
	lsl.l	#5,d1
	lsl.l	#2,d3
	add.l	d3,d1

	add.w	d1,d0
arp_NoFineTune
	add.b	d0,d0
	subq.b	#2,d0

	moveq	#36*2,d2
	cmp.b	d2,d0
	bls	arp_DAPC
	move.l	d2,d0
arp_DAPC
	lea	is_PeriodTable(pc),a1
	move.w	(a1,d0.l),ct_Period(a3)
	clr.w	ct_SampleRate(a3)
	move.b	ct_ChannelBit(a3),d0
	moveq	#FALSE,d2
	or.b	d0,ps_PlayList(a5)
	rts

ec_FinePortaUp
	bsr	de_PortaUp
	moveq	#TRUE,d2
	rts

de_PortaUp
	move.w	ct_Period(a3),d0
	sub.w	d2,d0
	moveq	#113,d1			; should be 108
	cmp.w	d1,d0
	bhs	pd_Ok
	move.w	d1,d0
	bra	pd_Ok

ec_FinePortaDown
	bsr	de_PortaDown
	moveq	#TRUE,d2
	rts

de_PortaDown
	move.w	ct_Period(a3),d0
	add.w	d2,d0
	move.w	#856,d1			; should be 907
	cmp.w	d1,d0
	blo	pd_Ok
	move.w	d1,d0
pd_Ok
	move.w	d0,ct_Period(a3)
	clr.w	ct_SampleRate(a3)
	move.b	ct_ChannelBit(a3),d0
	or.b	d0,ps_PlayList(a5)

	moveq	#TRUE,d2
	rts

de_TonePortamento				; uses D3
	move.b	ct_NPSpeed(a3),d0
	tst.b	d2
	beq	tp_UseOldValue
	move.b	d2,ct_NPSpeed(a3)
	move.b	d2,d0
tp_UseOldValue
	move.b	d0,d2
	moveq	#0,d0
	move.w	ct_NotePortamento(a3),d0

	tst.b	ct_GlissControl(a3)
	bne	tp_PeriodMode

tp_NoteMode
	moveq	#0,d1
	move.b	ct_Note(a3),d1
	move.b	d1,d3
	sub.b	d0,d1
	bmi	tp_ToneUp
tp_ToneDown
	move.b	d3,d1
	add.b	d2,d1
	cmp.b	d0,d1
	bhs	tp_ToneReached
	moveq	#FALSE,d2
	bra	tp_DoPorta
tp_ToneUp
	move.b	d3,d1
	sub.b	d2,d1
	cmp.b	d0,d1
	bls	tp_ToneReached
	moveq	#FALSE,d2
	bra	tp_DoPorta

tp_ToneReached
	move.b	d0,d1
	moveq	#TRUE,d2

tp_DoPorta
	move.b	d1,ct_Note(a3)
	moveq	#0,d0
	move.b	ct_FineTune(a3),d0
	beq	tp_NoFT

	move.l	d0,d2			; mulu.w #36,d0
	lsl.l	#5,d0
	lsl.l	#2,d2
	add.l	d2,d0

	add.w	d0,d1
tp_NoFT
	add.w	d1,d1
	subq.w	#2,d1
	lea	is_PeriodTable(pc),a1
	move.w	(a1,d1.l),ct_Period(a3)

	move.l	ps_SampleRates(a5),a1
	move.w	(a1,d1.l),ct_SampleRate(a3)
	move.b	ct_ChannelBit(a3),d0
	or.b	d0,ps_PlayList(a5)

	rts

tp_PeriodMode
	moveq	#0,d1
	move.b	ct_FineTune(a3),d1
	beq	tp_pm_NoFT

	move.l	d1,d3			; mulu.w #36,d1
	lsl.l	#5,d1
	lsl.l	#2,d3
	add.l	d3,d1

	add.w	d1,d0
tp_pm_NoFT
	add.w	d0,d0
	subq.b	#2,d0
	lea	is_PeriodTable(pc),a1
	move.w	(a1,d0.l),d0

	move.w	ct_Period(a3),d1
	move.w	d1,d3
	sub.w	d0,d1
	bmi	tp_pm_ToneUp
tp_pm_ToneDown
	move.w	d3,d1
	add.w	d2,d1
	cmp.w	d0,d1
	bhs	tp_pm_ToneReached
	moveq	#FALSE,d2
	bra	tp_pm_DoPorta
tp_pm_ToneUp
	move.w	d3,d1
	sub.w	d2,d1
	cmp.w	d0,d1
	bls	tp_pm_ToneReached
	moveq	#FALSE,d2
	bra	tp_DoPorta

tp_pm_ToneReached
	move.w	d0,d1
	moveq	#TRUE,d2

tp_pm_DoPorta
	move.w	d1,ct_Period(a3)
	clr.w	ct_SampleRate(a3)
	move.b	ct_ChannelBit(a3),d0
	or.b	d0,ps_PlayList(a5)

	rts

;de_Vibrato
	tst.b	d2
	bne	vb_UseNewValues
	move.b	ct_Vibrato(a3),d2

vb_UseNewValues
	move.b	d2,d1
	and.b	#%00001111,d2		; amplituudi
	and.b	#%11110000,d1		; ([oskillaatiot]*nopeus)/64


de_NewSampleOffset
	moveq	#0,d0
	move.b	ct_SampleOffset(a3),d0
	tst.b	d2
	beq	.UseOldOffset
	move.b	d2,ct_SampleOffset(a3)
	move.b	d2,d0
.UseOldOffset
	move.b	d0,d2
	add.w	#255,d2
	bra	so_DoOffset

de_SampleOffset
	moveq	#0,d0
	move.b	ct_SampleOffset(a3),d0
	tst.b	d2
	beq	.UseOldOffset
	move.b	d2,ct_SampleOffset(a3)
	move.b	d2,d0
.UseOldOffset
	move.b	d0,d2

so_DoOffset
	lsl.w	#7,d2
	cmp.w	ct_Length(a3),d2
	bge	.TooBigOffset
	sub.w	d2,ct_Length(a3)
	move.w	d2,ct_Start(a3)
	
	move.b	ct_ChannelBit(a3),d0
	or.b	d0,ps_InitList(a5)

.TooBigOffset
	moveq	#TRUE,d2
	rts

ec_FineVolumeSlideUp
	move.b	d2,d1
	bsr	vs_VolumeSlideUp	; slide value to d1
	moveq	#TRUE,d2
	rts
ec_FineVolumeSlideDown
	bsr	vs_VolumeSlideDown	; slide value to d2
	moveq	#TRUE,d2
	rts

de_VolumeSlide
	moveq	#0,d0
	move.w	ct_Volume(a3),d0
	move.b	d2,d1
	lsr.b	#4,d1
	beq	vs_VolumeSlideDown

vs_VolumeSlideUp
	moveq	#$40,d2
	add.b	d1,d0
	cmp.b	d2,d0
	bls	vs_VolumeOK
	move.l	d2,d0
	bra	vs_NoMoreSlide

vs_VolumeSlideDown
	sub.b	d2,d0
	bpl	vs_VolumeOK
	moveq	#0,d0
vs_NoMoreSlide
	moveq	#TRUE,d2
	bra	vs_DoVolume
vs_VolumeOK
	moveq	#FALSE,d2
vs_DoVolume
	push	d2
	move.l	d0,d2
	bsr	de_VolumeChange
	pull	d2
	rts

de_PatternBreak
	move.w	ps_Position(a5),d2
	addq.w	#1,d2
de_PositionJump
	move.w	d2,ps_Position(a5)
	moveq	#TRUE,d2
	move.w	d2,ps_UpdatePtrs(a5)
	clr.b	ps_Row(a5)
	rts

de_VolumeChange
	move.w	d2,ct_Volume(a3)
	move.b	ct_ChannelBit(a3),d0
	moveq	#TRUE,d2
	or.b	d0,ps_PlayList(a5)
	rts

de_SetSpeed
	cmp.b	#32,d2
	bhs	de_SetCIATempo
	tst.b	d2
	beq	ss_StopPlay
	move.b	d2,ps_PlayCounter(a5)
	move.b	d2,ps_Speed(a5)
	moveq	#TRUE,d2
	rts
ss_StopPlay
	; tässä pitäisi lopettaa soitto.

	moveq	#TRUE,d2
	rts

de_SetCIATempo
	cmp.w	ps_CIATempo(a5),d2
	beq	sct_Exit

	move.w	d2,ps_CIATempo(a5)
	add.w	d2,d2
	divu.w	#5,d2
	swap	d2
	clr.w	d2
	move.l	#TAG_DONE,-(sp)
	move.l	d2,-(sp)
	move.l	#AHIA_PlayerFreq,-(sp)
	move.l	sp,a1
	call	AHI_ControlAudioA
	lea	3*4(sp),sp
sct_Exit
	moveq	#TRUE,d2
	rts

; ---------------------------------------------------------------------------
;  Some E-commands here, others above.
; ---------------------------------------------------------------------------

ec_SetGlissControl
	tst.b	d2
	sne	ct_GlissControl(a3)
	moveq	#TRUE,d2
	rts

ec_JumpLoop
	tst.b	d2
	beq	jl_SetPattLoopStart
	move.b	ct_E60LoopCounter(a3),d0
	bmi	jl_Loopisover
	bpl	jl_loops
jl_LoopStarted
	move.b	d2,d0

jl_loops
	subq.b	#1,d0
	beq	jl_Loopisover

jl_ContinueLoop
	move.b	ct_E60LoopPos(a3),ps_Row(a5)
	move.l	ct_E60LoopPtr(a3),ps_PatternPointer(a5)
	move.b	d0,ct_E60LoopCounter(a3)
jl_IgnoreLoop
	bra	jl_Exit
jl_Loopisover
	move.b	#0,ct_E60LoopCounter(a3)
jl_Exit
	moveq	#TRUE,d2
	rts

jl_SetPattLoopStart
	tst.b	ct_E60LoopCounter(a3)
	bne	jl_spl

	move.b	ps_Row(a5),ct_E60LoopPos(a3)
	move.l	ps_PatternPointer(a5),ct_E60LoopPtr(a3)
	clr.b	ct_E60LoopCounter(a3)
jl_spl
	moveq	#TRUE,d2
	rts

ec_RetriggerNote
	tst.b	d2			; if no pos, prevent scramble
	beq	rn_Exit

	tst.b	ct_RetriggerCounter(a3)
	bne	rn_ActiveCounter
	move.b	d2,ct_RetriggerCounter(a3)	; set new counter
rn_ActiveCounter
	subq.b	#1,ct_RetriggerCounter(a3)
	ble	rn_Retrigger
rn_Exit
	rts

rn_Retrigger
	clr.b	ct_RetriggerCounter(a3)
	move.b	ct_ChannelBit(a3),d0
	or.b	d0,ps_PlayList(a5)
	rts

ec_NoteCut
	move.b	ps_Speed(a5),d0
	sub.b	d2,d0
	cmp.b	d0,d2
	bne	.Exit

	moveq	#0,d2
	bra	de_VolumeChange

.Exit	moveq	#FALSE,d2
	rts

ec_NoteDelay
	tst.b	d2
	beq	nd_Trigger
	subq.b	#1,d2
	move.b	#$d0,d0
	or.b	d2,d0
	move.b	d0,ct_EffectValue(a3)

	moveq	#FALSE,d2
	rts
nd_Trigger
	move.b	ct_ChannelBit(a3),d0
	moveq	#TRUE,d2
	or.b	d0,ps_PlayList(a5)
	rts

ec_PatternDelay
	moveq	#0,d0
	move.b	ps_Speed(a5),d0
	mulu.w	d2,d0
	add.b	d0,ps_Speed(a5)
	moveq	#TRUE,d2
	rts

	; end of player, next follows tables

	cnul	0,4
is_Pan	dc.l	0,$10000,0,$10000

is_VibratoTable				; sine-wave
        dc.b   0, 24, 49, 74, 97,120,141,161
        dc.b 180,197,212,224,235,244,250,253
        dc.b 255,253,250,244,235,224,212,197
        dc.b 180,161,141,120, 97, 74, 49, 24
	dc.b  66, 70, 66, 84, 69, 65, 77, 33 

	cnul	0,4
is_PeriodTable
; Tuning 0, Normal
        dc.w    856,808,762,720,678,640,604,570,538,508,480,453
        dc.w    428,404,381,360,339,320,302,285,269,254,240,226
        dc.w    214,202,190,180,170,160,151,143,135,127,120,113
	IFEQ	NoFineTunes
; Tuning 1
        dc.w    850,802,757,715,674,637,601,567,535,505,477,450
        dc.w    425,401,379,357,337,318,300,284,268,253,239,225
        dc.w    213,201,189,179,169,159,150,142,134,126,119,113
; Tuning 2
        dc.w    844,796,752,709,670,632,597,563,532,502,474,447
        dc.w    422,398,376,355,335,316,298,282,266,251,237,224
        dc.w    211,199,188,177,167,158,149,141,133,125,118,112
; Tuning 3
        dc.w    838,791,746,704,665,628,592,559,528,498,470,444
        dc.w    419,395,373,352,332,314,296,280,264,249,235,222
        dc.w    209,198,187,176,166,157,148,140,132,125,118,111
; Tuning 4
        dc.w    832,785,741,699,660,623,588,555,524,495,467,441
        dc.w    416,392,370,350,330,312,294,278,262,247,233,220
        dc.w    208,196,185,175,165,156,147,139,131,124,117,110
; Tuning 5
        dc.w    826,779,736,694,655,619,584,551,520,491,463,437
        dc.w    413,390,368,347,328,309,292,276,260,245,232,219
        dc.w    206,195,184,174,164,155,146,138,130,123,116,109
; Tuning 6
        dc.w    820,774,730,689,651,614,580,547,516,487,460,434
        dc.w    410,387,365,345,325,307,290,274,258,244,230,217
        dc.w    205,193,183,172,163,154,145,137,129,122,115,109
; Tuning 7
        dc.w    814,768,725,684,646,610,575,543,513,484,457,431
        dc.w    407,384,363,342,323,305,288,272,256,242,228,216
        dc.w    204,192,181,171,161,152,144,136,128,121,114,108
; Tuning -8
        dc.w    907,856,808,762,720,678,640,604,570,538,508,480
        dc.w    453,428,404,381,360,339,320,302,285,269,254,240
        dc.w    226,214,202,190,180,170,160,151,143,135,127,120
; Tuning -7
        dc.w    900,850,802,757,715,675,636,601,567,535,505,477
        dc.w    450,425,401,379,357,337,318,300,284,268,253,238
        dc.w    225,212,200,189,179,169,159,150,142,134,126,119
; Tuning -6
        dc.w    894,844,796,752,709,670,632,597,563,532,502,474
        dc.w    447,422,398,376,355,335,316,298,282,266,251,237
        dc.w    223,211,199,188,177,167,158,149,141,133,125,118
; Tuning -5
        dc.w    887,838,791,746,704,665,628,592,559,528,498,470
        dc.w    444,419,395,373,352,332,314,296,280,264,249,235
        dc.w    222,209,198,187,176,166,157,148,140,132,125,118
; Tuning -4
        dc.w    881,832,785,741,699,660,623,588,555,524,494,467
        dc.w    441,416,392,370,350,330,312,294,278,262,247,233
        dc.w    220,208,196,185,175,165,156,147,139,131,123,117
; Tuning -3
        dc.w    875,826,779,736,694,655,619,584,551,520,491,463
        dc.w    437,413,390,368,347,328,309,292,276,260,245,232
        dc.w    219,206,195,184,174,164,155,146,138,130,123,116
; Tuning -2
        dc.w    868,820,774,730,689,651,614,580,547,516,487,460
        dc.w    434,410,387,365,345,325,307,290,274,258,244,230
        dc.w    217,205,193,183,172,163,154,145,137,129,122,115
; Tuning -1
        dc.w    862,814,768,725,684,646,610,575,543,513,484,457
        dc.w    431,407,384,363,342,323,305,288,272,256,242,228
        dc.w    216,203,192,181,171,161,152,144,136,128,121,114
	ENDC

	END
