*---------------------------------------------------------------*
*								*
*		NoisetrackerV1.0 replayroutine			*
*								*
*     Documented, debugged and optimized by JMP in Nov 89	*
*								*
*---------------------------------------------------------------*
* ALTERATIONS
*
* 1)	SetVolume now sets the shadow copy as well as the chip volume
*	so that volume slides work.
* 2)	Vibrato no longer crashes the main program, it used to use A4
*	although this register wasn't saved by the interrupt routine !!
* 3)	The instrument parameters (addr,length,repeat,replen) are now
*	calculated once in mt_init rather than every time a new note
*	is played
* 4)	Code is now totally position independent.

	opt	p+

*------------------------------
* EQUATES

_custom	equ	$DFF000

dmacon	equ	$96

aud0	equ	$A0
aud1	equ	$B0
aud2	equ	$C0
aud3	equ	$D0

ac_ptr	equ	0
ac_len	equ	4
ac_per	equ	6
ac_vol	equ	8
ac_dat	equ	10

*------------------------------
* VOICE INFO

	rsreset
vi_command	rs.l	1	command $spppsfnn (see song format)
vi_addr		rs.l	1	sample start address
vi_length	rs.w	1	sample length
vi_repeat	rs.l	1	repeat address
vi_replen	rs.w	1	repeat length
vi_period	rs.w	1	current period
vi_volume	rs.w	1	current volume
vi_voice	rs.w	1	voice enable bit mask (for dmacon)
vi_tdir		rs.b	1	tone portamento direction 0 down/1 up
vi_tspeed	rs.b	1	tone portamento speed
vi_tperiod	rs.w	1	tone portamento dest. period (0=off)
vi_vspeed	rs.b	1	vibrato, bits 7..4=speed, bits 3..0=size
vi_vframe	rs.b	1	vibrato frame count
vi_size		rs.w	0

*------------------------------
* LOCAL VARIABLES

	rsreset
mv_file		rs.l	1	song file address
mv_speed	rs.b	1	current speed
mv_songpos	rs.b	1	current position 0..127
mv_counter	rs.b	1	frame count
mv_break	rs.b	1	set if pattern break/position jump
mv_length	rs.w	1	song length
mv_pattpos	rs.w	1	current pattern row 0..63
mv_dmacon	rs.w	1	shadow reg for enabled voices
mv_size		rs.w	0

*------------------------------
* CONSTANTS

no_of_instruments equ 31
no_of_positions equ 128
row_size equ 16
no_of_rows equ 64
pattern_size equ no_of_rows*row_size
instname_size equ 22
songname_size equ 20

*------------------------------
* SONG/MODULE format

* instrument info

	rsreset
in_name		rs.b	instname_size		padded with 0's
in_length	rs.w	1			length in words
in_volume	rs.w	1			volume 0..64
in_repeat	rs.w	1			repeat offset in words
in_replen	rs.w	1			repeat length in words
in_size		rs.w	0

* song file

	rsreset
sf_name		rs.b	songname_size		padded with 0's
sf_instr	rs.b	no_of_instruments*in_size
sf_length	rs.b	1			song length
sf_restart	rs.b	1			=120, not implemented
sf_positions	rs.b	no_of_positions		pattern nos.
sf_ident	rs.l	1			identifier 'M.K.'
sf_patterns	rs.w	0			patterns start here

* pattern format
*
* 16 bytes/row : 4 bytes per voice
* format : $spppsfnn
* where ppp = note (period value)
*	ss  = instrument no. (0 or $01..$1F)
*	f   = special function (0..$F)
*	nn  = parameters for function
*
* function	no.		parameters
*
* arpeggio	0	2nd halfnote	3rd halfnote
* porta up	1	           speed
* porta down	2		   speed
* tone porta	3		   speed
* vibrato	4	vibratospeed	 vibratosize
* volume slide	A	  upslide	  downslide
* jump		B		  position
* set volume	C		   volume
* pattern break	D	     0		     0
* set filter	E	     0		0=on/1=off
* set speed	F		   speed

*------------------------------
* TESTER

test	lea	mt_data(pc),a0
	bsr.s	mt_init			setup
.loop	move.l	_custom+4,d0		wait for v64
	lsr.l	#8,d0
	and.w	#$1FF,d0
	cmp.w	#128,d0
	bne.s	.loop
	move.w	#$070,_custom+$180	border=green
	bsr	mt_music		irq routine
	move.w	#$000,_custom+$180
	btst	#6,$BFE001		until right button
	bne.s	.loop
	bsr	mt_end			all done
	moveq	#0,d0			ok
	rts

*------------------------------
* PLAYER

* initialize song
* entry	a0=song file address
* exit	d0-d2,a0-a3 used

mt_init	lea	mt_vars(pc),a3
	move.l	a0,mv_file(a3)
	move.b	sf_length(a0),mv_length(a3)	save length
	lea	sf_positions(a0),a1		find maximum pattern no.
	moveq	#no_of_positions-1,d0
	moveq	#0,d2
.find	move.b	(a1)+,d1
	cmp.b	d1,d2
	bgt.s	.find2
	move.b	d1,d2
.find2	dbra	d0,.find
	addq.b	#1,d2
	mulu	#pattern_size,d2
	lea	sf_patterns(a0),a2
	add.l	d2,a2				a2=samples
	lea	sf_instr(a0),a0			a0=instrument info
	lea	mt_sampleinfo(pc),a1		a1=sample info
	moveq	#no_of_instruments-1,d0		for each instrument
.instr	clr.l	(a2)				wipe start of sample
	move.l	a2,(a1)				save addr
	moveq	#0,d1
	move.w	in_length(a0),d1
	move.w	d1,4(a1)			save length
	add.l	d1,d1
	add.l	d1,a2				step 2*length bytes
	move.w	in_volume(a0),12(a1)		save volume
	moveq	#0,d3
	move.w	in_repeat(a0),d3		if repeat<>0 then
	beq.s	.norep
	move.l	(a1),d2
	asl.w	#1,d3
	add.l	d3,d2
	move.l	d2,6(a1)				set repeat addr
	move.w	in_repeat(a0),d0
	add.w	in_replen(a0),d0
	move.w	d0,4(a1)				set length
	bra.s	.next
.norep	move.l	(a1),6(a1)			else	repeat addr = addr
.next	move.w	in_replen(a0),10(a1)			set repeat length
	lea	in_size(a0),a0			next
	lea	16(a1),a1
	dbra	d0,.instr
	or.b	#2,$bfe001			LED off i.e. filter off
	lea	_custom,a0
	clr.w	aud0+ac_vol(a0)			clear volumes
	clr.w	aud1+ac_vol(a0)
	clr.w	aud2+ac_vol(a0)
	clr.w	aud3+ac_vol(a0)
	move.b	#6,mv_speed(a3)			default tempo
	clr.b	mv_songpos(a3)			position 0
	clr.b	mv_counter(a3)			frame 0
	clr.w	mv_pattpos(a3)			row 0
	rts

* end song
* exit	a0 used

mt_end	lea	_custom,a0
	clr.w	aud0+ac_vol(a0)			clear volumes
	clr.w	aud1+ac_vol(a0)
	clr.w	aud2+ac_vol(a0)
	clr.w	aud3+ac_vol(a0)
	move.w	#$000F,dmacon(a0)		disable audio dma
	rts

* music irq handler
* exit	all registers preserved
* note	call on a vbl

mt_music
	movem.l	d0-d4/a0-a6,-(a7)		save used registers
	lea	mt_vars(pc),a3			local variables
	lea	_custom,a4			hardware
	move.l	mv_file(a3),a0			a0=song file
	addq.b	#1,mv_counter(a3)		frame+1
	move.b	mv_counter(a3),d0
	cmp.b	mv_speed(a3),d0			if frame<speed then
	bge.s	.getnew
	lea	mt_voice1(pc),a6			update each voice
	lea	aud0(a4),a5
	bsr	mt_checkcom
	lea	mt_voice2(pc),a6
	lea	aud1(a4),a5
	bsr	mt_checkcom
	lea	mt_voice3(pc),a6
	lea	aud2(a4),a5
	bsr	mt_checkcom
	lea	mt_voice4(pc),a6
	lea	aud3(a4),a5
	bsr	mt_checkcom
	bra	.endr
.getnew	clr.b	mv_counter(a3)			else frame=0
	move.l	mv_file(a3),a0			check new notes
	lea	sf_positions(a0),a2		a2=positions
	lea	sf_patterns(a0),a0		a0=patterns
	moveq	#0,d0
	move.l	d0,d1
	move.b	mv_songpos(a3),d0
	move.b	(a2,d0.w),d1			get current pattern no.
	asl.l	#8,d1
	asl.l	#2,d1
	add.l	d1,a0
	add.w	mv_pattpos(a3),a0		a0=ptr to current pattern
	lea	mt_voice1(pc),a6
	move.l	(a0)+,vi_command(a6)		get command bytes
	move.l	(a0)+,vi_size+vi_command(a6)
	move.l	(a0)+,2*vi_size+vi_command(a6)
	move.l	(a0)+,3*vi_size+vi_command(a6)
	clr.w	mv_dmacon(a3)			no voices selected
	lea	aud0(a4),a5			update each voice
	lea	mt_voice1(pc),a6
	bsr	mt_playvoice
	lea	aud1(a4),a5
	lea	mt_voice2(pc),a6
	bsr	mt_playvoice
	lea	aud2(a4),a5
	lea	mt_voice3(pc),a6
	bsr	mt_playvoice
	lea	aud3(a4),a5
	lea	mt_voice4(pc),a6
	bsr	mt_playvoice
	move.w	#300,d0				delay
.wait	dbra	d0,.wait
	move.w	mv_dmacon(a3),d0		enable flagged voices
	or.w	#$8000,d0
	move.w	d0,dmacon(a4)
	move.w	#300,d0				delay
.wait2	dbra	d0,.wait2
	lea	mt_voice1(pc),a6
	move.l	vi_repeat(a6),aud0+ac_ptr(a4)	set repeat address,length
	move.w	vi_replen(a6),aud0+ac_len(a4)
	move.l	vi_size+vi_repeat(a6),aud1+ac_ptr(a4)
	move.w	vi_size+vi_replen(a6),aud1+ac_len(a4)
	move.l	2*vi_size+vi_repeat(a6),aud2+ac_ptr(a4)
	move.w	2*vi_size+vi_replen(a6),aud2+ac_len(a4)
	move.l	3*vi_size+vi_repeat(a6),aud3+ac_ptr(a4)
	move.w	3*vi_size+vi_replen(a6),aud3+ac_len(a4)
	add.w	#row_size,mv_pattpos(a3)	bump pattern row
	cmp.w	#pattern_size,mv_pattpos(a3)	if end of pattern then
	bne.s	.endr
.nex	clr.w	mv_pattpos(a3)				row=0
	clr.b	mv_break(a3)				cancel break flag
	move.b	mv_songpos(a3),d1			next position
	addq.b	#1,d1
	and.b	#no_of_positions-1,d1
	cmp.b	mv_length(a3),d1			if end of song then
	bne.s	.ok
	moveq	#0,d1						restart
.ok	move.b	d1,mv_songpos(a3)
.endr	move.b	mv_break(a3),d0			if pattern break then next
	bne.s	.nex
	movem.l	(a7)+,d0-d4/a0-a6		restore registers
	rts

* update voice for new note
* entry	d1=index into current pattern
*	a5=audio ptr
*	a6=voice info

mt_playvoice
	moveq	#0,d2
	move.b	vi_command+2(a6),d2		get instrument no.
	lsr.b	#4,d2
	move.b	vi_command(a6),d0
	and.b	#$F0,d0
	or.b	d0,d2				if new instrument then
	beq.s	.setreg
	lsl.w	#4,d2					get parameters
	lea	mt_sampleinfo-16(pc),a1
	add.w	d2,a1
	move.l	(a1)+,vi_addr(a6)
	move.w	(a1)+,vi_length(a6)
	move.l	(a1)+,vi_repeat(a6)
	move.w	(a1)+,vi_replen(a6)
	move.w	(a1)+,vi_volume(a6)
.setreg	move.w	vi_volume(a6),ac_vol(a5)		set chip volume
	move.w	vi_command(a6),d2		get period
	and.w	#$FFF,d2
	beq	mt_checkcom2			if period<>0 then
	move.b	vi_command+2(a6),d0
	and.b	#$F,d0					if command=3 then
	cmp.b	#$3,d0
	bne.s	.setper					set portamento
	move.w	d2,vi_tperiod(a6)			set dest. period
	clr.b	vi_tdir(a6)
	cmp.w	vi_period(a6),d2			cancel if = period
	beq.s	.port1					set direction flag
	bge.s	.port2
	addq.b	#1,vi_tdir(a6)
	bra.s	.port2
.port1	clr.w	vi_tperiod(a6)
.port2	bra	mt_checkcom2
.setper	move.w	vi_voice(a6),d0			else
	move.w	d0,dmacon(a4)				disable voice
	or.w	d0,mv_dmacon(a3)			flag voice used
	move.w	d2,vi_period(a6)			set period
	clr.b	vi_vframe(a6)				clear vibrato frame
	move.l	vi_addr(a6),ac_ptr(a5)			set chip address
	move.w	vi_length(a6),ac_len(a5)		set chip length
	move.w	d2,ac_per(a5)				set chip period
	bra	mt_checkcom2

* check commands 1..4,10
* entry	A5=audio ptr
*	A6=voice info

mt_checkcom
	move.w	vi_command+2(a6),d0		skip if no command
	and.w	#$FFF,d0
	beq.s	.skip
	lsr.w	#6,d0
	and.w	#$003C,d0
	jmp	.v0(pc,d0.w)
.v0	bra	mt_arpeggio		(quicker)
.v1	bra	mt_portup
.v2	bra	mt_portdown
.v3	bra	mt_myport
.v4	bra	mt_vib
.v5	bra	.skip
.v6	bra	.skip
.v7	bra	.skip
.v8	bra	.skip
.v9	bra	.skip
.vA	bra	mt_volslide
.vB	bra	.skip
.vC	bra	.skip
.vD	bra	.skip
.vE	bra	.skip
.vF	bra	.skip
.skip	move.w	vi_period(a6),ac_per(a5)
	rts

* do arpeggio

mt_arpeggio
	moveq	#0,d0
	move.b	mv_counter(a3),d0		cycle = counter mod 3
	divs	#3,d0
	swap	d0
	cmp.w	#1,d0
	bcs.s	.arp2
	bne.s	.arp1
	moveq	#0,d0				cycle=1 : period+2nd halfnote
	move.b	vi_command+3(a6),d0
	lsr.b	#4,d0
	bra.s	.arp3
.arp1	moveq	#0,d0				cycle=2 : period+3rd halfnote
	move.b	vi_command+3(a6),d0
	and.b	#$F,d0
.arp3	asl.w	#1,d0
	moveq	#0,d1
	lea	mt_periods(pc),a0
	move.w	vi_period(a6),d1
	moveq	#37-1,d7
.loop	move.w	(a0,d0.w),d2			find new period
	cmp.w	(a0)+,d1
	bge.s	.arp4
	dbra	d7,.loop
	rts
.arp2	move.w	vi_period(a6),d2		cycle=0 : period
.arp4	move.w	d2,ac_per(a5)			set chip period
	rts

* portamento up

mt_portup
	moveq	#0,d0
	move.b	vi_command+3(a6),d0
	sub.w	d0,vi_period(a6)		period-offset
	move.w	vi_period(a6),d0
	and.w	#$FFF,d0
	cmp.w	#$71,d0				min $71
	bpl.s	.ok
	and.w	#$F000,vi_period(a6)
	or.w	#$71,vi_period(a6)
.ok	move.w	d0,ac_per(a5)			set chip period
	rts

* portamento down

mt_portdown
	moveq	#0,d0
	move.b	vi_command+3(a6),d0
	add.w	d0,vi_period(a6)		period+offset
	move.w	vi_period(a6),d0
	and.w	#$FFF,d0
	cmp.w	#$358,d0			max $358
	bmi.s	.ok
	and.w	#$F000,vi_period(a6)
	or.w	#$358,vi_period(a6)
.ok	move.w	d0,ac_per(a5)			set chip period
	rts

* do tone portamento

mt_myport
	move.b	vi_command+3(a6),d0		if new speed then
	beq.s	.slide
	move.b	d0,vi_tspeed(a6)			set speed
	clr.b	vi_command+3(a6)
.slide	tst.w	vi_tperiod(a6)			skip if off
	beq.s	.skip
	moveq	#0,d0				get speed
	move.b	vi_tspeed(a6),d0
	tst.b	vi_tdir(a6)			if up then
	bne.s	.minus
	add.w	d0,vi_period(a6)			period+speed
	move.w	vi_tperiod(a6),d0
	cmp.w	vi_period(a6),d0			if >=dest then done
	bgt.s	.ok
	bra.s	.done
.minus	sub.w	d0,vi_period(a6)		else
	move.w	vi_tperiod(a6),d0			period-speed
	cmp.w	vi_period(a6),d0
	blt.s	.ok					if <=dest then
.done	move.w	vi_tperiod(a6),vi_period(a6)			period=dest
	clr.w	vi_tperiod(a6)					cancel
.ok	move.w	vi_period(a6),ac_per(a5)		set chip period
.skip	rts

* do vibrato

mt_vib	move.b	vi_command+3(a6),d0		if new speed/size then
	beq.s	.vi
	move.b	d0,vi_vspeed(a6)			set speed/size
.vi	move.b	vi_vframe(a6),d0		get frame
	lsr.w	#2,d0
	and.w	#$1F,d0
	moveq	#0,d2
	move.b	.sine(pc,d0.w),d2		index offset = 255*sin(frame)
	move.b	vi_vspeed(a6),d0		*size/64
	and.w	#$F,d0
	mulu	d0,d2
	lsr.w	#6,d2
	move.w	vi_period(a6),d0
	tst.b	vi_vframe(a6)			set chip period+/-offset
	bpl.s	.plus
	neg.w	d2
.plus	add.w	d2,d0
	move.w	d0,ac_per(a5)
	move.b	vi_vspeed(a6),d0		frame+speed
	lsr.w	#2,d0
	and.w	#$3C,d0
	add.b	d0,vi_vframe(a6)
	rts

* fn(i=0..31)=255*sin(i*360/32)

.sine	dc.b	$00,$18,$31,$4a,$61,$78,$8d,$a1
	dc.b	$b4,$c5,$d4,$e0,$eb,$f4,$fa,$fd
	dc.b	$ff,$fd,$fa,$f4,$eb,$e0,$d4,$c5
	dc.b	$b4,$a1,$8d,$78,$61,$4a,$31,$18
	even

* volume slide

mt_volslide
	move.w	vi_period(a6),ac_per(a5)	set period
	moveq	#0,d0
	move.b	vi_command+3(a6),d0
	cmp.b	#16,d0				if high nibble <> 0 then
	bcs.s	.down
	lsr.b	#4,d0
	add.w	vi_volume(a6),d0			volume+offset, max 64
	cmp.w	#64,d0
	bmi.s	.vol2
	moveq	#64,d0
	bra.s	.vol2
.down	and.b	#$F,d0				else
	neg.w	d0
	add.w	vi_volume(a6),d0			volume-offset, min 0
	bpl.s	.vol2
	moveq	#0,d0
.vol2	move.w	d0,vi_volume(a6)
	move.w	d0,ac_vol(a5)			set chip volume
	rts

* check for commands B..F

mt_checkcom2
	move.b	vi_command+2(a6),d0		get command no.
	and.w	#$000F,d0
	add.w	d0,d0
	jmp	.v0(pc,d0.w)
.v0	rts			(quicker)
.v1	rts
.v2	rts
.v3	rts
.v4	rts
.v5	rts
.v6	rts
.v7	rts
.v8	rts
.v9	rts
.vA	rts
.vB	bra.s	.jump
.vC	bra.s	.setvol
.vD	bra.s	.break
.vE	bra.s	.filter
.vF	bra.s	.speed
.filter	move.b	vi_command+3(a6),d0
	and.b	#1,d0
	add.b	d0,d0
	and.b	#$FD,$BFE001			0=on/1=off
	or.b	d0,$BFE001
	rts
.jump	move.b	vi_command+3(a6),d0
	subq.b	#1,d0
	move.b	d0,mv_songpos(a3)		set song position
.break	not.b	mv_break(a3)			force break
	rts
.setvol	moveq	#0,d0
	move.b	vi_command+3(a6),d0		max 64
	cmp.b	#64,d0
	ble.s	.sv2
	moveq	#64,d0
.sv2	move.w	d0,vi_volume(a6)
	move.w	d0,ac_vol(a5)
	rts
.speed	move.b	vi_command+3(a6),d0
	and.w	#31,d0				skip if 0
	beq.s	.speed2
	clr.b	mv_counter(a3)
	move.b	d0,mv_speed(a3)
.speed2	rts

*------------------------------
* TABLES


mt_periods
	dc.w	$0358,$0328,$02fa,$02d0,$02a6,$0280
	dc.w	$025c,$023a,$021a,$01fc,$01e0,$01c5
	dc.w	$01ac,$0194,$017d,$0168,$0153,$0140
	dc.w	$012e,$011d,$010d,$00fe,$00f0,$00e2
	dc.w	$00d6,$00ca,$00be,$00b4,$00aa,$00a0
	dc.w	$0097,$008f,$0087,$007f,$0078,$0071
	dc.w	0,0

*------------------------------
* VARIABLES

mt_vars	ds.b	mv_size
	even

* sample info
* 16 bytes per sample = addr.l,length.w,repeat.l,replen.w,volume.w,padding.w

mt_sampleinfo
	ds.b	no_of_instruments*16

* voice info (see rs at top of file)

mt_voice1
	ds.l	5
	dc.w	$0001		dma select mask
	ds.w	3
mt_voice2
	ds.l	5
	dc.w	$0002
	ds.w	3
mt_voice3
	ds.l	5
	dc.w	$0004
	ds.w	3
mt_voice4
	ds.l	5
	dc.w	$0008
	ds.w	3

*------------------------------
* MODULE

mt_data	incbin	"st-00:modules/mod.scav3"

*------------------------------

	end


