;----------------------------------------------------------------------------------------
;pro tracker (mod) player for the spc700 (c)d4s in 2005
;----------------------------------------------------------------------------------------

.MEMORYMAP					; Tell WLA that the SPC has RAM at locations ;$0000-$ffff in every bank
SLOTSIZE $10000			; and that this area is $10000 bytes in size.
DEFAULTSLOT 0				; There is only a single slot in SNES, other consoles
SLOT 0 $0000				; may have more slots per bank.
.ENDME

.ROMBANKSIZE $10000		; Every ROM bank is 64 KBytes in size
.ROMBANKS 1
;----------------------------------------------------------------------------------------
.BANK 0 SLOT 0



.define SpcCmdUploadSong	$f1		;indicates that a song is to be uploaded
.define SpcCmdUploadSongT1	$f2		;indicates that data for transfer1 is on apu ports
.define SpcCmdUploadSongT2	$f3		;indicates that data for transfer2 is on apu ports
.define SpcCmdUploadSongDone	$f4		;indicates that song upload is complete
.define SpcCmdReceiveStream	$f5		;indicates that 65816 wants to stream brr data to spc
.define SpcCmdSubmitStreamNumber	$f7		;indicates that 65816 wants to stream brr data to spc
.define SpcCmdReceiveStreamComplete	$f6	;spc wants to end stream transmission
.define SpcCmdUploadSamplePack	$f8		;indicates that a sample pack is to be uploaded. the rest of the commands are taken from normal song upload
.define SpcCmdUploadSamplePackDone	$f9		;indicates that sample pack upload is complete
.define SpcCmdPlaySoundEffect	$fa		;play a sound effect
.define SpcCmdStopSong		$fb
.define SpcCmdSetSongSpeed	$fc		;set timer speed of mod playback routine
.define SpcCmdSetSongChMask	$fd		;song channel mask
.define SpcCmdReportType	$fe		;type of data spc should respond with
.define SpcCmdSetSongVolume	$ff

.define SpcStreamLatency	7		;number of frames to load before actually playing sample
.define SpcStreamSampleNumber	33		;number of dsp sample to use as streamer
.define StreamerTimer0		$00		;how long to wait before downloading next frame

.define ChannelArray		$0
.define ExtSample		$20		;this sample is used for set sample offset effect

.define Stack			$ff
.define ApuCodeBaseAdress	$300
.define SampleSource		$0200		;$ff bytes long
.define StreamBuffer		$fb80		;480 bytes long
.define HighestPortaPeriod	$0071
.define LowestPortaPeriod	$0358
.define EchoStartAdress		$f8
.define EchoDelay		$01

.define SongEffectsUploaded	$ef			;flags that indicate whether a song or sample pack have already been uploaded.bit0=song bit1=sample pack. wla is too stupid to recognize a variable here, so we have to use a static one.
;variables:
;.........................................................
.enum $60
WaitCounter		dw
CurrentPatternPointerLo	db
CurrentPatternPointerHi	db
InitPatternPointerLo	db
InitPatternPointerHi	db
PortamentoDownLimitLo	db
PortamentoDownLimitHi	db
PortamentoUpLimitLo	db
PortamentoUpLimitHi	db
CurrentChDataLo		db
CurrentChDataHi		db
TempBuffer1		db
TempBuffer2		db
TempBuffer3		db
TempBuffer4		db

TempBuffer5		db
TempBuffer6		db
TempBuffer7		db
TempBuffer8		db
TempBuffer9		db
TempBuffer10		db
TempVol			db
KeyOn			db
CurrentChannel		db
CurrentVolume		db
PitchBufferLo		db
PitchBufferHi		db
CurrentPatternNumber	db
PortamentoSpeedMask	db
NotUsedVariable		db
CurrentPeriodPointer	db
CurrentPeriodPointerHi	db

PeriodTablePointerLo	db
PeriodTablePointerHi	db
SpcDataPointerLo	db
SpcDataPointerHi	db
ModFilePointerLo	db
ModFilePointerHi	db
ModFilePointer2Lo	db
ModFilePointer2Hi	db

;added variables

ScratchPad1		db
ScratchPad2		db
ScratchPad3		db

PeriodBufferLo		db
PeriodBufferHi		db

NumberOfPatterns	db
PatternSpeed		db
NextRowChFadeOuts	db		;lower nibble bits0-4:channels to fade out for next row
NextRowChFadeOutTime	db		;patternspeed-2
PortamentoLUTLo		db	;table for portamento up/down
PortamentoLUTHi		db	;table for portamento up/down
TonePortamentoLUTLo	db	;table for portamento up/down
TonePortamentoLUTHi	db	;table for portamento up/down
CurrentRowPointerLo	db
CurrentRowPointerHi	db
CurrentLoopPointerLo	db
CurrentLoopPointerHi	db
LoopCounter		db
PatternBreakFlag	db	;if bit0 is set, break to next pattern

;main volume section:
MainVolL		db
MainVolR		db
EchoVolL		db
EchoVolR		db
ChannelEnable		db
ChannelEchoEnable	db


PitchTablePointerLo	db
PitchTablePointerHi	db

Channel1Array		dw
Channel2Array		dw
Channel3Array		dw
Channel4Array		dw

;apu streaming stuff
StreamState		db			;variable for streaming state machine
StreamCurrentBuffer	db			;indicates currently active stream buffer out of 8
StreamVolume		db

ActiveSoundEffectCh	db			;0-3
ModPlayerTimer0		db
SongChannelMask		db			;mask variable for 4 song channels. default $0f
SpcReportType		db
ModTimecode		dw			;timecode for mod currently playing. reset after song upload
SpecialReportCmd	db
SoundEffectFlipFlag	db
TickSampleStart		db		;channel mask	to start samples on the next tick. used for delay and retrigger
MusicVol1			db		;mod channel 0,2 volume
MusicVol2			db		;mod channel 1,3 volume
.ende


;channel variables
.enum			$00
ch_instrument			db		;00
ch_note				db		;01
ch_effect			db		;02
ch_effect_data			db		;03
ch_finetune			db		;04
ch_current_volume		db		;05
ch_current_pitch_lo		db		;06 this gets updated on portamento etc
ch_current_pitch_hi		db		;07 (not directly used in sourcecode)
ch_target_pitch_lo		db		;08 (not used at all in sourcecode) target pitch for tone portamento
ch_target_pitch_hi		db		;09 (not used at all in sourcecode)
ch_tone_portamento_speed	db		;0a
ch_portamento_slide_dir		db		;0b bit0 sets up/down
ch_vibrato_command_buffer	db		;0c this is a buffer for depth and speed when using vibrato continue
ch_vibrato_position		db		;0d
ch_vibrato_tremolo_waveform	db		;0e 0=sine, 1=ramp down, 2=square
ch_tremolo_position		db		;0f
ch_tremolo_command_buffer	db		;10
ch_arpeggio_note_buffer		db		;11
ch_tick_delay			db		;12
ch_amiga_period_lo		db		;13 this is the current amiga period
ch_amiga_period_hi		db		;14 (not directly used in sourcecode)
ch_current_amiga_period_lo	db		;15 this is the tone portamento target amiga period
ch_current_amiga_period_hi	db		;16 (not directly used in sourcecode)
;next must be the last entry in this array:
ch_current_channelmask		db		;last byte in array and mask for channel



.ende


;.........................................................



.ORGA (ApuCodeBaseAdress-2)		;$2FC
.DW ApuCodeBaseAdress			;spcram copy offset




BootInit:
	clrp
	mov	$f1,#$f0
	mov	a,#$ff
	mov	!$fff0,a
	mov	x,#Stack			;set stack pointer (old: $cf)
	mov	sp,x
	mov	a,#$00
	mov	x,a
	jmp	!ClearVariables

CopyRight:
	.db "matt@dforce3000.de"
CopyRightEnd:

ClearVariables:
	mov	(x)+,a
	cmp	x,#$f0
	bne	ClearVariables

	mov MainVolL,#$7f
	mov MainVolR,#$7f
	mov EchoVolL,#$00
	mov EchoVolR,#$00
	mov ChannelEnable,#$0f
	mov ChannelEchoEnable,#$0f
	mov SpcReportType,#0		;reset report type to void
	mov	MusicVol1,#$7f			;init volume. narrow stereo, a little bit stereo separation
	mov	MusicVol2,#$7f

	
	mov	SpcDataPointerLo,#(ModFile & $00ff)		;setup mod pointer
	mov	SpcDataPointerHi,#(ModFile >> 8)

	call	!ClearAllChannelArray
	call	!SetupDspRegsAndTimers		;reset dsp regs first.
	call	!InitVariables		;set up pointers to first pattern
	call	!UpdateVolEchoVariablesOnly
	

MainLoop:
	mov	a,$fd		;clear counter0
MainLoop1:
	mov	a,$fd		;wait for counter 0 to become zero
	beq	MainLoop1		;takes roughly 44 scanlines total

	call	!MainWaitLoop		
	call	!SendReportData

	mov	a,$f4		;check if command present on port0
	and	a,#$f0
	cmp	a,#$f0
	bne	MainLoop	;return to mainloop if there isn't

	mov	a,$f4		;get command
	and	a,#$0f	
	asl 	a
	mov	x,a			;pointer into jump table
	jmp	[!CommandJumpTable+x]
	
CommandJumpTable:
	.dw CommandVoid			;0
	.dw ReceiveSong
	.dw CommandVoid
	.dw CommandVoid
	.dw CommandVoid
	.dw ReceiveStream		;5
	.dw CommandVoid
	.dw CommandVoid
	.dw ReceiveSamplePack
	.dw CommandVoid
	.dw PlaySoundEffect		;10
	.dw StopSongStream
	.dw SetSongSpeed
	.dw SetSongChannelMask
	.dw SetReportType
	.dw SetSongVolume


ReceiveSong:
	 ;timers must be reset before receiving a song, else we can't wait properly for the echo buffer to initialize
	call	!ReceiveSongHandler
	call	!ClearAllChannelArray
	call	!SetupSamples		;set up sample offsets
	call	!SetupDspRegsAndTimers		;reset dsp regs first.
	call	!InitVariables		;set up pointers to first pattern
	call	!UpdateVolEchoVariablesOnly
;return to mainloop:
	bra MainLoop

UpdateVolEcho:
	call	!UpdateVolEchoHandler
	bra	MainLoop

ResetHandler:
;clear apu ports:
	mov	y,#$00
	mov	$f4,y
	mov	$f5,y
	mov	$f6,y
	mov	$f7,y
;clear dsp regs:
	mov	a,#$6c
	mov	y,#$e0		;reset dsp, mute on, echo off
	movw	$f2,ya
	mov	a,#$0c
	movw	$f2,ya
	mov	a,#$1c
	movw	$f2,ya
	mov	a,#$2c
	movw	$f2,ya
	mov	a,#$3c
	movw	$f2,ya
	mov	a,#$4c
	movw	$f2,ya
	mov	y,#$ff
	mov	a,#$5c
	movw	$f2,ya
	mov	y,#$00
	mov	a,#$0d
	movw	$f2,ya
	mov	a,#$2d
	movw	$f2,ya
	mov	a,#$3d
	movw	$f2,ya
	mov	a,#$4d
	movw	$f2,ya
	mov	a,#$5d
	movw	$f2,ya
	mov	a,#$6d
	movw	$f2,ya
	mov	a,#$7d
	movw	$f2,ya
	mov	$f1,#$f0
;jump to ipl rom:
	jmp	!$ffc0
	jmp	!$ffc0

MainWaitLoop:
	bbs	$ef.0,MainWaitLoopSongPresent		;only process song if song has been uploaded before
	ret
	
MainWaitLoopSongPresent:
	inc	WaitCounter

InnerWaitLoop:
	cmp	WaitCounter,PatternSpeed			;old #$04 incremet WaitCounter until 4, selfmodying code changes the default speed(command setspeed0x0f) by writing to the second operand
	bne	UpdateTickBasedEffectsOnly


	mov	WaitCounter,#$00
	mov	y,NextRowChFadeOuts
	mov	a,#$5c
	movw	$f2,ya
	mov	a,$fe	;clear 4bit counter
	mov	y,#2
KofWaitLoop:
	mov	a,$fe		;wait for 2mS
	beq	KofWaitLoop
;**************************************************
	dbnz	y,KofWaitLoop

	call	 !ProcessNextRow

	call	!PreTestRow
	ret



UpdateTickBasedEffectsOnly:	


	movw	ya,Channel1Array
	movw	CurrentChDataLo,ya
	mov	CurrentChannel,#$00
	call	!CheckForTickBasedEffects
	movw	ya,Channel2Array
	movw	CurrentChDataLo,ya
	mov	CurrentChannel,#$10
	call	!CheckForTickBasedEffects
	movw	ya,Channel3Array
	movw	CurrentChDataLo,ya
	mov	CurrentChannel,#$20
	call	!CheckForTickBasedEffects

	movw	ya,Channel4Array
	movw	CurrentChDataLo,ya	
	mov	CurrentChannel,#$30
	call	!CheckForTickBasedEffects

	mov	a,TickSampleStart
	beq UpdateTickBasedEffectsOnlyNoRetrigger

	mov	$f2,#$4c			
	mov	$f3,a							;retrigger recently started samples
	
	mov	TickSampleStart,#0
UpdateTickBasedEffectsOnlyNoRetrigger:

	ret



ProcessNextRow:
	movw	ya,CurrentPatternPointerLo	;mark beginning of current row
	movw	CurrentRowPointerLo,ya

	incw	ModTimecode
	mov	KeyOn,#$00			;clear key on
	movw	ya,Channel1Array
	movw	CurrentChDataLo,ya	
	mov	CurrentChannel,#$00
	call	!GetDataFromPattern
	call	!SetVolume
	movw	ya,Channel2Array
	movw	CurrentChDataLo,ya	
	mov	CurrentChannel,#$10
	call	!GetDataFromPattern
	call	!SetVolume
	movw	ya,Channel3Array
	movw	CurrentChDataLo,ya	
	mov	CurrentChannel,#$20
	call	!GetDataFromPattern
	call	!SetVolume
	movw	ya,Channel4Array
	movw	CurrentChDataLo,ya	
	mov	CurrentChannel,#$30
	call	!GetDataFromPattern
	call	!SetVolume
	
	call	!KeyOnKeyOff
	call	!PatternLoopJumper
	jmp	!GetDataFromPatternCheckPatternPosition


GetDataFromPatternEntryIsVoid:
	incw	CurrentPatternPointerLo
	mov	y,#ch_instrument
	mov	a,#$ff					;store $ff in all 4 channel input buffers
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	[CurrentChDataLo]+y,a
	ret



GetDataFromPattern:
	mov	y,#ch_instrument
	mov	a,[CurrentChDataLo]+y			;get last rows instrument, pitch and effects
	mov	TempBuffer1,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer5,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer6,a
	or	 TempBuffer1,TempBuffer2
	or	 TempBuffer1,TempBuffer5
	or	 TempBuffer1,TempBuffer6			;check if a note was present
	mov	a,TempBuffer1
	bne	GetDataFromPatternEntryContainsNewData
	call	!GetPitchFromBufferAndSet

GetDataFromPatternEntryContainsNewData:
	mov	y,#$00
	mov	a,[CurrentPatternPointerLo]+y
	cmp	a,#$ff			;compare if channel entry is $ff(skip)
	beq	GetDataFromPatternEntryIsVoid

;there is a new note to trigger, copy data from pattern to current channel data buffer:
	
	mov	[CurrentChDataLo]+y,a	;copy instrument
	inc	y
	mov	a,[CurrentPatternPointerLo]+y			;copy note
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,[CurrentPatternPointerLo]+y			;copy effect
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,[CurrentPatternPointerLo]+y			;copy effect data
	mov	[CurrentChDataLo]+y,a
	incw	CurrentPatternPointerLo
	incw	CurrentPatternPointerLo
	incw	CurrentPatternPointerLo
	incw	CurrentPatternPointerLo


	mov	y,#ch_effect
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	mov	TempBuffer1,a
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	and	a,#$f0
	or	a,TempBuffer1
	cmp	a,#$de					;delay sample effect present? if yes, don't trigger sample
	beq DontSetNewSample
	
	call	!SetNewSample
	
DontSetNewSample:
	ret
	
SetNewSample:		
	mov	y,#ch_instrument						;a non-present instrument is indicated by $00
	mov	a,[CurrentChDataLo]+y
	beq	GetDataFromPatternInstrumentIsVoid

;trigger new instrument, fetch sample data for current instrument
	dec	a							;multiply instrument number with 8 to get pointer into sample array
	mov	y,#$08
	mul	ya
	addw	ya,ModFilePointer2Lo				
	movw	TempBuffer1,ya					;tempbuffer1 is now pointer to current sample entry
	mov	y,#$02						;get sample finetune
	mov	a,[TempBuffer1]+y
	mov	y,#ch_finetune						;save to current channel finetune
	mov	[CurrentChDataLo]+y,a				
	mov	y,#$03						;get sample volume
	mov	a,[TempBuffer1]+y
	mov	CurrentVolume,a					;save to current volume and current channel volume
	mov	y,#ch_current_volume
	mov	[CurrentChDataLo]+y,a

GetDataFromPatternInstrumentIsVoid:
	mov	y,#ch_note
	mov	a,[CurrentChDataLo]+y
	cmp	a,#$ff					;a non-present note is indicated by $ff
	beq	GetDataFromPatternPeriodIsVoid

	mov	y,#ch_effect					;check if effect command is $ff
	mov	a,[CurrentChDataLo]+y
	cmp	a,#$ff
	beq	GetDataFromPatternCalculateNewPeriod	;if it is, directly calculate new period (why?)

	and	a,#$0f					;merge effect command and data to test for finetune command
	mov	TempBuffer1,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	and	a,#$f0
	or	 a,TempBuffer1
	xcn	a

	cmp	a,#$e5			;finetune
	beq	FinetuneStep

	mov	y,#ch_effect
	mov	a,[CurrentChDataLo]+y			;get current effect
	and	a,#$0f
	cmp	a,#$03					;check if effect is tone portamento
	beq	GetDataFromPatternPortamento

	cmp	a,#$05					;check if effect is tone portamento and volslide
	beq	GetDataFromPatternPortamento
	bra	GetDataFromPatternCalculateNewPeriod

FinetuneStep:
	call	!Finetune
	bra	GetDataFromPatternCalculateNewPeriod

GetDataFromPatternPortamento:
	call	!TonePortamento
	
	jmp	!GetDataFromPatternCheckEffects


GetDataFromPatternPeriodIsVoid:
	jmp	!GetDataFromPatternCheckEffects

GetDataFromPatternCalculateNewPeriod:
	mov	y,#ch_note
	mov	a,[CurrentChDataLo]+y
	mov	y,#ch_arpeggio_note_buffer
	mov	[CurrentChDataLo]+y,a
	mov	y,a				;put period in y
	push	y
	mov	y,#ch_finetune				
	mov	a,[CurrentChDataLo]+y		;calculate finetune
	mov	y,#$48
	mul	ya
	addw	ya,PeriodTablePointerLo
	movw	CurrentPeriodPointer,ya
	
	pop	y					;get corresponding value in 14bit period table
	mov	a,[CurrentPeriodPointer]+y				;get current period
	mov	TempBuffer1,a
	mov	PeriodBufferLo,a
	inc	y
	mov	a,[CurrentPeriodPointer]+y
	mov	TempBuffer2,a
	mov	PeriodBufferHi,a

;fetch pitch from pitch table. input: period	
	mov	y,TempBuffer2		
	mov	a,TempBuffer1
	addw	ya,TempBuffer1		;multiply by 2
	addw	ya,PitchTablePointerLo	;add table offset	
	movw	TempBuffer1,ya				;move adress to pointer
	mov	y,#0

	mov	a,[TempBuffer1]+y				;get lo pitch byte	
	mov	PitchBufferLo,a			;save new pitch
	inc	y
	mov	a,[TempBuffer1]+y
	mov	PitchBufferHi,a
;fetch pitch end	
	
	mov	y,#ch_current_pitch_lo			;save new pitch to array
	mov	a,PitchBufferLo
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,PitchBufferHi
	mov	[CurrentChDataLo]+y,a
	
	mov	y,#ch_amiga_period_lo			;save new period to array
	mov	a,PeriodBufferLo
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,PeriodBufferHi
	mov	[CurrentChDataLo]+y,a
	mov	y,#ch_vibrato_tremolo_waveform			;check vibrato waveform
	mov	a,[CurrentChDataLo]+y
	cmp	a,#$04
	bne	VibratoRetrigger

	mov	y,#ch_vibrato_position			;reset vibrato position
	mov	a,#$00
	mov	[CurrentChDataLo]+y,a

VibratoRetrigger:
	mov	y,#ch_vibrato_tremolo_waveform
	mov	a,[CurrentChDataLo]+y
	cmp	a,#$40
	bne	VibratoNoWaveformReset

	mov	y,#ch_tremolo_position
	mov	a,#$00
	mov	[CurrentChDataLo]+y,a

VibratoNoWaveformReset:
	mov	a,CurrentChannel
	or	 a,#$02
	mov	x,a
	mov	y,#ch_current_pitch_lo
	mov	a,[CurrentChDataLo]+y
	mov	$f2,x			;set pitch height(hi), dsp reg $x2
	mov	$f3,a
	inc	y
	inc	x
	mov	a,[CurrentChDataLo]+y
	
	mov	$f2,x			;set pitch height(lo), dsp reg $x3
	mov	$f3,a
	inc	x
	
	mov	y,#ch_instrument			;set source sample number
	mov	a,[CurrentChDataLo]+y
	beq	DontUpdateSourceSampleNumber
	mov	$f2,x
	mov	$f3,a
	inc	x
	mov	$f2,x			;disable adsr, enable gain
	mov	$f3,#$00			;old #$0c
	inc	x

	mov	$f2,x			;clear adsr2
	mov	$f3,#$00			
	inc	x
	inc	x
	mov	y,#ch_current_channelmask
	mov	a,[CurrentChDataLo]+y			;trigger note
	or	 a,KeyOn			;set note in key on var
	mov	KeyOn,a
DontUpdateSourceSampleNumber:
	jmp	!GetDataFromPatternCheckEffects

GetPitchFromBufferAndSet:
	mov	a,CurrentChannel
	or	 a,#$02
	mov	x,a
	mov	y,#ch_current_pitch_lo
	mov	a,[CurrentChDataLo]+y
	mov	$f2,x
	mov	$f3,a
	inc	x
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	$f2,x
	mov	$f3,a
	ret


CheckForTickBasedEffects:
;check if note cutoff is due

	mov	y,#ch_effect
	mov	a,[CurrentChDataLo]+y
	cmp	a,#$ff
	beq	GetPitchFromBufferAndSet
;check for special effects:
	and	a,#$0f
	asl	a
	mov	x,a			;use effect command as pointer into jump table
	jmp	[!TickBasedEffectJumpTable+x]
	
TickBasedEffectJumpTable:	
	.dw TickBasedArpeggio
	.dw TickBasedPortamentoUp
	.dw TickBasedPortamentoDown
	.dw TickBasedTonePortamento
	.dw TickBasedVibrato
	.dw TickBasedVolslidePorta
	.dw VolslideVibrato
	.dw TickBasedTremolo
	.dw TickBasedVoid
	.dw TickBasedVoid
	.dw TickbasedVolslide
	.dw TickBasedVoid
	.dw TickBasedVoid
	.dw TickBasedVoid
	.dw TickBasedExEffects
	.dw TickBasedVoid


TickBasedVoid:
	ret
	
TickBasedExEffects:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y		;get ex-effect subtype
	and	a,#$f0
	lsr a
	lsr a
	lsr a
	mov	x,a			;use effect command as pointer into jump table
	jmp	[!TickBasedExEffectJumpTable+x]

TickBasedExEffectJumpTable:
	.dw TickBasedExVoid
	.dw TickBasedExVoid
	.dw TickBasedExVoid
	.dw TickBasedExVoid
	.dw TickBasedExVoid
	.dw TickBasedExVoid
	.dw TickBasedExVoid
	.dw TickBasedExVoid
	.dw TickBasedExVoid
	.dw TickBasedExEffectNoteRetrigger
	.dw TickBasedExVoid
	.dw TickBasedExVoid
	.dw TickBasedExEffectNoteCutOff
	.dw TickBasedExEffectNoteDelay
	.dw TickBasedExVoid
	.dw TickBasedExVoid

TickBasedExVoid:
	ret

;check for special effects commands
GetDataFromPatternCheckEffects:
	mov	y,#ch_effect
	mov	a,[CurrentChDataLo]+y
	cmp	a,#$ff			;no effect present?
	beq	GetPitchFromBufferAndSetLong

	and	a,#$0f

	asl	a
	mov	x,a			;use effect command as pointer into jump table
	jmp	[!NormalEffectJumpTable+x]
	
NormalEffectJumpTable:
	.dw GetPitchFromBufferAndSet
	.dw GetPitchFromBufferAndSet
	.dw GetPitchFromBufferAndSet
	.dw GetPitchFromBufferAndSet
	.dw GetPitchFromBufferAndSet
	.dw GetPitchFromBufferAndSet
	.dw GetPitchFromBufferAndSet
	.dw GetPitchFromBufferAndSet
	.dw GetPitchFromBufferAndSet
	.dw SetSampleOffset
	.dw GetPitchFromBufferAndSet
	.dw PositionJump
	.dw EffectSetVolume
	.dw PatternBreak
	.dw GetDataFromPatternCheckExEffects
	.dw SetSpeed0x0F

GetPitchFromBufferAndSetLong:
	jmp	!GetPitchFromBufferAndSet
	
;these are the special $Ex effects
GetDataFromPatternCheckExEffects:

	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	and	a,#$f0
	mov	y,a			;save a
	xcn	a
	asl	a
	mov	x,a			;pointer into jump table

	jmp	[!ExEffectJumpTable+x]
	
ExEffectJumpTable:
	.dw ExEffectSpecialReportCmd
	.dw FinePortamentoUp
	.dw FinePortamentoDown
	.dw ExEffectVoid	
	.dw SetVibratoWaveform
	.dw Finetune
	.dw PatternLoop
	.dw ExEffectVoid
	.dw ExEffectVoid
	.dw EffectNoteRetrigger
	.dw FineVolSlideUp
	.dw FineVolSlideDown
	.dw EffectNoteCutOff
	.dw EffectNoteDelay
	.dw ExEffectVoid
	.dw ExEffectVoid
	

ExEffectVoid:
	ret
	

TickBasedArpeggio:
	mov	y,#ch_finetune				;calculate finetune
	mov	a,[CurrentChDataLo]+y
	mov	y,#$48
	mul	ya
	addw	ya,PeriodTablePointerLo
	movw	CurrentPeriodPointer,ya
	
	mov	y,#ch_current_pitch_lo
	mov	a,[CurrentChDataLo]+y		;save current pitch to buffer
	mov	PitchBufferLo,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	PitchBufferHi,a
	jmp	!ArpeggioToneSelector

TickBasedVolslidePorta:
	call	!TonePortamentoContinue
	jmp	!TickbasedVolslide
VolslideVibrato:
	call	!ContinueVibrato
	jmp	!TickbasedVolslide




ArpeggioToneSelector:
	mov	a,WaitCounter		;get waitcounter
	mov	y,#0			;clear y
	mov	x,#3			;put 3 into x
	div	ya,x			;divide waitcounter by three
	
	mov	TempBuffer1,y		;put remainder(can take values 0-2) into TempBuffer1
	
	cmp	TempBuffer1,#$01
	beq	ArpeggioTone1

	cmp	TempBuffer1,#$02
	beq	ArpeggioTone2

	cmp	TempBuffer1,#$00
	beq	ArpeggioTone3
	
		
	ret

ArpeggioTone1:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	xcn	a
	and	a,#$0f
	bra	ArpeggioCommit

ArpeggioTone2:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	bra	ArpeggioCommit

ArpeggioTone3:
	mov	y,#ch_current_pitch_lo				;use current tone as pitch, unchanged
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer1,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a
	bra	ArpeggioSetPitch

ArpeggioCommit:
	asl	a
	mov	TempBuffer1,a
	mov	y,#ch_arpeggio_note_buffer
	mov	a,[CurrentChDataLo]+y
	clrc
	adc	a,TempBuffer1
	mov	y,a

	mov	a,[CurrentPeriodPointer]+y				;get current period
	mov	ScratchPad1,a
	inc	y
	mov	a,[CurrentPeriodPointer]+y
	mov	ScratchPad2,a
	
	mov	y,ScratchPad2		
	mov	a,ScratchPad1
	addw	ya,ScratchPad1		;multiply by 2
	addw	ya,PitchTablePointerLo	;add table offset	
	movw	ScratchPad1,ya				;move adress to pointer
	mov	y,#0

	mov	a,[ScratchPad1]+y				;get lo pitch byte	
	mov	TempBuffer1,a			;save new pitch
	inc	y
	mov	a,[ScratchPad1]+y
	mov	TempBuffer2,a


ArpeggioSetPitch:
	mov	a,CurrentChannel
	or	 a,#$02
	mov	$f2,a
	mov	$f3,TempBuffer1
	inc	a
	mov	$f2,a
	mov	$f3,TempBuffer2
	ret

FinePortamentoUp:
	cmp	WaitCounter,#$00
	bne	PortamentoExit				

	mov	PortamentoSpeedMask,#$0f				;portamento up, but only with lower nibble of effect value

TickBasedPortamentoUp:
	mov	a, #(HighestPortaPeriod & $00ff)
	mov	y, #(HighestPortaPeriod	>> 8)
	movw	TempBuffer5,ya			;put highest slideable period into buffer
	
	mov	y,#ch_amiga_period_lo				;get current period
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer1,a			;and save it to temp buffer
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a

	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y		;get current effect value
	and	a,PortamentoSpeedMask		;and with mask depeding on normal or finetune portamento
	mov	y,#$00				;inverse clear y
	movw	TempBuffer3,ya			;put ya into tempbuffer3
	movw	ya,TempBuffer1			;get current period
	subw	ya,TempBuffer3			;substract portamento speed

	cmpw	ya,TempBuffer5			;if negative, we've slided too high
	bpl	PortaUpNoLimit

	movw	ya,TempBuffer5		;use limit value if we've slided too high

PortaUpNoLimit:	
	movw	TempBuffer1,ya		;put new pitch to buffer

	mov	y,#ch_amiga_period_lo				;save back current period
	mov	a,TempBuffer1
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,TempBuffer2
	mov	[CurrentChDataLo]+y,a
	
	mov	y,TempBuffer2		;get current period	
	mov	a,TempBuffer1
	addw	ya,TempBuffer1		;multiply by 2
	addw	ya,PitchTablePointerLo	;add table offset	
	movw		TempBuffer3,ya				;move adress to pointer
	mov	y,#0

	mov	a,[TempBuffer3]+y				;get lo pitch byte	
	mov	TempBuffer1,a			;save new pitch
	inc	y
	mov	a,[TempBuffer3]+y
	mov	TempBuffer2,a	
	
	
	mov	y,#ch_current_pitch_lo		;save new pitch to current pitch and update pitch register
	mov	a,TempBuffer1
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,TempBuffer2
	mov	[CurrentChDataLo]+y,a
	mov	a,CurrentChannel
	or	 a,#$02
	mov	$f2,a
	mov	$f3,TempBuffer1
	inc	a
	mov	$f2,a
	mov	$f3,TempBuffer2

PortamentoExit:
	mov	PortamentoSpeedMask,#$ff
	ret

FinePortamentoDown:
	cmp	WaitCounter,#$00
	bne	PortamentoExit

	mov	PortamentoSpeedMask,#$0f

TickBasedPortamentoDown:

	mov	a, #(LowestPortaPeriod & $00ff)
	mov	y, #(LowestPortaPeriod	>> 8)
	movw	TempBuffer5,ya			;put lowest slideable period into buffer
	
	mov	y,#ch_amiga_period_lo				;get current period
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer1,a			;and save it to temp buffer
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a

	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y		;get current effect value
	and	a,PortamentoSpeedMask		;and with mask depeding on normal or finetune portamento
	mov	y,#$00				;inverse clear y
	addw	ya,TempBuffer1			;add portamento speed

	cmpw	ya,TempBuffer5			;if positive, we've slided too high
	bmi	PortaDownNoLimit

	movw	ya,TempBuffer5		;use limit value if we've slided too high

PortaDownNoLimit:	
	movw	TempBuffer1,ya		;put new pitch to buffer

	mov	y,#ch_amiga_period_lo				;save back current period
	mov	a,TempBuffer1
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,TempBuffer2
	mov	[CurrentChDataLo]+y,a
	
	mov	y,TempBuffer2		;get current period	
	mov	a,TempBuffer1
	addw	ya,TempBuffer1		;multiply by 2
	addw	ya,PitchTablePointerLo	;add table offset	
	movw		TempBuffer3,ya				;move adress to pointer
	mov	y,#0

	mov	a,[TempBuffer3]+y				;get lo pitch byte	
	mov	TempBuffer1,a			;save new pitch
	inc	y
	mov	a,[TempBuffer3]+y
	mov	TempBuffer2,a
	
		
		
	mov	y,#ch_current_pitch_lo
	mov	a,TempBuffer1
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,TempBuffer2
	mov	[CurrentChDataLo]+y,a
	mov	a,CurrentChannel
	or	 a,#$02
	mov	$f2,a
	mov	$f3,TempBuffer1
	inc	a
	mov	$f2,a
	mov	$f3,TempBuffer2
	ret

TonePortamento:
	mov	y,#ch_note							;get note
	mov	a,[CurrentChDataLo]+y
	mov	y,a				;put period in y
	push	y
	mov	y,#ch_finetune				
	mov	a,[CurrentChDataLo]+y		;calculate finetune
	mov	y,#$48
	mul	ya
	addw	ya,PeriodTablePointerLo
	movw	CurrentPeriodPointer,ya
	
	pop	y					;get corresponding value in 14bit period table
	mov	a,[CurrentPeriodPointer]+y				;get current period
	mov	PeriodBufferLo,a
	inc	y
	mov	a,[CurrentPeriodPointer]+y
	mov	PeriodBufferHi,a

	mov	y,#ch_current_amiga_period_lo							;this is our target period to slide to				
	mov	a,PeriodBufferLo
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,PeriodBufferHi
	mov	[CurrentChDataLo]+y,a

	mov	y,#ch_amiga_period_lo							;get current actual period
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer1,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a	
	
	
	mov	y,#ch_portamento_slide_dir
	mov	a,#$00
	mov	[CurrentChDataLo]+y,a
	movw	ya,PeriodBufferLo
	cmpw	ya,TempBuffer1
	beq	TonePortamentoEqual					;check if already at target period

	bmi	TonePortamentoExit					;if plus, the target period is higher(tone is lower) than the current period. slide value must be added in that case

	mov	y,#ch_portamento_slide_dir							;invert sliding direction
	mov	a,#$01
	mov	[CurrentChDataLo]+y,a

TonePortamentoExit:
	ret

TonePortamentoEqual:
	mov	y,#ch_current_amiga_period_lo
	mov	a,#$00
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	[CurrentChDataLo]+y,a
	ret

TickBasedTonePortamento:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y		;get effect data (tone portamento speed value)
	beq	TonePortamentoContinue		;if zero, continue and dont alter period

	mov	y,#ch_tone_portamento_speed				;save speed
	mov	[CurrentChDataLo]+y,a
	mov	y,#ch_effect_data				;clear effect data
	mov	a,#$00
	mov	[CurrentChDataLo]+y,a

TonePortamentoContinue:
	mov	y,#ch_current_amiga_period_lo				;get target period, put into tempbuffer1
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer1,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a
	
	
	movw	ya,TempBuffer1			;exit if target period is zero (set before if target period has already been reached
	beq	TonePortamentoExit
	mov	y,#ch_tone_portamento_speed
	mov	a,[CurrentChDataLo]+y		;get current speed value (tone portamento speed)
	mov	y,#$00				;clear y
	movw	TempBuffer3,ya			;put slide amount into tempbuffer3
	
	mov	y,#ch_portamento_slide_dir
	mov	a,[CurrentChDataLo]+y
	bne	TonePortaSlideDown		;check whether to slide up or down

	mov	y,#ch_amiga_period_lo				;get current period, put into TempBuffer5
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer5,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer6,a
	
	movw	ya,TempBuffer5			;get current period
	subw	ya,TempBuffer3			;substract slidespeed
	cmpw	ya,TempBuffer1			;check for overflow
	bpl	TonePortaSlideDone

	mov	y,#ch_current_amiga_period_lo				;if overflow occured, clear tone porta target period
	mov	a,#$00
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	[CurrentChDataLo]+y,a
	movw	ya,TempBuffer1			;and load target period instead
	bra	TonePortaSlideDone

TonePortaSlideDown:
	mov	y,#ch_amiga_period_lo				;get current period, put into TempBuffer5
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer5,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer6,a
	
	movw	ya,TempBuffer5			;get current period
	addw	ya,TempBuffer3			;substract slidespeed
	cmpw	ya,TempBuffer1			;check for overflow
	bmi	TonePortaSlideDone

	mov	y,#ch_current_amiga_period_lo				;if overflow occured, clear tone porta target period
	mov	a,#$00
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	[CurrentChDataLo]+y,a
	movw	ya,TempBuffer1			;and load target period instead
;	bra	TonePortaSlideDone

TonePortaSlideDone:
	movw	TempBuffer1,ya			;save newly calculated period to current period buffer

	mov	y,#ch_amiga_period_lo				;save to current period buffer
	mov	a,TempBuffer1
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,TempBuffer2
	mov	[CurrentChDataLo]+y,a

;fetch pitch from pitch table. input: period	
	movw	ya,TempBuffer1		
	addw	ya,TempBuffer1		;multiply by 2
	addw	ya,PitchTablePointerLo	;add table offset	
	movw	TempBuffer1,ya				;move adress to pointer
	mov	y,#0

	mov	a,[TempBuffer1]+y				;get lo pitch byte	
	mov	TempBuffer5,a			;save new pitch
	inc	y
	mov	a,[TempBuffer1]+y
	mov	TempBuffer6,a


	mov	y,#ch_current_pitch_lo				;save to current pitch buffer
	mov	a,TempBuffer5
	mov	[CurrentChDataLo]+y,a
	inc	y
	mov	a,TempBuffer6
	mov	[CurrentChDataLo]+y,a
	mov	a,CurrentChannel
	or	 a,#$02
	mov	$f2,a				;write to dsp
	mov	$f3,TempBuffer5
	inc	a
	mov	$f2,a
	mov	$f3,TempBuffer6
	ret

TickBasedVibrato:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y		;if speed & depth are zero, continue vibrato without updating em.
	beq	ContinueVibrato

	mov	TempBuffer1,a
	mov	y,#ch_vibrato_command_buffer				;get vibrato speed & depth buffer
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a
	and	TempBuffer1,#$0f
	beq	VibratoSkipDepthUpdate		;dont update depth if its zero

	and	TempBuffer2,#$f0			;update buffer
	or	 TempBuffer2,TempBuffer1

VibratoSkipDepthUpdate:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y		;get vibrato speed
	mov	TempBuffer1,a
	and	TempBuffer1,#$f0
	beq	VibratoSkipSpeedUpdate		;dont upgrade speed if its zero

	and	TempBuffer2,#$0f			;update buffer
	or	 TempBuffer2,TempBuffer1

VibratoSkipSpeedUpdate:
	mov	y,#ch_vibrato_command_buffer
	mov	a,TempBuffer2			;save back updated vibrato speed & depth
	mov	[CurrentChDataLo]+y,a

ContinueVibrato:
	mov	y,#ch_vibrato_position
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a			;get current vibrato position, this is our pointer into the vibrato table
	lsr	TempBuffer2
	lsr	TempBuffer2
	and	TempBuffer2,#$1f			;divide by 4 and limit
	mov	y,#ch_vibrato_tremolo_waveform			
	mov	a,[CurrentChDataLo]+y
	and	a,#$03				;select current waveform
	mov	TempBuffer3,a
	beq	VibratoSelectSine

	asl	TempBuffer2			;multiply table pointer with 8
	asl	TempBuffer2
	asl	TempBuffer2
	cmp	a,#$01
	beq	VibratoSelectRampDown

	mov	TempBuffer3,#$ff			;this is square vibrato
	bra	VibratoSelectSet

VibratoSelectRampDown:
	mov	y,#ch_vibrato_position
	mov	a,[CurrentChDataLo]+y		;get current vibrato position
	bpl	VibratoSet

	mov	TempBuffer3,#$ff
	setc
	sbc	TempBuffer3,TempBuffer2
	bra	VibratoSelectSet

VibratoSet:
	mov	TempBuffer3,TempBuffer2
	bra	VibratoSelectSet

VibratoSelectSine:
	mov	x,TempBuffer2
	mov	a,!VibratoTable+x			;access vibrato table
	mov	TempBuffer3,a

VibratoSelectSet:
	mov	y,#ch_vibrato_command_buffer					
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f				;get current vibrato depth		
	mov	y,TempBuffer3
	mul	ya				;multiply with selected vibrato position
	movw	TempBuffer5,ya
	mov	y,#$07

VibratoDivLoop:
	lsr	TempBuffer6			;divide by 128
	ror	TempBuffer5
	dbnz	y,VibratoDivLoop
	
	mov	y,#ch_amiga_period_lo				;get current period
	mov	a,[CurrentChDataLo]+y
	mov	PitchBufferLo,a
	inc	y
	mov	a,[CurrentChDataLo]+y
	mov	PitchBufferHi,a
	mov	y,#ch_vibrato_position
	mov	a,[CurrentChDataLo]+y		;get vibrato position
	bmi	VibratoPosNegative


	movw	ya,PitchBufferLo
	subw	ya,TempBuffer5
	bra	VibratoUpdatePitch

VibratoPosNegative:
	movw	ya,PitchBufferLo
	addw	ya,TempBuffer5

VibratoUpdatePitch:
	movw	PitchBufferLo,ya			;this is our new period
	addw	ya,PitchBufferLo		;multiply by 2
	addw	ya,PitchTablePointerLo		;add table offset	
	movw	ScratchPad1,ya			;move adress to pointer
	mov	y,#0

	mov	a,[ScratchPad1]+y				;get lo pitch byte	
	mov	PitchBufferLo,a			;save new pitch
	inc	y
	mov	a,[ScratchPad1]+y
	mov	PitchBufferHi,a

	
	mov	a,CurrentChannel			;write pitch to dsp
	or	 a,#$02
	mov	$f2,a
	mov	$f3,PitchBufferLo
	inc	a
	mov	$f2,a
	mov	$f3,PitchBufferHi


	mov	y,#ch_vibrato_command_buffer
	mov	a,[CurrentChDataLo]+y
	lsr	a
	lsr	a
	and	a,#$3c
	mov	TempBuffer1,a
	mov	y,#ch_vibrato_position
	mov	a,[CurrentChDataLo]+y
	clrc
	adc	a,TempBuffer1
	mov	[CurrentChDataLo]+y,a
	ret

TickBasedTremolo:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	beq	TremoloContinue


	mov	TempBuffer1,a
	mov	y,#ch_tremolo_command_buffer
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a
	and	TempBuffer1,#$0f
	beq	TremoloNoNewSpeed

	and	TempBuffer2,#$f0
	or	 TempBuffer2,TempBuffer1

TremoloNoNewSpeed:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer1,a
	and	TempBuffer1,#$f0
	beq	TremoloNoNewDepth

	and	TempBuffer2,#$0f
	or	 TempBuffer2,TempBuffer1

TremoloNoNewDepth:
	mov	y,#ch_tremolo_command_buffer
	mov	a,TempBuffer2
	mov	[CurrentChDataLo]+y,a

TremoloContinue:
	mov	y,#ch_tremolo_position
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer2,a
	lsr	TempBuffer2
	lsr	TempBuffer2
	and	TempBuffer2,#$1f
	mov	y,#ch_vibrato_tremolo_waveform
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer3,a
	lsr	TempBuffer3
	lsr	TempBuffer3
	lsr	TempBuffer3
	lsr	TempBuffer3
	and	TempBuffer3,#$03
	beq	TremolWavSel


	asl	TempBuffer2
	asl	TempBuffer2
	asl	TempBuffer2
	asl	TempBuffer2
	cmp	TempBuffer3,#$01
	beq	TremolWavSel2

	mov	TempBuffer3,#$ff
	bra	TremoloCommit

TremolWavSel2:
	mov	y,#ch_vibrato_position
	mov	a,[CurrentChDataLo]+y
	bpl	TremoloCommit2

	mov	TempBuffer3,#$ff
	setc
	sbc	TempBuffer3,TempBuffer2
	bra	TremoloCommit

TremoloCommit2:
	mov	TempBuffer3,TempBuffer2
	bra	TremoloCommit

TremolWavSel:
	mov	x,TempBuffer2
	mov	a,!VibratoTable+x			;access table #2
	mov	TempBuffer3,a

TremoloCommit:
	mov	y,#ch_tremolo_command_buffer
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	mov	y,TempBuffer3
	mul	ya
	movw	TempBuffer5,ya
	mov	y,#$06

TremolDivLoop:
	lsr	TempBuffer6
	ror	TempBuffer5
	dbnz	y,TremolDivLoop
	
	mov	y,#ch_current_volume
	mov	a,[CurrentChDataLo]+y
	mov	TempBuffer1,a
	mov	y,#ch_tremolo_position
	mov	a,[CurrentChDataLo]+y
	bmi	TremolPosNeg

	clrc
	adc	TempBuffer1,TempBuffer5
	bra	TremolPosPos

TremolPosNeg:
	setc
	sbc	TempBuffer1,TempBuffer5

TremolPosPos:
	bpl	TremolPosRes

	mov	TempBuffer1,#$00

TremolPosRes:
	cmp	TempBuffer1,#$40
	bmi	TremolPosRes2

	mov	TempBuffer1,#$40

TremolPosRes2:
	mov	a,CurrentChannel
	or	 a,#$00
	mov	$f2,a
	mov	$f3,TempBuffer1
	inc	a
	mov	$f2,a
	mov	$f3,TempBuffer1
	mov	y,#ch_tremolo_command_buffer
	mov	a,[CurrentChDataLo]+y
	lsr	a
	lsr	a
	and	a,#$3c
	mov	TempBuffer1,a
	mov	y,#ch_tremolo_position
	mov	a,[CurrentChDataLo]+y
	clrc
	adc	a,TempBuffer1
	mov	[CurrentChDataLo]+y,a
	ret

TickbasedVolslide:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	xcn	a
	and	a,#$0f
	beq	TickbasedVolSlideDown

	mov	TempBuffer1,a

VolSlideUp:
	mov	y,#ch_current_volume
	mov	a,[CurrentChDataLo]+y
	adc	a,TempBuffer1
	cmp	a,#$40
	bmi	VolSlideUpNoWaveformReset

	mov	a,#$40

VolSlideUpNoWaveformReset:
	mov	[CurrentChDataLo]+y,a
	mov	CurrentVolume,a

	call	!SetVolumeVibrato
	
	ret

TickbasedVolSlideDown:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	mov	TempBuffer1,a

VolSlideDown:
	mov	y,#ch_current_volume
	mov	a,[CurrentChDataLo]+y
	sbc	a,TempBuffer1
	bpl	VolSlideDownNoReset

	mov	a,#$00

VolSlideDownNoReset:
	mov	[CurrentChDataLo]+y,a
	mov	CurrentVolume,a
	call	!SetVolumeVibrato


	
	ret

SetSpeed0x0F:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	mov	PatternSpeed,a			;self-modifying code
	dec	a
	mov	NextRowChFadeOutTime,a
	ret

SetSampleOffset:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	mov	x,a
	mov	a,#0
	mov	y,#0
	mov	TempBuffer3,#$90			;512/16*9=288, $120
	mov	TempBuffer4,#$00

SetSampleOffsetCalcLoop:	
	cmp	x,#0
	beq SetSampleOffsetCalcLoopDone

	addw	ya,TempBuffer3
	dec	x
	bra SetSampleOffsetCalcLoop

SetSampleOffsetCalcLoopDone:
	movw	TempBuffer1,ya
			
	mov	y,#ch_instrument
	mov	a,[CurrentChDataLo]+y
	asl	a
	asl	a
	mov	x,a
	mov	a,!SampleSource+x			;create a new sample (#3f) that plays with the modified start position
	mov	TempBuffer5,a
	mov	!(SampleSource+(ExtSample*4)),a
	mov	a,!(SampleSource+1)+x
	mov	TempBuffer6,a
	mov	!(SampleSource+1+(ExtSample*4)),a
	mov	a,!(SampleSource+2)+x
	mov	!(SampleSource+2+(ExtSample*4)),a
	mov	a,!(SampleSource+3)+x
	mov	!(SampleSource+3+(ExtSample*4)),a
	movw	ya,TempBuffer5
	addw	ya,TempBuffer1
	movw	TempBuffer5,ya
	mov	a,TempBuffer5
	mov	!(SampleSource+(ExtSample*4)),a
	mov	a,TempBuffer6
	mov	!(SampleSource+1+(ExtSample*4)),a
	mov	a,CurrentChannel
	or	 a,#$04
	mov	$f2,a
	mov	$f3,#ExtSample
	ret

PositionJump:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	mov	CurrentPatternNumber,a
	mov	x,a
	mov	a,!(ModFile+$fa)+x
	asl	a
	mov	x,a
	mov	a,!(ModFile+$17a)+x
	mov	y,a
	mov	a,!(ModFile+$17b)+x
	addw	ya,InitPatternPointerLo
	movw	CurrentPatternPointerLo,ya
	pop	a							;need to purge the stack a bit first
	pop	a							;else, it would overflow slowly when using the pattern break command constantly
	pop	a
	pop	a
	pop	a
	pop	a

	jmp	!MainLoop


;set channel volume:
EffectSetVolume:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y


	mov	y,#ch_current_volume
	mov	[CurrentChDataLo]+y,a
	
	mov	CurrentVolume,a
	call	!SetVolume
	ret

PatternBreak:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y					;get break row.
	bne	PatternBreakZero						;only breaks to row #0 of next pattern are supported. else dont do anything

	mov	PatternBreakFlag,#1					;set break flag. the actual break gets executed after the current row has been completely processed
PatternBreakZero:
	ret


ExEffectSpecialReportCmd:
	inc SpecialReportCmd		;increase special cmd counter.
	ret
	
SetVibratoWaveform:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	mov	TempBuffer1,a
	mov	y,#ch_vibrato_tremolo_waveform							;update vibrato waveform
	mov	a,[CurrentChDataLo]+y
	and	a,#$f0
	or	 a,TempBuffer1
	mov	[CurrentChDataLo]+y,a
	ret

Finetune:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	mov	y,#ch_finetune						;whats written to chdata $04 here?
	mov	[CurrentChDataLo]+y,a
	ret

FineVolSlideUp:
	mov	a,WaitCounter
	bne	FineVolSlideUpCancel

	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	mov	TempBuffer1,a
	jmp	!VolSlideUp

FineVolSlideUpCancel:
	ret

FineVolSlideDown:
	mov	a,WaitCounter
	bne	FineVolSlideDownCancel

	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	mov	TempBuffer1,a
	jmp	!VolSlideDown

FineVolSlideDownCancel:
	ret

InitVariables:
	mov	WaitCounter,#$00
	mov	a,#$06
	mov	PatternSpeed,a		;self-modifying code
	dec	a
	mov	NextRowChFadeOutTime,a
	mov	LoopCounter,#0
	mov	CurrentPatternNumber,#$00
	mov	PortamentoSpeedMask,#$ff
	mov	a,!(ModFile+$f8)			;$13d3
	mov	NumberOfPatterns,a
	mov	TempBuffer1,#$fc			;initial relative adress of pattern tables
	mov	TempBuffer2,#$01
	movw	ya,ModFilePointerLo
	movw	ModFilePointer2Lo,ya		
	addw	ya,TempBuffer1			
	movw	CurrentPatternPointerLo,ya
	movw	InitPatternPointerLo,ya
	movw	CurrentLoopPointerLo,ya
	mov	a,!(ModFile+$fa)			;load initial pattern.
	asl	a
	mov	x,a
	mov	a,!(ModFile+$17a)+x			;get pointer to pattern
	mov	y,a
	mov	a,!(ModFile+$17b)+x
	addw	ya,InitPatternPointerLo
	movw	CurrentPatternPointerLo,ya

	mov	a,#(PeriodTable & $00ff)			;set pointer to amiga period table
	mov	PeriodTablePointerLo,a
	mov	a,#(PeriodTable >> 8)
	mov	PeriodTablePointerHi,a

	mov	a,#(PitchTable & $00ff)			;set pointer to snes pitch table
	mov	PitchTablePointerLo,a
	mov	a,#(PitchTable >> 8)
	mov	PitchTablePointerHi,a
	mov	PatternBreakFlag,#0

	mov	SpecialReportCmd,#0

	ret

PortamentoDownLimit:
	.dw $01e3

;set reset some dsp registers:
SetupDspRegsAndTimers:
	mov	$f1,#$00							;stop timers
	mov		a,$fd									;clear 4bit counters
	mov		a,$fe									;clear 4bit counters
	mov		a,$ff									;clear 4bit counters
	mov	SongChannelMask,#$0f		;enable all 4 mod channels
	mov	ModPlayerTimer0,#$a0			
	mov	$fa,ModPlayerTimer0			;20 milliSeconds on timer 0
	mov	$fb,#$10			;2 milliSeconds on timer 1

	nop
	nop
	mov	$f1,#$03			;start timer(s)

	mov	a,#$00			;clear all dsp regs
	mov	x,a

DspRegClearLoop:
	cmp	x,#$6c
	bne	DspRegClearLoopEchoSkip

	mov	$f2,x			;disable echo
	mov	$f3,#$20
	inc	x
	
DspRegClearLoopEchoSkip:	
	mov	$f2,x
	mov	$f3,a
	inc	x
	bpl	DspRegClearLoop

	mov	$f2,#$5d			;setup dsp regs
	mov	$f3,#(SampleSource >> 8)			;this is the sample info loaction (dir)
	mov	$f2,#$6c
	mov	$f3,#$20
	mov	$f2,#$0c			;set master volume
	mov	$f3,MainVolL
	mov	$f2,#$1c
	mov	$f3,MainVolR
	mov	$f2,#$3d
	mov	$f3,#$00
	mov	$f2,#$7c
	mov	$f3,a
;setup echo stuff:

;set filter:
	mov	$f2,#$0f			;fir
	mov	$f3,#$30
	mov	$f2,#$1f			;fir
	mov	$f3,#$00
	mov	$f2,#$2f			;fir
	mov	$f3,#$00
	mov	$f2,#$3f			;fir
	mov	$f3,#$00
	mov	$f2,#$4f			;fir
	mov	$f3,#$00
	mov	$f2,#$5f			;fir
	mov	$f3,#$00
	mov	$f2,#$6f			;fir
	mov	$f3,#$20
	mov	$f2,#$7f			;fir
	mov	$f3,#$30


;setup adsr on channels 1-4:	
	mov	a,CurrentChannel
	push	a
	mov	CurrentChannel,#$00		;channel 1
	call	!SetupAdsr
	mov	CurrentChannel,#$10		;channel 2
	call	!SetupAdsr
	mov	CurrentChannel,#$20		;channel 3
	call	!SetupAdsr
	mov	CurrentChannel,#$30		;channel 4
	call	!SetupAdsr

	pop	a


	ret
	
SetupAdsr:
	mov	a,#$05			;dsp reg $x5 (adsr1)
	or	a,CurrentChannel			;or with channel number
	mov	x,a
	
	mov	$f2,x			;set adsr 1 (winter gold example: $dc) new value: $cb (slower attack an decay)
	mov	$f3,#$0e			;old #$0c
	inc	x
	mov	$f2,x			;set adsr 2 (winter gold example: $2d) new value: $36 (longer release)
	mov	$f3,#$2d			;old #$2d
	inc	x

	mov	a,x
	and	a,#$f0			;only get channel, not register
	mov	x,a
	mov	a,#$40
	ret	


SetupSamples:
	mov	x,#$00
	mov	y,#$00

SetupSamplesLoop:
	inc	y
	mov	a,[ModFilePointerLo]+y		;get sample relative adress low
	dec	y
	clrc
	adc	a,ModFilePointerLo		;add modfile position
	mov		!(SampleSource+4)+x,a		;store in dsp sample area
	mov	a,[ModFilePointerLo]+y		;get sample relative adress hi
	adc	a,ModFilePointerHi		;add modfile position, with carry from previous add
	mov	!(SampleSource+5)+x,a		;store in dsp sample area
	inc	y
	inc	y
	inc	y
	inc	y
;repeat the same with loop adress:	
	inc	y
	mov	a,[ModFilePointerLo]+y		;get sample relative adress low
	dec	y
	clrc
	adc	a,ModFilePointerLo		;add modfile position
	mov		!(SampleSource+6)+x,a		;store in dsp sample area
	mov	a,[ModFilePointerLo]+y		;get sample relative adress hi
	adc	a,ModFilePointerHi		;add modfile position, with carry from previous add
	mov	!(SampleSource+7)+x,a		;store in dsp sample area
	inc	y
	inc	y
	inc	y
	inc	y
	inc	x
	inc	x
	inc	x
	inc	x
	cmp	x,#$7c
	bne	SetupSamplesLoop
	
	ret
	
;old:
	clrc
	mov	a,!(ModFile+1)+x		;get sample location $12dc
	adc	a,#(ModFile & $00ff)		;add #$12db
	mov	!(SampleSource+4)+y,a		;store in dsp sample area
	mov	a,!ModFile+x
	adc	a,#(ModFile >> 8)
	mov	!(SampleSource+5)+y,a
	inc	x
	inc	x
	inc	x
	inc	x
	clrc
	mov	a,!(ModFile+1)+x
	adc	a,#(ModFile & $00ff)
	mov	!(SampleSource+6)+y,a
	mov	a,!ModFile+x
	adc	a,#(ModFile >> 8)
	mov	!(SampleSource+7)+y,a
	inc	x
	inc	x
	inc	x
	inc	x
	inc	y
	inc	y
	inc	y
	inc	y
	cmp	y,#$7c
	bne	SetupSamplesLoop



	ret

;setup stream sample adress, sample #33

SetupStreamSampleAdress:
	mov	a,#SpcStreamSampleNumber	;get number of sample to use
	asl	a				;multiply by 4
	asl	a
	mov	y,a				;pointer into sample source table
	
	mov	a,#(StreamBuffer & $00ff)		;get 1st stream buffer adress
	mov	!(SampleSource)+y,a		;store in dsp sample area
	mov	!(SampleSource+2)+y,a
	mov	a,#(StreamBuffer >> 8)
	mov	!(SampleSource+1)+y,a
	mov	!(SampleSource+3)+y,a
	ret
	
KeyOnKeyOff:
	mov	y,#0
	mov	a,#$5c
	movw	$f2,ya
	mov	$f2,#$4c			
	mov	a,KeyOn
	and	a,SongChannelMask
	mov	$f3,a
	ret

CutOffChannelGain:
	mov	a,#$07				;select GAIN dsp reg
	or	a,CurrentChannel		;
	mov	$f2,a			;set gain	(winter gold example: $3d)
	mov	$f3,#%10011111		;decrease volume, 8mS	;old #%10111111
	mov	y,#ch_current_volume
	mov	a,#0						;clear volume
	mov	[CurrentChDataLo]+y,a
	ret					;return





SetVolumeVibrato:
;set channel mod volume
	mov	a,#$07				;select GAIN dsp reg
	or	a,CurrentChannel		;
	mov	$f2,a				;set gain
	mov	a,CurrentVolume
	and	a,#$7f				;set direct gain mode
	mov	$f3,a	

;set channel global volume
	mov	a,CurrentChannel					;check active channel
	cmp a,#0
	beq SetVolumeLeftCh
	cmp a,#$20
	beq SetVolumeLeftCh

	mov	y,MusicVol1
	movw	$f2,ya
	inc	a
	mov	y,MusicVol2
	movw	$f2,ya	
	ret


SetVolume:
;set channel mod volume
	mov	a,#$07				;select GAIN dsp reg
	or	a,CurrentChannel		;
	mov	$f2,a				;set gain
	mov	y,#ch_current_volume
	mov	a,[CurrentChDataLo]+y	
	and	a,#$7f				;set direct gain mode
	mov	$f3,a	

;set channel global volume
	mov	a,CurrentChannel					;check active channel
	cmp a,#0
	beq SetVolumeLeftCh
	cmp a,#$20
	beq SetVolumeLeftCh

	mov	y,MusicVol1
	movw	$f2,ya
	inc	a
	mov	y,MusicVol2
	movw	$f2,ya	
	ret


SetVolumeLeftCh:
	mov	y,MusicVol2
	movw	$f2,ya
	inc	a
	mov	y,MusicVol1
	movw	$f2,ya	
	ret
		
	mov	a,CurrentChannel					;save current volume
	mov	$f2,a
	mov	a,$f3
	mov	TempVol,a
	
SetVolumeLoop:
	cmp	CurrentVolume,TempVol						;check if bigger or smaller
	beq	SetVolumeExit
	bmi	SetVolumeDec							;branch if old value is bigger than new

	inc	TempVol
	mov	a,CurrentChannel					;check active channel
	mov	y,TempVol
	movw	$f2,ya
	inc	a
	movw	$f2,ya
	
	bra	SetVolumeLoop

SetVolumeDec:
	dec	TempVol	
	mov	a,CurrentChannel					;check active channel
	mov	y,TempVol
	movw	$f2,ya
	inc	a
	movw	$f2,ya	
	bra	SetVolumeLoop


SetVolumeExit:

	ret




	

PreTestRow:
	mov	y,#$00
	mov	NextRowChFadeOuts,#$00
	mov	CurrentChannel,#$01
	call	!PreTestChannel
	mov	CurrentChannel,#$02
	call	!PreTestChannel
	mov	CurrentChannel,#$04
	call	!PreTestChannel
	mov	CurrentChannel,#$08
	jmp	!PreTestChannel
	
	
PreTestChannel:
	mov	a,[CurrentPatternPointerLo]+y
	cmp	a,#$00
	beq	PreTestChannelIsEffectOnly
	cmp	a,#$ff			;compare if channel entry is $ff(skip)
	beq	PreTestChannelIsVoid
	
	inc	y				;check if a period is present
	mov	a,[CurrentPatternPointerLo]+y
	cmp	a,#$ff
	beq	PreTestChannelIsEffectOnly2
	
	inc	y
	mov	a,[CurrentPatternPointerLo]+y
	cmp	a,#3				;check if tone portamento (which doesnt actually play a new note, so the old one must not be muted)
	beq	PreTestChannelIsEffectOnly3

	and	a,#$0f
	mov	TempBuffer1,a
	inc	y
	mov	a,[CurrentPatternPointerLo]+y
	dec y
	and	a,#$f0
	or	a,TempBuffer1
	cmp	a,#$de					;delay sample effect present? if yes, don't trigger sample
	beq PreTestChannelIsEffectOnly3
	
	mov	a,NextRowChFadeOuts
	or	a,CurrentChannel	;if theres a note in the next row on this channel, set flag to fade it out before the next row gets processed
	mov	NextRowChFadeOuts,a
	bra	PreTestChannelIsEffectOnly3

PreTestChannelIsEffectOnly:	
	inc	y				;if theres a note, the pattern data entry is 4 bytes long
PreTestChannelIsEffectOnly2:
	inc	y
PreTestChannelIsEffectOnly3:	
	inc	y

PreTestChannelIsVoid:
	inc	y
	ret

	
PatternLoop:
;get effect value
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y		;get current effect value
	and	a,#$0f				;and $0f
	bne	PatternLoopDontSetLoopPoint

;effect value is 0, set loop point:
	movw	ya,CurrentRowPointerLo	
	movw	CurrentLoopPointerLo,ya
	ret

;effect value is not 0, check if we are in an active loop
PatternLoopDontSetLoopPoint:
	mov	a,LoopCounter			;check if loop is in progress, must be reset by loop jumper after loop repeat has been finished.
	and	a,#$20
	bne	PatternLoopDontSetLoopPointButLoop
;we arent in a loop, set loop count:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y		;get current effect value
	and	a,#$0f				;and $0f
	or	a,#$30				;set "in loop"-flag and "do jump"-flag
	mov	LoopCounter,a
	ret

PatternLoopDontSetLoopPointButLoop:
;we are in a loop, just jump:
	mov	a,LoopCounter
	and	a,#$0f
	dec	a
	mov	LoopCounter,a
	beq	PatternLoopFinished

;do another loop jump
	or	LoopCounter,#$30
	
PatternLoopFinished:
	ret


PatternLoopJumper:
	mov	a,LoopCounter
	and	a,#%00010000
	beq	PatternLoopJumperNoJump

	movw	ya,CurrentLoopPointerLo
	movw	CurrentPatternPointerLo,ya
	mov	a,LoopCounter		
	and	a,#%11101111
	mov	LoopCounter,a

PatternLoopJumperNoJump:
	ret

EffectNoteRetrigger:
EffectNoteDelay:
EffectNoteCutOff:
	mov	y,#ch_effect_data
	mov	a,[CurrentChDataLo]+y		;get current effect value
	and	a,#$0f				;and $0f
	beq EffectNoteCutOffImmediately

	dec a

EffectNoteCutOffImmediately:
	mov	y,#ch_tick_delay
	mov	[CurrentChDataLo]+y,a		;write to buffer
	ret

TickBasedExEffectNoteCutOff:
	mov	y,#ch_tick_delay	
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	bne	CheckForTickBasedEffectsNoteCutOffDec
	

	mov	[CurrentChDataLo]+y,a
	call	!CutOffChannelGain
	
	mov	y,#ch_current_channelmask
	mov	a,[CurrentChDataLo]+y			;disable channel in keyon reg
	eor	a,#$ff
	mov	ScratchPad1,a
	and	KeyOn,ScratchPad1

	mov	CurrentVolume,#$00
	call	!SetVolume

	mov	y,#ch_effect_data					;clear effect data so this routine isnt triggered on successive ticks of this frame
	mov	a,#0
	mov	[CurrentChDataLo]+y,a
	
	ret


CheckForTickBasedEffectsNoteCutOffDec:
	dec a
	mov	[CurrentChDataLo]+y,a
	
;CheckForTickBasedEffectsNoNoteCutOff:
	ret	

TickBasedExEffectNoteRetrigger:
TickBasedExEffectNoteDelay:
	mov	y,#ch_tick_delay
	mov	a,[CurrentChDataLo]+y
	and	a,#$0f
	bne	CheckForTickBasedEffectsNoteDelayDec
	
	call	!SetNewSample

	mov	y,#ch_current_channelmask
	mov	a,[CurrentChDataLo]+y			;enable channel in keyon reg
	or	a,TickSampleStart
	mov	TickSampleStart,a

	mov	y,#ch_effect_data					;clear effect data so this routine isnt triggered on successive ticks of this frame
	mov	a,#0
	mov	[CurrentChDataLo]+y,a
	ret

CheckForTickBasedEffectsNoteDelayDec:
	dec a
	mov	[CurrentChDataLo]+y,a
	ret		

SetEchoAndVolume:
	mov	$f2,#$2c			;echo volume l
	mov	$f3,#$00			;0d
	mov	$f2,#$3c			;echo volume r
	mov	$f3,#$00			;0d
	mov	$f2,#$4d			;echo channel enable
	mov	$f3,#$00			;ff
	ret

ReceiveSongHandler:
	mov	$f2,#$0c
	mov	$f3,#$00
	mov	$f2,#$1c
	mov	$f3,#$00
	mov	$f2,#$2c
	mov	$f3,#$00
	mov	$f2,#$3c
	mov	$f3,#$00
	mov	$f2,#$6c			;disable echo
	mov	$f3,#$20			

	
	
	mov	a,#SpcCmdUploadSong			;set "upload song command received"-flag	so cpu knows we're ready
	mov	$f4,a
	mov	a,#$00			;clear remaining ports
	mov	$f5,a
	mov	$f6,a
	mov	$f7,a	

	mov	SpcDataPointerLo,#(ModFile & $00ff)		;setup mod pointer
	mov	SpcDataPointerHi,#(ModFile >> 8)
	mov	ModFilePointerLo,#(ModFile & $00ff)		;setup mod pointer
	mov	ModFilePointerHi,#(ModFile >> 8)

	mov	y,#$00				;clear mod pointer counter(actually, this counter always stays 0,just needed for indirect write)

ReceiveSongWaitLoop2:				;wait for first transfer
	cmp	$f4,#SpcCmdUploadSongDone
	beq	ReceiveSongComplete		;check if upload complete
	
	cmp	$f4,#SpcCmdUploadSongT1			;check if data for transfer 1 is stable and on ports
	bne	ReceiveSongWaitLoop2

	mov	a,$f5				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word
	mov	a,$f6				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word
	mov	a,$f7				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word

	mov	$f4,#SpcCmdUploadSongT1
	
ReceiveSongWaitLoop3:				;wait for second transfer
	cmp	$f4,#SpcCmdUploadSongT2
	bne	ReceiveSongWaitLoop3

	mov	a,$f5				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word
	mov	a,$f6				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word
	mov	a,$f7				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word

	mov	$f4,#SpcCmdUploadSongT2
	bra	ReceiveSongWaitLoop2

ReceiveSongComplete:
	mov	a,#$00			;clear apu ports so that cpu knows were ready
	mov	$f4,a
	mov	$f5,a
	mov	$f6,a
	mov	$f7,a

	mov	ModTimecode,#0					;clear Timecode
	mov	ModTimecode+1,#0

	mov	SongEffectsUploaded,#%00000001			;set "song uploaded" flag, clear "sample pack uploaded" flag
	ret

ReceiveSamplePack:
	mov	a,#SpcCmdUploadSamplePack			;set "upload song command received"-flag	so cpu knows we're ready
	mov	$f4,a
	mov	a,#$00			;clear remaining ports
	mov	$f5,a
	mov	$f6,a
	mov	$f7,a
	
	mov	$f2,#$5c			;key off all channels
	mov	$f3,#%11111111
	
	movw	ya,SpcDataPointerLo		;save offset
	movw	TempBuffer1,ya

	mov	y,#$00				;clear mod pointer counter(actually, this counter always stays 0,just needed for indirect write)

ReceiveSamplePackWaitLoop2:				;wait for first transfer
	cmp	$f4,#SpcCmdUploadSamplePackDone
	beq	ReceiveSamplePackComplete		;check if upload complete
	
	cmp	$f4,#SpcCmdUploadSongT1			;check if data for transfer 1 is stable and on ports
	bne	ReceiveSamplePackWaitLoop2

	mov	a,$f5				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word
	mov	a,$f6				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word
	mov	a,$f7				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word

	mov	$f4,#SpcCmdUploadSongT1
	
ReceiveSamplePackWaitLoop3:				;wait for second transfer
	cmp	$f4,#SpcCmdUploadSongT2
	bne	ReceiveSamplePackWaitLoop3

	mov	a,$f5				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word
	mov	a,$f6				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word
	mov	a,$f7				;get byte
	mov	[SpcDataPointerLo]+y,a
	incw	SpcDataPointerLo				;increment counter, word

	mov	$f4,#SpcCmdUploadSongT2
	bra	ReceiveSamplePackWaitLoop2

ReceiveSamplePackComplete:
	mov	a,#$00			;clear apu ports so that cpu knows were ready
	mov	$f4,a
	mov	$f5,a
	mov	$f6,a
	mov	$f7,a
	movw	ya,TempBuffer1					;restore saved pointer
	movw	SpcDataPointerLo,ya
	
	or	SongEffectsUploaded,#%00000010			;set "sample pack uploaded" flag
	call	!SetupEffectSamples
;return to mainloop:
	jmp	!MainLoop


SetupEffectSamples:
	mov	x,#34*4				;start copying to 34th dsp sample
	mov	y,#$00
	mov	a,[SpcDataPointerLo]+y		;get number of sample in pack
	and	a,#$1f				;maximum number of samples 31
	mov	TempBuffer1,a			;this is our number of samples to process
	inc	y				;advance to first byte pointer

SetupEffectSamplesLoop:

;sample adress:
	mov	a,[SpcDataPointerLo]+y		;get sample relative adress low
	inc	y
	clrc
	adc	a,SpcDataPointerLo		;add modfile position
	mov		!(SampleSource+0)+x,a		;store in dsp sample area
	mov	a,[SpcDataPointerLo]+y		;get sample relative adress hi
	adc	a,SpcDataPointerHi		;add modfile position, with carry from previous add
	dec	y
	mov	!(SampleSource+1)+x,a		;store in dsp sample area
	inc	y
	inc	y
;sample loop adress:	
	mov	a,[SpcDataPointerLo]+y		;get sample relative adress low
	inc	y
	clrc
	adc	a,SpcDataPointerLo		;add modfile position
	mov		!(SampleSource+2)+x,a		;store in dsp sample area
	
	
	mov	a,[SpcDataPointerLo]+y		;get sample relative adress hi
	adc	a,SpcDataPointerHi		;add modfile position, with carry from previous add
	dec	y
	mov	!(SampleSource+3)+x,a		;store in dsp sample area

	mov	a,y				;increment source pointer by 15 (entry is 16 bytes long)
	clrc
	adc	a,#14
	mov	y,a
	
	inc	x
	inc	x
	inc	x
	inc	x
	dbnz	TempBuffer1,SetupEffectSamplesLoop
	
	ret

PlaySoundEffectAbort:
	jmp	!MainLoop

PlaySoundEffect:
	movw	ya,$f4				;save all 4 ports to variables
	movw	TempBuffer1,ya			;tempbuffer1:command,tb2:se number,tb3-4:volume/pitch
	movw	ya,$f6
	movw	TempBuffer3,ya
	
	mov	$f4,#SpcCmdPlaySoundEffect	;echo command received


	

	bbc	$ef.1,PlaySoundEffectAbort		;only process song if song has been uploaded before

	mov	a,TempBuffer4
	and	a,#$80				;get flip flag
	eor	a,SoundEffectFlipFlag
	bne	PlaySoundEffectAbort		;only play effect if this flag has changed as expected
	
	eor	SoundEffectFlipFlag,#$80	;toggle flag for next soundeffect

	mov	y,#0
	mov	a,[SpcDataPointerLo]+y		;get number of samples in pack
	mov	TempBuffer5,a
	mov	a,TempBuffer2
	
	cmp	a,TempBuffer5
	bcs	PlaySoundEffectAbort		;return if sound effect to play is bigger than number of samples in pack


	mov	a,ActiveSoundEffectCh
	inc	a				;goto next channel
	and	a,#3				;maximum number of channels is 3
	mov	ActiveSoundEffectCh,a		;save back
	mov	x,a
	mov	a,!SoundEffectChannelDspRegLUT+x	;get channel value for dsp access
	mov	TempBuffer5,a

	mov	a,!SoundEffectChannelKofLUT+x	;get channel key off bit
	mov	TempBuffer6,a
	mov	$f2,#$5c				;key off current channel
	mov	$f3,a

	
;wait 8ms for key off to finish
	mov	a,$fe	;clear 4bit counter
	mov	y,#4
KofWaitLoop2:
	mov	a,$fe				;wait for 2mS
	beq	KofWaitLoop2
	dbnz	y,KofWaitLoop2

	mov	x,TempBuffer5			;get pointer to dsp channel register set
	
	mov	a,TempBuffer2			;get sample number
	asl	a				;multiply by 16
	asl	a
	asl	a
	asl	a
	inc	a				;increment because first byte in sample pack is number of samples
	mov	y,a
	
	inc	y
	inc	y
	inc	y
	inc	y
	
	mov	a,TempBuffer4			;get 65816 command volume
	and	a,#$7f				;mask off flip flag
	beq	PlaySoundEffectDefaultVol	

;play SE with custom volume
	and	a,#%0001111			;get volume
	mov	TempBuffer7,a
	clrc
	asl	a
	asl	a
	asl	a
	and	TempBuffer7,#%00001111		;add lower bits to get maximum range
	or	a,TempBuffer7
	mov	TempBuffer7,a

	call	!SoundEffectCalcPanL

	mov	$f2,x
	mov	$f3,a
	inc	y
	inc	x
	call	!SoundEffectCalcPanR
	mov	$f2,x
	mov	$f3,a
	bra 	PlaySoundEffectCustomVol

PlaySoundEffectDefaultVol:	
	mov	a,[SpcDataPointerLo]+y		;get volume l
	
	mov	$f2,x
	mov	$f3,a
	inc	y
	inc	x
	mov	a,[SpcDataPointerLo]+y		;get volume r
	
	mov	$f2,x
	mov	$f3,a

PlaySoundEffectCustomVol:
	inc	y
	inc	x

	mov	a,TempBuffer3			;get 65816 command pitch
	beq	PlaySoundEffectDefaultPitch

;play custom pitch. same as mod pitch numbers. valid pitch numbers: 0-59
	push	y
;**************************************************
	asl	a			;asl because each period is 2 bytes long
	mov	y,a				;get period number
	mov	a,[PeriodTablePointerLo]+y				;get current period
	mov	TempBuffer7,a
	inc	y
	mov	a,[PeriodTablePointerLo]+y
	mov	TempBuffer8,a


;fetch pitch from pitch table. input: period	
	mov	y,TempBuffer8		
	mov	a,TempBuffer7
	addw	ya,TempBuffer7		;multiply by 2
	addw	ya,PitchTablePointerLo	;add table offset	
	movw	TempBuffer7,ya				;move adress to pointer
	mov	y,#0

	mov	a,[TempBuffer7]+y				;get lo pitch byte	
	mov	PitchBufferLo,a			;save new pitch
	inc	y
	mov	a,[TempBuffer7]+y
	mov	PitchBufferHi,a
	pop	y
	mov	a,PitchBufferLo		;get pitch lo
	mov	$f2,x
	mov	$f3,a
	inc	y
	inc	x
	mov	a,PitchBufferHi		;get pitch hi
	mov	$f2,x
	mov	$f3,a
	bra	PlaySoundEffectCustomPitch
	
;**********************************************+


	
PlaySoundEffectDefaultPitch:		
	mov	a,[SpcDataPointerLo]+y		;get pitch lo
	
	mov	$f2,x
	mov	$f3,a
	inc	y
	inc	x
	mov	a,[SpcDataPointerLo]+y		;get pitch hi
	
	mov	$f2,x
	mov	$f3,a

PlaySoundEffectCustomPitch:
	inc	x

	mov	a,TempBuffer2			;get sound effect to play
	clrc
	adc	a,#34				;add 34 (sound effects start at dsp sample 34)
	
	mov	$f2,x
	mov	$f3,a

	inc	y
	inc	x
	mov	a,[SpcDataPointerLo]+y		;get adsr lo
	
	mov	$f2,x
	mov	$f3,a

	inc	y
	inc	x
	mov	a,[SpcDataPointerLo]+y		;get adsr hi
	
	mov	$f2,x
	mov	$f3,a

	inc	y
	inc	x
	mov	a,[SpcDataPointerLo]+y		;get gain
	
	mov	$f2,x
	mov	$f3,a

	mov	$f2,#$5c			;reset key off in case it's still active.(shouldn't, though)
	mov	$f3,#%00000000

	mov	$f2,#$4c			;key on channel
	mov	$f3,TempBuffer6
	
	jmp	!MainLoop

;in: 	TempBuffer4:volume&pan command
;	TempBuffer7:total channel volume
;out:	a,8bit: panned channel volume
;uses:	TempBuffer8

SoundEffectCalcPanL:
	mov	a,TempBuffer4			;get pan bits
	xcn	a				;swap nibbles
						;put pan bits into lower 4 bits. we're skipping lsr a, asl a here because the pointers in the LUT are words.
	and	a,#%00000111			;mask off rest
	push	x
	asl	a
	mov	x,a				;use as pointer
	mov	a,TempBuffer7
	jmp	[!PlaySoundEffectLVolLUT+x]


PlaySoundEffectLVolLUT:
	.dw PlaySoundEffectVolTBL
	.dw PlaySoundEffectVolTBL+1
	.dw PlaySoundEffectVolTBL+2
	.dw PlaySoundEffectVolTBL+3
	.dw PlaySoundEffectVolTBL+4
	.dw PlaySoundEffectVolTBL+5
	.dw PlaySoundEffectVolTBL+6
	.dw PlaySoundEffectVolTBL+7


SoundEffectCalcPanR:
	mov	a,TempBuffer4			;get pan bits
	xcn	a				;swap nibbles
						;put pan bits into lower 4 bits. we're skipping lsr a, asl a here because the pointers in the LUT are words.
	and	a,#%00000111			;mask off rest
	push	x
	asl	a
	mov	x,a				;use as pointer
	mov	a,TempBuffer7
	jmp	[!PlaySoundEffectRVolLUT+x]


PlaySoundEffectRVolLUT:
	.dw PlaySoundEffectVolTBL+7
	.dw PlaySoundEffectVolTBL+6
	.dw PlaySoundEffectVolTBL+5
	.dw PlaySoundEffectVolTBL+4
	.dw PlaySoundEffectVolTBL+3
	.dw PlaySoundEffectVolTBL+2
	.dw PlaySoundEffectVolTBL+1
	.dw PlaySoundEffectVolTBL
			
PlaySoundEffectVolTBL:
	lsr	a
	lsr	a
	lsr	a
	lsr	a
	lsr	a
	lsr	a
	lsr	a
	nop
	mov	TempBuffer8,a			;save
	mov	a,TempBuffer7
	setc
	sbc	a,TempBuffer8			;substract calculated pan value
	pop	x
	ret

ReceiveStream:
	mov	$f2,#$5c			;key off channels 0-3
	mov	$f3,#%00001111
	
	mov	StreamState,#0			;reset transfer state machine
	mov	StreamCurrentBuffer,#0		;reset to first buffer
	
	call	!SetupStreamSampleAdress
	movw	ya,$f5
	movw	TempBuffer1,ya			;get total number of samples to transfer
	mov	a,#0
	mov	y,a
	movw	TempBuffer3,ya			;clear current frame counter
	movw	$f5,ya				;tell 65816 current frame to transfer
	mov	$f4,#SpcCmdReceiveStream

ReceiveStreamWaitLoop1:
	cmp	$f4,#SpcCmdSubmitStreamNumber
	bne	ReceiveStreamWaitLoop1


;wait for desired frame
	movw	ya,$f5
	cmpw	ya,TempBuffer3
	bne	ReceiveStreamRequestFrame

	mov	a,$f7
	mov	StreamVolume,a
	beq	ReceiveStreamExit		;exit streaming if volume is 0
;enter receive state machine
	mov	a,StreamState
	asl 	a
	mov	x,a			;pointer into jump table
	jmp	[!StreamJumpTable+x]
	
ReceiveStreamWaitLoop2:
	cmp	$f4,#SpcCmdSubmitStreamNumber	;wait for port0 data to change. Don't know if this works out as desired, but 
	beq	ReceiveStreamWaitLoop2	
	
		
ReceiveStreamTransferLoop:			;66 cycles per scanline. this loop must be exactly 66 bytes long while looping.
;26 cycles					;59 cycles per scanline left for actual transfer

	movw	ya,$f4				
	push a					;save data on stack cause that's the fastest way possible
	push y
	movw	ya,$f6
	push a
	push y
	
;waste 33 cyles:
;******************************
	mov	a,TempBuffer1			;real snes/bsnes need a total of 33 cycles here
	mov	a,TempBuffer1
	mov	a,TempBuffer1
	mov	a,TempBuffer1
	mov	a,TempBuffer1
	mov	a,TempBuffer1
	mov	a,TempBuffer1
	mov	a,TempBuffer1
	mov	a,TempBuffer1
	mov	a,TempBuffer1
	mov	a,TempBuffer1




;7 cycles when looping back
	dbnz	TempBuffer5,ReceiveStreamTransferLoop

	mov	a,#36
	mov	TempBuffer5,a	

	mov	y,#144-1				;set counter to last byte in buffer(reverse copy because of stack)
	
ReceiveStreamSaveLoop:
	pop a					;dummy loop to fix stack
	mov	[TempBuffer7]+y,a		;7	-move to buffer
	dec	y
	pop a					;dummy loop to fix stack
	mov	[TempBuffer7]+y,a		;7	-move to buffer
	dec	y
	pop a					;dummy loop to fix stack
	mov	[TempBuffer7]+y,a		;7	-move to buffer
	dec	y
	pop a					;dummy loop to fix stack
	mov	[TempBuffer7]+y,a		;7	-move to buffer
	dec	y				
	dbnz	TempBuffer5,ReceiveStreamSaveLoop

	cmp	StreamCurrentBuffer,#SpcStreamLatency
	bne	SpcStreamDontSetEndBit
	

	mov	y,#144-9				;set pointer to last samples header
	mov	a,[TempBuffer7]+y			;get header byte
	or	a,#%00000011				;set loop	& end bits

	mov	[TempBuffer7]+y,a			;save back header byte

SpcStreamDontSetEndBit:	
	incw	TempBuffer3			;request next frame
	inc	StreamCurrentBuffer
	and	StreamCurrentBuffer,#7				;maximum number of buffers: 8
StreamTimerWait:				;wait for 16 brr samples to play / ca. 2 frames to pass, then continue downloading stream
	mov	a,$fd
	beq	StreamTimerWait

ReceiveStreamRequestFrame:
	movw	ya,TempBuffer3
	movw	$f5,ya				;tell 65816 which frame to transfer
	
	movw	ya,TempBuffer1			;check if done transfering
	cmpw	ya,TempBuffer3
	bne	ReceiveStreamWaitLoop1

ReceiveStreamExit:
	mov	$f2,#$5c			;key off channels 0-3
	mov	$f3,#%00000001
	mov	$f4,#SpcCmdReceiveStreamComplete	;tell 65816 we're done

;StreamWaitSampleEnd:
	mov	$f1,#$00			;stop timer
	nop
	nop
	mov	$fa,ModPlayerTimer0			;reset timer, return to modplayer
	
	nop
	nop
	mov	$f1,#$03			;start timer
	
	jmp	!MainLoop				;return


StreamJumpTable:
	.dw	InitStream		;fill buffer, then start playing sample
	.dw	StreamContinue		;fill buffer according to timer

StreamContinue:
;setup current stream buffer
	mov	TempBuffer7,#(StreamBuffer & $00ff)		;pointer to first stream buffer
	mov	TempBuffer8,#(StreamBuffer >> 8)
	mov	a,StreamCurrentBuffer

	mov	y,#144
	mul	ya
	addw	ya,TempBuffer7
	movw	TempBuffer7,ya			;pointer to current stream buffer in TempBuffer7,8

	mov	a,#$00
	mov	y,StreamVolume			;update volume
	movw	$f2,ya
	inc	a
	movw	$f2,ya


	mov	y,#0				;clear target copy counter
	
	mov	a,#36				;amount of scanlines to copy
	mov	TempBuffer5,a
	
	jmp	!ReceiveStreamWaitLoop2

InitStream:
;receive stream here:

;setup current stream buffer
	mov	TempBuffer7,#(StreamBuffer & $00ff)		;pointer to first stream buffer
	mov	TempBuffer8,#(StreamBuffer >> 8)
	mov	a,StreamCurrentBuffer
	and	a,#7				;maximum number of buffers: 8
	mov	y,#144
	mul	ya
	addw	ya,TempBuffer7
	movw	TempBuffer7,ya			;pointer to current stream buffer in TempBuffer7,8


	mov	y,#0				;clear target copy counter
	
	mov	a,#36				;amount of scanlines to copy
	mov	TempBuffer5,a

		
	cmp	StreamCurrentBuffer,#SpcStreamLatency
	bne	SpcStreamDontStartSample




;setup sample dsp regs on channel 1:
;wide stereo effect
	mov	a,#$00
	mov	y,StreamVolume			;volume
	movw	$f2,ya
	inc	a
	movw	$f2,ya
	inc	a
	mov	y,#$00				;pitch lo
	movw	$f2,ya
	inc	a
	mov	y,#$04				;pitch hi
	movw	$f2,ya
	inc	a
	mov	y,#SpcStreamSampleNumber	;sample number
	movw	$f2,ya
	inc	a
	mov	y,#0				;adsr1 (use gain instead)
	movw	$f2,ya
	inc	a
	movw	$f2,ya				;adsr2
	inc	a
	mov	y,#$7f				;volume
	movw	$f2,ya

	mov	$f2,#$5c			;reset key off in case it's still active.(shouldn't, though)
	mov	$f3,#%00000000

	mov	$f2,#$4c			;key on channel 1
	mov	$f3,#%00000001

	mov	$f1,#$00			;stop timer
	nop
	nop
	mov	$fa,#StreamerTimer0			;32 milliSeconds on timer 0
	nop
	nop
	mov	$f1,#$03			;start timer
	inc	StreamState			;goto next state

SpcStreamDontStartSample:		
	jmp	!ReceiveStreamWaitLoop2



UpdateVolEchoHandler:
	mov	ModFilePointerLo,#(MainVolL & $00ff)		;setup mod pointer
	mov	ModFilePointerHi,#(MainVolL >> 8)

	mov	y,#$00				;clear mod pointer counter(actually, this counter always stays 0,just needed for indirect write)
	mov	$f4,#$01


UpdateVolEchoWaitLoop1:	
	cmp	$f4,#$00			;check if another data download is due
	bne	UpdateVolEchoWaitLoop1

	mov	a,$f5				;get byte
	mov	[ModFilePointerLo]+y,a
	incw	ModFilePointerLo				;increment counter, word
	mov	a,$f6				;get byte
	mov	[ModFilePointerLo]+y,a
	incw	ModFilePointerLo				;increment counter, word
	mov	a,$f7				;get byte
	mov	[ModFilePointerLo]+y,a
	incw	ModFilePointerLo				;increment counter, word

	mov	$f4,#$00

UpdateVolEchoWaitLoop2:	
	cmp	$f4,#$01			;check if another data download is due
	bne	UpdateVolEchoWaitLoop2

	mov	a,$f5				;get byte
	mov	[ModFilePointerLo]+y,a
	incw	ModFilePointerLo				;increment counter, word
	mov	a,$f6				;get byte
	mov	[ModFilePointerLo]+y,a
	incw	ModFilePointerLo				;increment counter, word
	mov	a,$f7				;get byte
	mov	[ModFilePointerLo]+y,a
	incw	ModFilePointerLo				;increment counter, word

	mov	a,#$00			;clear apu ports so that cpu knows were ready
	mov	$f4,a
	mov	$f5,a
	mov	$f6,a
	mov	$f7,a
UpdateVolEchoVariablesOnly:	

	mov	$f2,#$6d			;echo start adress
	mov	$f3,#EchoStartAdress			;80
	mov	$f2,#$7d			;echo delay
	mov	$f3,#EchoDelay			;06
	mov	$f2,#$0d			;echo feedback
	mov	$f3,#$04			;04


	mov	$f2,#$0c
	mov	$f3,MainVolL
	mov	$f2,#$1c
	mov	$f3,MainVolR
	mov	$f2,#$2c
	mov	$f3,EchoVolL
	mov	$f2,#$3c
	mov	$f3,EchoVolR
	mov	$f2,#$4d
	mov	$f3,ChannelEchoEnable

	
	ret



;set key on/key off here:
GetDataFromPatternCheckPatternPosition:
;***********************************************	
;check if pattern break is due
	mov	a, PatternBreakFlag					;check if pattern break flag is set
	beq	CheckPatternBreakExit

	inc	CurrentPatternNumber					;increment current pattern
	mov	x,CurrentPatternNumber					;check if we overflow
	cmp	x,NumberOfPatterns
	bne	PatternBreakNoOverflow

	mov	CurrentPatternNumber,#$00					;reset to pattern #0 if overflow occured
	mov	x,CurrentPatternNumber

PatternBreakNoOverflow:
	mov	a,!(ModFile+$fa)+x					;get id number of next pattern
	asl	a
	mov	x,a
	mov	a,!(ModFile+$17a)+x					;get word-pointer to that table
	mov	y,a
	mov	a,!(ModFile+$17b)+x
	addw	ya,InitPatternPointerLo				
	movw	CurrentPatternPointerLo,ya				;update current pattern pointer

	mov	PatternBreakFlag,#0					;clear pattern break flag and exit
	pop	a							;need to purge the stack a bit first
	pop	a							;else, it would overflow slowly when using the pattern break command constantly
	pop	a
	pop	a
	jmp	!MainLoop

CheckPatternBreakExit:

;check if at end of current pattern
	mov	x,CurrentPatternNumber
	mov	a,!(ModFile+$fa)+x		;load pattern number
	inc	a
	asl	a			;multiply, use as pointer into patterntable pointertable list
	mov	x,a
	mov	a,!(ModFile+$17a)+x				;$1455+x	;(ModFile+$17a) ;$17a
	mov	TempBuffer2,a
	mov	a,!(ModFile+$17b)+x				;$1456+x	;17b
	mov	TempBuffer1,a
	movw	ya,CurrentPatternPointerLo
	subw	ya,InitPatternPointerLo
	cmpw	ya,TempBuffer1
	bpl	CheckPatternPositionPatternEnd

	ret

CheckPatternPositionPatternEnd:
	inc	CurrentPatternNumber		;increment position in current pattern
	mov	a,CurrentPatternNumber
	cmp	a,NumberOfPatterns		;check if at end of pattern
	beq	CheckPatternPositionReset

	mov	x,a
	mov	a,!(ModFile+$fa)+x
	asl	a
	mov	x,a
	mov	a,!(ModFile+$17a)+x
	mov	y,a
	mov	a,!(ModFile+$17b)+x
	addw	ya,InitPatternPointerLo
	movw	CurrentPatternPointerLo,ya
	movw	CurrentLoopPointerLo,ya
	ret

CheckPatternPositionReset:				;wrap to pattern 0
	mov	$f6,#$50
	mov	CurrentPatternNumber,#$00
	mov	x,CurrentPatternNumber
	mov	a,!(ModFile+$fa)+x
	asl	a
	mov	x,a
	mov	a,!(ModFile+$17a)+x
	mov	y,a
	mov	a,!(ModFile+$17b)+x
	addw	ya,InitPatternPointerLo
	movw	CurrentPatternPointerLo,ya
	ret
	

ClearAllChannelArray:
	mov	a,#(ChannelArray & $ff)		;get pointer to first channel array
	mov	y,#(ChannelArray	>> 8)
	movw	TempBuffer1,ya
	movw	Channel1Array,ya
	mov	a,#1	
	call	!ClearChannelArray
	
	movw	Channel2Array,ya
	mov	a,#2
	call	!ClearChannelArray

	movw	Channel3Array,ya
	mov	a,#4
	call	!ClearChannelArray

	movw	Channel4Array,ya
	mov	a,#8
	call	!ClearChannelArray		
	ret
	
	
ClearChannelArray:
	mov	y,#ch_current_channelmask	;number of bytes to copy
	mov	[TempBuffer1]+y,a		;put channel mask into array pointer + channel mask value
	decw	TempBuffer1
	mov	a,#0
ClearChannelArrayLoop:
	mov	[TempBuffer1]+y,a		;clear array
	dbnz	y,ClearChannelArrayLoop
	
	incw	TempBuffer1
	mov	a,#ch_current_channelmask+1	;get amount of bytes per array and add to current adress
	mov	y,#0
	addw	ya,TempBuffer1
	movw	TempBuffer1,ya			;save adress of next array
	ret		

CommandVoid:
	jmp	!MainLoop

SetSongChannelMask:
	mov		SongChannelMask,$f5			;get new channel mask
	mov	$f2,#$5c
	mov	a,SongChannelMask
	eor	a,#$0f					;invert, switch off masked channels immediately
	mov	$f3,a					;key off 
	mov	$f4,#SpcCmdSetSongChMask		;ack
	jmp	!MainLoop				;return


SetSongSpeed:
	mov	ModPlayerTimer0,$f5			;get new speed
	
	mov	$f1,#$00
	nop
	nop
	mov	$fa,ModPlayerTimer0			;20 milliSeconds on timer 0
	mov	$fb,#$10			;2 milliSeconds on timer 1
	nop
	nop
	mov	$f1,#$03			;start timer(s)	
	mov	$f4,#SpcCmdSetSongSpeed			;ack
	jmp	!MainLoop				;return


StopSongStream:
	and	SongEffectsUploaded,#%11111110		;clear "song uploaded" flag
	mov	$f2,#$5c				;reset key off in case it's still active.(shouldn't, though)
	mov	$f3,#%00001111				;key off all mod/stream channels
	mov	$f4,#SpcCmdStopSong			;ack command
	jmp	!MainLoop				;return



SetReportType:
	mov		SpcReportType,$f5			;get new report type
	mov	$f4,#SpcCmdReportType			;ack
	jmp	!MainLoop

SetSongVolume:
	mov		MusicVol1,$f5			;get new volume, mod channel 0,1
	mov		MusicVol2,$f6			;get new volume, mod channel 0,1
	mov	$f4,#SpcCmdSetSongVolume			;ack
	jmp	!MainLoop

SendReportData:
	mov	a,SpcReportType		;get report type
	and	a,#$07	
	asl 	a
	mov	x,a			;pointer into jump table

	jmp	[!ReportJumpTable+x]
	
ReportJumpTable:
	.dw	ReportVoid
	.dw	ReportTimecode
	.dw	ReportChannelLevels
	.dw	ReportSpecialCmd
	.dw	ReportVoid
	.dw	ReportVoid
	.dw	ReportVoid
	.dw	ReportVoid
	
ReportVoid:
	mov	a,#0
	mov	$f5,a			;clear report data
	mov	$f6,a			
	mov	$f7,a	
	ret


ReportTimecode:
	mov	$f5,#0			;clear report byte first
	movw	ya,ModTimecode
	movw	$f6,ya
	mov	a,SpcReportType
	or	a,#$e0
	mov	$f5,a			;return report type 
	ret


;report current sample levels of all 4 mod channels as 4bit nibbles unsigned.
ReportChannelLevels:
	mov	$f2,#$09
	mov	a,$f3			;get current wave height ch0
	lsr	a
	lsr	a
	lsr	a
	and	a,#$0f
	mov	TempBuffer1,a	

	mov	$f2,#$19
	mov	a,$f3			;get current wave height ch1
	asl	a
	and	a,#$f0
	or	a,TempBuffer1
	mov	TempBuffer1,a

	mov	$f2,#$29
	mov	a,$f3			;get current wave height ch2
	lsr	a
	lsr	a
	lsr	a
	and	a,#$0f
	mov	TempBuffer1+1,a	

	mov	$f2,#$39
	mov	a,$f3			;get current wave height ch3
	asl	a
	and	a,#$f0
	or	a,TempBuffer1+1
	mov	TempBuffer1+1,a				
	
	movw	ya,TempBuffer1
	
	mov	$f5,#0			;clear report byte first
	movw	$f6,ya			;write data to ports
	mov	a,SpcReportType
	or	a,#$e0
	mov	$f5,a			;return report type 
	ret	

ReportSpecialCmd:
	mov	$f5,#0			;clear report byte first
	mov	a,SpecialReportCmd
	mov	$f6,a
	mov	a,SpcReportType
	or	a,#$e0
	mov	$f5,a			;return report type 
	ret
	
	
SoundEffectChannelDspRegLUT:
	.db	$40
	.db	$50
	.db	$60
	.db	$70

SoundEffectChannelKofLUT:
	.db	$10
	.db	$20
	.db	$40
	.db	$80
		
VibratoTable:
	.incbin "data/apu/vibratotable.tbl"

PeriodTable:
	.include "data/apu/amigaperiodtable.tbl"

PitchTable:
	.include "data/apu/pitchtable_new.tbl"
	.incbin "data/apu/pitchtable2.tbl"
ModFile:
