;BOOT BLOCK MUSIC by Martin Åberg aka 'obbe' (ma0 @ home . se , 0=zero).
;This is my try to make a music player routine that fits the boot block
;on an Amiga floppy disk. The aim was to make a compact data format and
;a compact replayer.

;FEATURES:
;Approximately 250 byte replayer code
;1 byte per tone including wait instruction
;looping
;Quite uncomfortable music editing =)
;Primitive envelope
;Tested on the Amiga 500 and Amiga 1200.

;ABOUT THE CODE:
;The development of this replayer ended in January 2007. When I am
;writing this it's June 2008 and I have decided to release the source
;code to the public. The code is not very clear at all points and
;comments are sparse. Probably an example of 'bad programming habits'.
;Well, highly probably because I don't know very much about 'good
;programming habits' =).

;To find out more about the sound generating hardware and the boot block
;specification see the books 'Amiga ROM Kernel Reference Manual' and
;'Amiga Hardware Reference Manual'.

;USE HINTS:
;You should really implement your own sample generating code to get some
;interesting output. At present the samples are just some random data in
;memory.

;In addition you should also modify the values att Data+DecayWait and
;Data+DecayLevel (see routine setupsoundtrash) to tune the envelope
;thing to yout pleasing.

;The value Speed sets the replayer speed (resolution).

;The variable 'bootblock' can be set to 1. If so the file boot.s will
;be inserted on top of the program. You can then write the code
;generated to the first two sectors of a floppy disk. In ASM-One the
;command is 'ws\diskstart\0\2\'. Then you have to write the correct
;checksum to the bootblock (ASM-One command 'cc').

;DATA FORMAT
;So you are interested in 'tracking' music for this strange replayer?
;Here is how: First have a look at the file 'bmtune.s' and the EQU-lines
;at the end of this file to get and idea of the data format.

;There are basicly three commands that the replayer can recognize:
;NOTE ON
;The format of this command is 'chX+tY' where X is the hardware channel
;(0 to 3) and Y is the tone to play according to 'periodtable' and the
;EQU's at the end of this source.

;Yes, the tY thing is just offsets to the 'periodtable'
;array that contain different sample periods (corresponding to audio
;frequencies).

;NOTE OFF
;The format of this command is 'chX+noteoff'. The result is a muted
;channel X.

;WAIT
;The format of this command is 'wait' or 'wait2'. The result of 'wait'
;is that the player will wait 'Speed' number of VBlanks before issuing
;it's next command. 'wait2' waits two times this.

;It is possible to combine the 'wait' and NOTE ON command in the same
;data byte. The format is then 'chX+tY+wait'.

;It is also possible to combine the 'wait2' command with the NOTE ON
;command in the same data byte, but there is a restriction
;regarding which tone you can play when using 'wait2'. (Only the four
;least significant bits holds tone information in this case, allowing
;for a choice of 14 different tones as compared to 30 when wait2 is not
;used).
;Hint:To be safe just don't use the NOTE ON and 'wait2'-combo =).


; The following code is written in early January 2007.
; /obbe (June 2008).

custom		= $dff000
vhposr		= $006
color		= $180
aud		= $0a0
MEMF_CHIP_CLEAR	= (2^1)+(2^16)
AllocMem	= -198
FreeMem		= -210
Speed		= $8		; Number of VBlanks per wait

DecayWait	= 0		; OFFSETS for my Data
DecayLevel	= 2
DecayTimer	= 4
ToneVolume	= 6
Variables	= 8*4
DataSize	= 0+32

bootblock	= 0
start:
	ifne	bootblock
	include bbmusic:boot.s
	auto	ws\diskstart\0\2\
	endc

	move.l	$4.w,a6			; execbase
	moveq.l	#DataSize,d0
	move.l	#MEMF_CHIP_CLEAR,d1	; parameters to AllocMem
	jsr	AllocMem(a6)		; Mem for variables
	move.l	d0,a1			; Data @ a1

	lea	custom+aud,a0		; custombase+aud @ a0
	bsr.w	setupsoundtrash		; trashar a2, a3 och d1...
	lea	music(pc),a3		; musicdata pointer
	lea	musicend(pc),a4		; musicdata end
	lea	periodtable(pc),a5	; Tone periods
	moveq.l	#1,d1			; VBlank counter
loop:	; custom+aud @ a0, Data @ a1, scratch @ a2, musicptr @ a3
	; musicend @ a4, pertable @ a5, execbase @ a6
	; scratch @ d0,d2-d5, VBlank counter @ d1
	moveq.l	#24,d0
	lea	$038(a0),a2		; custom+aud3+ac_vol -> a2
decayloop:
	subq.w	#1,DecayTimer(a1,d0.w)
	bne.b	decaynozero
	move.w	DecayLevel(a1,d0.w),(a2)
decaynozero:
	sub.l	#$10,a2			;a2 - ac_SIZEOF -> a2
	subq.w	#8,d0
	bpl.b	decayloop

	subq.w	#1,d1			; VBlank counter
	bne.b	nomusic			; Time to play?
musicplay:
	cmp.l	a3,a4
	bne.b	norepeat		; repeat if equal
repeat:
	lea	music(pc),a3		; musicdata pointer
norepeat:
	sub.l	d0,d0
	move.b	(a3)+,d0		; music byte in d0
	bpl.b	musicnowait		; Are we going to wait?

	moveq.l	#Speed,d1		; Wait some vblanks
	bclr.l	#4,d0
	beq.b	musicnowait
	lsl.w	d1			; Double wait if bit 4 is set
musicnowait:
	moveq.l	#%00011111,d2		; tone mask
	moveq.l	#%01100000,d3		; channel mask
	and.b	d0,d2			; tone nr @ d2
	beq.b	dummywait		; dummywait if tone = 0

	and.b	d0,d3			; mask out channel
	lsr.b	#2,d3
	move.w	ToneVolume(a1,d3.w),d4	; Default channel volume
	move.w	DecayWait(a1,d3.w),d5	; Reset DecaytTimer for nomute
	cmp.b	#1,d2
	bne.b	nomute
mute:	sub.w	d4,d4			; Mute EQU channel volume 0
	moveq.l	#-1,d5			; Reset DecayTimer for mute
nomute:
	lsl.b	d2			; otherwise word align (pointer)
	lea	-4(a5,d2.w),a2		; period at (a2)

	move.w	d5,DecayTimer(a1,d3.w)	; reset decay counter

	lsl.b	#1,d3			;
	move.w	(a2),$06(a0,d3.w)	; period
	move.w	d4,$08(a0,d3.w)		; volume...

dummywait:
	tst.b	d0			; Was this a wait-byte?
	bmi.b	nomusic			; Then leave and wait
	bra.b	musicplay		; Otherwise take next byte
nomusic:
;	move.w	#%0000000000001111,color-aud(a0)
waitnegline:	tst.b	vhposr-aud(a0) 	; 6 words, no register use!!
		bpl.b	waitnegline
waitposline:	tst.b	vhposr-aud(a0)
		bmi.b	waitposline
;	move.w	#%0000111111111111,color-aud(a0)

	ifeq	bootblock
	btst	#6,$bfe001		;Test mouse
	beq.w	end
	endc

	bra.w	loop

	ifeq	bootblock
end:
	move.w	#%0000000000001111,$096-aud(a0)	;audio dma off (dmacon)
	moveq.l	#DataSize,d0
;	move.l	$4.w,a6
	jmp	FreeMem(a6)
	endc

setupsoundtrash:
	moveq.l	#$30,d1
audloop:
	move.w	#$08,$04(a0,d1.w)	; Waveform length in words
	move.l	#$00,$00(a0,d1.w)	; Pointer to waveform
	sub.w	#$10,d1
	bpl.b	audloop

	move.l	#$00040010,d0	; DecayWait DecayLevel
	moveq.l	#$00000040,d1	; DecayTimer ToneVolume
	move.l	d0,d2
	move.l	d1,d3
	movem.l	d0-d3,(a1)
	movem.l	d0-d3,16(a1)
extra:
	move.w	#$20,$30+$04(a0)
	move.w	#%1000001000001111,$096-aud(a0)	;audio dma on (dmacon)
	rts
music:
	dc.b	ch0+noteoff,ch1+noteoff,ch2+noteoff,ch3+noteoff
	include bbmusic:bmtune.s
musicend:

	even
periodtable:
	dc.w	856,762,678,640,570,508,453
;pt_periodtable:
;	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
;These are the periods recommended in the Amiga Hardware Reference
;manual for PAL machines. Put the values you are going to use at the
;label 'periodtable.

wait	= %10000000	;Can be combined with 'note on' command!
wait2	= %10010000	;Can be combined with 'note on' and 14 tones.
ch0	= %00000000
ch1	= %00100000
ch2	= %01000000
ch3	= %01100000
noteoff	= %00000001
tc1	= %00000010
td1	= %00000011
te1	= %00000100
tf1	= %00000101
tg1	= %00000110
ta1	= %00000111
th1	= %00001000
;There is room for some more tones, 30 in total.
diskend:
