;---------------------------------------
;
; FM Song Player
;
; by drx [www.hacking-cult.org]
;        [www.bluehedgehog.org]
;
; Plays some Beethoven using the Megadrive FM Chip, accessed by 68k
; Features its own song format
;
;---------------------------------------


Vectors:	dc.l $FFFE00,	Entrypoint,	Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l HBlank,	Error,		VBlank,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
		dc.l Error,	Error,		Error,	Error
Header:		dc.b \'SEGA MEGA DRIVE \' ; Console name
		dc.b \'(C) DRX 2004.NOV\' ; Copyright/Date
DomesticName:	dc.b \'FM Song Player by drx - www.hacking-cult.org    \' ; Domestic Name
		dc.b \'FM Song Player by drx - www.hacking-cult.org    \' ; International Name
		dc.b \'GM 00000000-00\'   ; Version
Checksum:	dc.w $1337
					; Checksum
		dc.b \'J               \' ; I/O Support
RomStartLoc:	dc.l 0			; ROM Start
RomEndLoc:	dc.l RomEnd
					; ROM End
RamStartLoc:	dc.l $FF0000		; RAM Start
RamEndLoc:	dc.l $FFFFFF		; RAM End

		dc.b $20,$20,$20,$20	; \'RA\',$F8,$20 if SRAM = on
		
SramStart:	dc.l $20202020		; $200000 if SRAM = on
SramEnd:	dc.l $20202020		; $20xxxx if SRAM = on
		
		dc.b \'                                              \' ; Notes
		dc.b \'      \'
		dc.b \'JUE             \' ; Country

;---------------------
; Code start
;---------------------
		
Entrypoint:

		tst.l	($A10008).l		;Test Port A control
		bne	PortA_Ok

		tst.w	($A1000C).l		;Test Port C control

PortA_Ok:
		bne	SkipSetup

		move.b	($A10001).l,d0		;version
		andi.b	#$F,d0
		beq	SkipSecurity		;if the smd/gen model is 1, skip the security
		move.l	#\'SEGA\',($A14000).l

SkipSecurity:   

		move.w	($C00004).l,d0		;test if VDP works

		moveq	#0,d0
		movea.l	d0,a6
		move	a6,usp			;set usp to $0
		
;---------------------
; Setup VDP registers
;---------------------
		lea	(VDPSetupArray),a0
		move.w	#(VDPSetupArrayEnd-VDPSetupArray)/2,d1	;$18 VDP registers
		
VDPSetupLoop:
		move.w	(a0)+,($C00004).l
		dbf	d1,VDPSetupLoop
		
		
		move.l	#$40000080,($C00004).l
		move.w	#0,($C00000).l		;clean the screen
	
	
;---------------------
; Init the Z80
;---------------------	

		move.w	#$100,($A11100).l	;Stop the Z80
		move.w	#$100,($A11200).l	;Reset the Z80
		
Waitforz80:
		btst	#0,($A11100).l
		bne	Waitforz80		;Wait for z80 to halt
		
		lea	(Z80Init),a0
		lea	($A00000).l,a1
		move.w	#Z80InitEnd-Z80Init,d1
		
InitZ80:
		move.b	(a0)+,(a1)+
		dbf	d1,InitZ80
		
		move.w	#0,($A11200).l
		move.w	#0,($A11100).l		;Start the Z80
		move.w	#$100,($A11200).l
		
	
;---------------------
; Reset the RAM
;---------------------	

		lea	($FFFF0000).l,a0
		move.w	#$3fff,d1
		
ClearRAM:
		move.l	#0,(a0)+
		dbf	d1,ClearRAM
		
		
;---------------------
; VDP again
;---------------------			
		
		move.w	#$8174,($C00004).l
		move.w	#$8F02,($C00004).l
		
		
;---------------------
; Clear the CRAM
;---------------------	

		move.l	#$C0000000,($C00004).l	;Set VDP ctrl to CRAM write
		move.w	#$3f,d1
		
ClearCRAM:
		move.w	#0,($C00000).l
		dbf	d1,ClearCRAM
		
			
;---------------------
; Clear the VDP stuff
;---------------------		

		move.l	#$40000010,($C00004).l
		move.w	#$13,d1
		
ClearStuff:
		move.l	#0,($C00000).l
		dbf	d1,ClearStuff
		

;---------------------
; Init the PSG
;---------------------	

		move.b	#$9F,($C00011).l
		move.b	#$BF,($C00011).l
		move.b	#$DF,($C00011).l
		move.b	#$FF,($C00011).l
		
		
		move.w	#0,($A11200).l
		
		
;---------------------
; Load the z80 driver
;---------------------

		move.w	#$100,($A11100).l	;Stop the Z80
		move.w	#$100,($A11200).l	;Reset the Z80
		
Waitforz80a:
		btst	#0,($A11100).l
		bne	Waitforz80a		;Wait for z80 to halt
		
		lea	(Z80Driver),a0
		lea	($A00000).l,a1
		move.W	#Z80DriverEnd-Z80Driver,d1
		
LoadZ80Driver:
		move.b	(a0)+,(a1)+
		dbf	d1,LoadZ80Driver
		
		move.w	#0,($A11100).l		;Start the Z80
	
;---------------------
; Clear the registers
; and set the SR
;---------------------	

		movem.l	($FF0000).l,d0-a6	
		lea	($FFFE00).l,a7
		move	#$2700,sr

SkipSetup:	

	
;-----------------------
; Here starts your code
;-----------------------
Main:
 
;
; Stop the Z80 (Z80 and 68k cannot access FM at the same time)
;
		move.w	#$100,($A11100).l
		
;
; Setup FM registers
;
		lea	(FMRegArray),a0
		move.l	#(FMRegArrayEnd-FMRegArray)/2,d7	;number of registers (simple math :P)
		
FMRegLoop:
		move.b	(a0)+,d0
		move.b	(a0)+,d1
		bsr	RegisterWrite		;write to a FM register
		dbf	d7,FMRegLoop		;loop until all registers are written to the FM memory


;
; Song playing loop
;			
		move.w	#((SongEnd-Song)/3)-1,d4	;number of notes (simple math again =P)
		lea	(Song).l,a1

PlaySong:
		move.b	(a1)+,d0	;load the note
		move.b	(a1)+,d1	;load the octave
		move.l	#$80,d2		;the length of the full note (in unidentified time ammount thingy - let\'s call it ms from now on :P)
		move.b	(a1)+,d3	;load the note type
		bpl	noL		;if it\'s positive (bit 7 is 1 [$80]), branch
		
		add.l	#$40,d2		;if this flag is set (didn\'t branch), add 0.5 of the previous note length
			
noL:
		andi.b	#$7f,d3		;strip out the $80 flag
		lsr.l	d3,d2		;shift the length to the right x times - x = d3
		bsr	PlayNote	;branch to the PlayNote subroutine
		
		dbf	d4,PlaySong	;loop, until the song ends
		
		move.w	#$0,($A11100).l	;now we have finished working with the FM, so we can start the Z80

		dc.w	$60fe			;infinite loop (bra $-2)

		rts


;-----------------------
; Play a note
;
; Input:
;  d0.b - note (1-12)
;  d1.b - octave (1-7)
;  d2.l - note duration (in ms)
;-----------------------
PlayNote:

		move.l	d2,-(sp)	;let\'s store the note duration on the stack for now
		
		lea	(NoteArray).l,a0

;
; There are registers ($a0, $a4), which store the frequency. The frequencies are 14-bit values and the format is:
;
;  00aa abbb bbbb bbbb
;
;  a - octave (1-7)
;  b - note inside the octave
;

;
; First, the note. We have a note frequency array and we have to convert our input from 1-12 to those frequencies
;	
		sub.b	#1,d0		;we have to substract 1 from d0, because the array starts from 0, not 1
		bmi	NoSound		;if the result is negative ($80-$ff), we get no sound - the d0 was 00 (pause) or smaller before the substraction
		add.b	d0,d0		;multiply the result by 2		
		andi.w	#$ff,d0		;strip the higher byte of word
		move.w	(a0,d0.w),d3	;the array magic

;
; now we have to shift the octave to the left and add it to the note
;
		andi.l	#$7,d1		;strip out unneeded stuff (you don\'t HAVE TO do it, but it\'s safer sometimes)	
		lsl.w	#8,d1		;
		lsl.w	#3,d1		; shift 8+3=11 bits
		
		add.w	d1,d3		; add the octave to the note

;
; now, the register $a4 stores the higher byte of the frequency, the $a0 stores the lower byte. easy
;		
		move.w	d3,d1
		lsr.w	#8,d1		;shift the higher byte to the lower byte
		move.b	#$a4,d0		;register number
		bsr	RegisterWrite
		
		move.w	d3,d1
		andi.w	#$ff,d1		;strip out the higher byte (leave only the lower byte)
		move.b	#$a0,d0		;lower freq register
		
		bsr	RegisterWrite	;no idea what this does, really *shot*
		
		move.b	#$28,d0		;key register
		move.b	#$f0,d1		;turn our channel on (the note starts playing)
		bsr	RegisterWrite	;no need to comment this 1234 times

NoSound:
		move.l	(sp)+,d0	;take our note duration back from stack
		bsr	Sleep		;the strange sleep function, which \'sleeps\' for unknown time :P the only thing known is that, if d0 rises, the sleep length rises =P
		
		move.b	#$28,d0		;key register
		move.b	#$00,d1		;after playing the note for some time, let\'s turn the channel off :P
		bsr	RegisterWrite
		
		rts

;
; Frequencies for all 12 notes (C, C#, D, etc.)
;
NoteArray:
		dc.w	617,	653,	692, 	733
		dc.w	777,	823,	872,	924
		dc.w	979,	1037,	1099,	1164

;-----------------------
; Write to FM register
;
; Input:
;  d0.b - register
;  d1.b - data
;-----------------------

RegisterWrite:
		move.b	($A04000).l,d2		;the status register. it could be actually anything from $a04000 to $a04003 (yeah, wide range =P)
		btst	#7,d2			;check the BUSY bit
		bne.s	RegisterWrite		;if FM is busy, wait (loop)
		move.b	d0,($A04000).l		;if it\'s not, write our register number to the \'control\' address 1
		nop     			;let\'s wait (=P)
		nop     
		nop
		
RegisterWrite_Loop2:
		move.b	($A04000).l,d2		;we have to wait again, to write the data now
		btst	#7,d2
		bne.s	RegisterWrite_Loop2
		move.b	d1,($A04001).l		;write our data to the \'data\' address 1 (ie. to our register)
		rts   				
;
; ok, why address 1?
;  because there is also address 2, which is only for channels 3-6 :P
;	

;---------------------
; Sleep
;
; d0.l - sleep time (ms)
;---------------------	
Sleep:
		move.l	#4193,d1
	
ms:
		sub.l	#1,d1
		bne	ms
		
		sub.l	#1,d0
		bne	Sleep
		
		rts		
		
;---------------------
; Error exceptions
;---------------------

Error:
		rte
		
;---------------------
; Horizontal Blank
;---------------------
HBlank:

		rte
				
;---------------------
; Vertical Blank
;---------------------
VBlank:

		rte

;---------------------
; Note constants
;---------------------

P	= $00
C	= $01
CP	= $02
D	= $03
DP	= $04
E	= $05
F	= $06
FP	= $07
G	= $08
GP	= $09
A	= $0a
AP	= $0b
H	= $0c

L	= $80	;lengths the note duration by 0.5

;---------------------
; My little song format
;
;  1st value - note
;   00 - pause
;   01 - C	02 - C#
;   03 - D	04 - D#
;   05 - E
;   06 - F	07 - F#
;   08 - G	09 - G#
;   0a - A	0b - A# ;this is probably something different, I suck at this =PPP
;   0c - H
;
;  2nd value - octave (1-7)
;  3rd value - duration
;   00 - full note
;   01 - half
;   02 - quarter
;   03 - eight
;   04 - sixtienth
;
;---------------------

Song:

		dc.b	E, $04, $02
		dc.b	E, $04, $02
		dc.b	F, $04, $02
		dc.b	G, $04, $02
		dc.b	G, $04, $02
		dc.b	F, $04, $02
		dc.b	E, $04, $02
		dc.b	D, $04, $02
		dc.b	C, $04, $02
		dc.b	C, $04, $02
		dc.b	D, $04, $02
		dc.b	E, $04, $02
		dc.b	E, $04, $02|L
		dc.b	D, $04, $03
		dc.b	D, $04, $01
		
		dc.b	E, $04, $02
		dc.b	E, $04, $02
		dc.b	F, $04, $02
		dc.b	G, $04, $02
		dc.b	G, $04, $02
		dc.b	F, $04, $02
		dc.b	E, $04, $02
		dc.b	D, $04, $02
		dc.b	C, $04, $02
		dc.b	C, $04, $02
		dc.b	D, $04, $02
		dc.b	E, $04, $02
		dc.b	D, $04, $02|L
		dc.b	C, $04, $03
		dc.b	C, $04, $01
		
		dc.b	D, $04, $02
		dc.b	D, $04, $02
		dc.b	E, $04, $02
		dc.b	C, $04, $02
		dc.b	D, $04, $02
		dc.b	E, $04, $03
		dc.b	F, $04, $03
		dc.b	E, $04, $02
		dc.b	C, $04, $02
		dc.b	D, $04, $02
		dc.b	E, $04, $03
		dc.b	F, $04, $03
		dc.b	E, $04, $02
		dc.b	D, $04, $02
		dc.b	C, $04, $02
		dc.b	D, $04, $02
		dc.b	G, $03, $01
		
		dc.b	E, $04, $02
		dc.b	E, $04, $02
		dc.b	F, $04, $02
		dc.b	G, $04, $02
		dc.b	G, $04, $02
		dc.b	F, $04, $02
		dc.b	E, $04, $02
		dc.b	D, $04, $02
		dc.b	C, $04, $02
		dc.b	C, $04, $02
		dc.b	D, $04, $02
		dc.b	E, $04, $02
		dc.b	D, $04, $02|L
		dc.b	C, $04, $03
		dc.b	C, $04, $01
				
		

SongEnd:


;---------------------
; FM registers array
;
;  1st value - register
;  2nd value - data
;---------------------

FMRegArray:
		dc.b	$22, $00	; LFO off
		dc.b	$27, $00	; Channel 3 mode normal
		
		dc.b	$28, $00	; All channels off
		dc.b	$28, $01	;
		dc.b	$28, $02	;
		dc.b	$28, $04	;	
		dc.b	$28, $05	;
		dc.b	$28, $06	;
		
		dc.b	$2B, $00	; DAC off
		
		dc.b	$30, $71	; DT1/MUL
		dc.b	$34, $0d	;
		dc.b	$38, $33	;	
		dc.b	$3c, $01	;
		
		dc.b	$40, $23	; Total Level
		dc.b	$44, $2d	;
		dc.b	$48, $26	;
		dc.b	$4c, $00	;
		
		dc.b	$50, $5f	; RS/AR
		dc.b	$54, $99	;
		dc.b	$58, $5f	;
		dc.b	$5c, $94	;
		
		dc.b	$60, $05	; AM/DIR
		dc.b	$64, $05	;
		dc.b	$68, $05	;
		dc.b	$6c, $07	;
		
		dc.b	$70, $02	; D2R
		dc.b	$74, $02	;	
		dc.b	$78, $02	;
		dc.b	$7c, $02	;
		
		dc.b	$80, $11	; D1L/RR
		dc.b	$84, $11	;
		dc.b	$88, $11	;
		dc.b	$8c, $A6	;
		
		dc.b	$90, $00	; Proprietary
		dc.b	$94, $00	;
		dc.b	$98, $00	;
		dc.b	$9c, $00	;
		
		dc.b	$b0, $32	; Feedback/algorithm
		dc.b	$b4, $c0	; Both speakers on
		
		dc.b	$28, $00	; Key off
FMRegArrayEnd:


;---------------------
; VDP registers array
;---------------------

VDPSetupArray:
		dc.w	$8004	;9-bit palette = 1 (otherwise would be 3-bit), HBlank = 0
		dc.w	$8134	;Genesis display = 1, DMA = 1, VBlank = 1, display = 0
		dc.w	$8230	;Scroll A - $C000
		dc.w	$8338	;Window   - $E000
		dc.w	$8407	;Scroll B - $E000
		dc.w	$857c	;Sprites  - $F800
		dc.w	$8600	;Unused
		dc.w	$8700	;Backdrop color - $00
		dc.w	$8800	;Unused
		dc.w	$8900	;Unused
		dc.w	$8A00	;H Interrupt register
		dc.w	$8B00	;Full screen scroll, no external interrupts
		dc.w	$8C81	;40 cells display
		dc.w	$8D3F	;H Scroll - $FC00
		dc.w	$8E00	;Unused
		dc.w	$8F02	;VDP auto increment
		dc.w	$9001	;64 cells scroll
		dc.w	$9100	;Window H position
		dc.w	$9200	;Window V position
		dc.w	$93FF	;DMA stuff (off)
		dc.w	$94FF	;DMA stuff (off)
		dc.w	$9500	;DMA stuff (off)
		dc.w	$9600	;DMA stuff (off)
		dc.w	$9780	;DMA stuff (off)
VDPSetupArrayEnd:


;---------------------
; Z80 init code
;---------------------

Z80Init:
	dc.w	$af01, $d91f, $1127, $0021, $2600, $f977 
	dc.w    $edb0, $dde1, $fde1, $ed47, $ed4f, $d1e1                                   
	dc.w    $f108, $d9c1, $d1e1, $f1f9, $f3ed, $5636
	dc.w	$e9e9 
Z80InitEnd:

;---------------------
; Music driver (z80)
;---------------------		

Z80Driver:
		dc.b	$c3,$46,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
		dc.b	$00,$00,$00,$00,$00,$f3,$ed,$56,$31,$00,$20,$3a,$39,$00,$b7,$ca,$4c,$00,$21,$3a,$00,$11,$40,$00,$01,$06,$00,$ed,$b0,$3e,$00,$32,$39,$00,$3e,$b4,$32,$02,$40,$3e,$c0,$32,$03,$40,$3e,$2b,$32,$00,$40,$3e,$80,$32,$01,$40,$3a,$43,$00,$4f,$3a,$44,$00,$47,$3e,$06,$3d
		dc.b	$c2,$81,$00,$21,$00,$60,$3a,$41,$00,$07,$77,$3a,$42,$00,$77,$0f,$77,$0f,$77,$0f,$77,$0f,$77,$0f,$77,$0f,$77,$0f,$77,$3a,$40,$00,$6f,$3a,$41,$00,$f6,$80,$67,$3e,$2a,$32,$00,$40,$7e,$32,$01,$40,$21,$40,$00,$7e,$c6,$01,$77,$23,$7e,$ce,$00,$77,$23,$7e,$ce,$00,$77
		dc.b	$3a,$39,$00,$b7,$c2,$4c,$00,$0b,$78,$b1,$c2,$7f,$00,$3a,$45,$00,$b7,$ca,$4c,$00,$3d,$3a,$45,$00,$06,$ff,$0e,$ff,$c3,$7f,$00
Z80DriverEnd:



RomEnd: