aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README52
-rw-r--r--built/echo-timer-version-a.binbin0 -> 32768 bytes
-rw-r--r--built/echo-timer-version-b.binbin0 -> 32768 bytes
-rw-r--r--built/echo-timer-version-c.binbin0 -> 32768 bytes
-rw-r--r--data/bg.binbin0 -> 2240 bytes
-rw-r--r--data/fm/bass.eifbin0 -> 29 bytes
-rw-r--r--data/fm/dguitar.eifbin0 -> 29 bytes
-rw-r--r--data/fm/string.eifbin0 -> 29 bytes
-rw-r--r--data/font.binbin0 -> 1536 bytes
-rw-r--r--data/music/hol.esfbin0 -> 1817 bytes
-rw-r--r--data/music/minion.esfbin0 -> 1143 bytes
-rw-r--r--data/pcm/kick.ewf4
-rw-r--r--data/pcm/snare.ewf1
-rw-r--r--src-68k/build.68k21
-rw-r--r--src-68k/core/entry.68k170
-rw-r--r--src-68k/core/header.68k37
-rw-r--r--src-68k/core/menu.68k141
-rw-r--r--src-68k/core/songlist.68k28
-rw-r--r--src-68k/core/vars.68k12
-rw-r--r--src-68k/input/joypad.68k43
-rw-r--r--src-68k/sound/bgms.68k23
-rw-r--r--src-68k/sound/echo.68k245
-rw-r--r--src-68k/sound/list.68k85
-rw-r--r--src-68k/sound/sfxs.68k26
-rw-r--r--src-68k/video/bg.68k43
-rw-r--r--src-68k/video/text.68k108
-rw-r--r--src-68k/video/vsync.68k19
-rw-r--r--src-z80/build.z8010
-rw-r--r--src-z80/core/bgm.z80290
-rw-r--r--src-z80/core/main.z80330
-rw-r--r--src-z80/core/sfx.z80316
-rw-r--r--src-z80/core/vars.z8070
-rw-r--r--src-z80/player/fm.z80784
-rw-r--r--src-z80/player/freq.z80105
-rw-r--r--src-z80/player/misc.z8031
-rw-r--r--src-z80/player/pcm.z80139
-rw-r--r--src-z80/player/psg.z80667
37 files changed, 3800 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..150d9fa
--- /dev/null
+++ b/README
@@ -0,0 +1,52 @@
+ _ _ _ _____ _____ _ _ _ _ _ _____
+| | | | | | | _ | | _ | | \ | | | | | \ | | | ___|
+| | | | | | | |_| | | |_| | | \| | | | | \| | | | __
+| | | | | | | _ | | _| | | | | | | | || |
+| |_| |_| | | | | | | |\ \ | |\ | | | | |\ | | |_| |
+|____.____| |_| |_| |_| \_\ |_| \_| |_| |_| \_| |___._|
+
+This is a VERY unstable and unfinished version of Echo. It isn't anywhere
+ready for use in production code, and it's up here only so other people can
+help with development. This also means this git will be a mess until it's
+ready for use.
+
+tl;dr DON'T USE ECHO YET :|
+
+-----------------------------------------------------------------------------
+
+Current version: 0.8
+
+ And yes, I know the source code is a mess, I need to clean it up severely.
+ Also I know some stuff could be done better (e.g. buffering PCM instead of
+ reading each byte from ROM every time), but leaving that for after the
+ first official release, don't fix what isn't broken -_-'
+
+-----------------------------------------------------------------------------
+
+How to build:
+
+ * Make sure the root of this source code tree is the current directory
+ * Assemble src-z80/build.z80 to bin/prog-z80.bin
+ * Assemble src-68k/build.68k to bin/prog-68k.bin
+
+ Sorry for not providing a build script but my current setup is a mess and
+ needs to be changed x_x; Currently using z80-asm to build the Z80 code and
+ asm68k to build the 68000 code, I need to find good replacements for both
+ (z80-asm for being crappy, asm68k for not being free).
+
+ Also no source for the data blobs yet, for similar reasons, although I
+ have more control over those tools at least so I may come up with good
+ replacements (or even just rebuild the current tools, pretty sure one of
+ them (mdtiler) already has a replacement) :/
+
+-----------------------------------------------------------------------------
+
+Available builds:
+
+ built/echo-timer-version-a.bin
+ Timer test. Both timers get reloaded each time they're fired.
+ built/echo-timer-version-b.bin
+ Timer test. Only timer B is reloaded when it fires, timer A is left to
+ loop on its own.
+ built/echo-timer-version-c.bin
+ Timer test. Both timers are left to loop on their own.
diff --git a/built/echo-timer-version-a.bin b/built/echo-timer-version-a.bin
new file mode 100644
index 0000000..cf637f5
--- /dev/null
+++ b/built/echo-timer-version-a.bin
Binary files differ
diff --git a/built/echo-timer-version-b.bin b/built/echo-timer-version-b.bin
new file mode 100644
index 0000000..2c2df59
--- /dev/null
+++ b/built/echo-timer-version-b.bin
Binary files differ
diff --git a/built/echo-timer-version-c.bin b/built/echo-timer-version-c.bin
new file mode 100644
index 0000000..0459b30
--- /dev/null
+++ b/built/echo-timer-version-c.bin
Binary files differ
diff --git a/data/bg.bin b/data/bg.bin
new file mode 100644
index 0000000..0ce7ca6
--- /dev/null
+++ b/data/bg.bin
Binary files differ
diff --git a/data/fm/bass.eif b/data/fm/bass.eif
new file mode 100644
index 0000000..3daf3e1
--- /dev/null
+++ b/data/fm/bass.eif
Binary files differ
diff --git a/data/fm/dguitar.eif b/data/fm/dguitar.eif
new file mode 100644
index 0000000..dbf2b29
--- /dev/null
+++ b/data/fm/dguitar.eif
Binary files differ
diff --git a/data/fm/string.eif b/data/fm/string.eif
new file mode 100644
index 0000000..1bb4b06
--- /dev/null
+++ b/data/fm/string.eif
Binary files differ
diff --git a/data/font.bin b/data/font.bin
new file mode 100644
index 0000000..f7de517
--- /dev/null
+++ b/data/font.bin
Binary files differ
diff --git a/data/music/hol.esf b/data/music/hol.esf
new file mode 100644
index 0000000..2a4b894
--- /dev/null
+++ b/data/music/hol.esf
Binary files differ
diff --git a/data/music/minion.esf b/data/music/minion.esf
new file mode 100644
index 0000000..2b89ee6
--- /dev/null
+++ b/data/music/minion.esf
Binary files differ
diff --git a/data/pcm/kick.ewf b/data/pcm/kick.ewf
new file mode 100644
index 0000000..6902184
--- /dev/null
+++ b/data/pcm/kick.ewf
@@ -0,0 +1,4 @@
+���~���~����}�|��~�v}yztyxjzto�|d`��S�qE�G)QEFBaO�[Q�4wuh�}yf������������ƪ���տ�����������˺���ŝ�������{u|{rS\UCRB,6+#!. #   
+ !%&+5/55=JGVYfpw�������������������������������������������������������«�������|���k^muc^`ILKFB(06$$#')" #  "*+,,-39:59<BE>EJNTSTTX[__aegiktqpwy}}���������������������������������������������������������������������������������������ż������������������xvvqqkjd^_ZUTRPNJHHEAB;6431-('(#$! 
+
+ !$'+.147:>ADFJNQTXZ_eklr}z�����������������������������������������������������������������������û���������������|xusokgd`_\YXUSQPLGGEDCA@?>;<=;:;:79:9:9<:;><>==@>@BAABDEFHIJLLMNOPPQRSTUWXZ[[]]```caceefhjjkmoqpqqsttuwz|{~~}������������������������������������������������������������������������������������������������������~zyyurommlgdd`^^\[ZWVUQPOMKHIIFEFDCDAABAA?>>@A??@?<>>=?@BAAEEDFIKINNORRVXZ]bbeimppwyz|����������������������������������������������������������������¿�������������������������~zwtrpmjhd_^\WVTQOMIJIGCECA@100,/-.+)-+)++,)*-*,+*//.-11/12759:9=:<>>>BDDGGKMMQTTYZ]baeihkloqtwy|�������������������������������������������������������������������������������������������������������||yzuuusrqqpnopmlkmllmlnnpononqrrrrtsuwxyxzzz||}}}~~}��~��~��~}�}}�|{{z{zyzywwvvwvutrssrqrsrrqrrtroqrrqsssutuvvxwwyz{}}�����������������������������������������������������~~{}{{zwxvussrppqnmnllklliihihihggghggeggffggggghghhijjiikjkjkkllmonopqortrsuvwxzzz||~�������������������������������������������������������������|{zwutrponmkiihfeedbbaa`^``_````bbbdbdffhikllnprrstvxz{|}�������������������������������������������������������������������������������������������������������������������������������������������������������~}||{zzyxwvvvtttrrqpppnnnmlllkkjiiiiiihhiihhiiiijjjjkkkllmnopppqrrstuuvwwxyyz{||}~������������������������������������������������������������������������������������~~}||{zzzxxxwwvvvvuuuutuuutuuuuvvvwwwwxyyzz{{|}}~~��������������������������������������������������������~~}}}}}}}}|||||||||}}|}}}}}}}~~~~~~~~������������������������������������������~~~~~~~~}}}}}}|||{|{{{{zzzzzyyyyyyyxyxxxxxxxxyyyyzzz{{{{||}}~~����������������������������������������������������������������~~~}}}}||||{{{{{{{{{{{z{{{{{{{{{{{{{{{{{{{{{{{||||||||||||||||||||||||||||||||||||}}}}}}}}}}~~~~~~� \ No newline at end of file
diff --git a/data/pcm/snare.ewf b/data/pcm/snare.ewf
new file mode 100644
index 0000000..886db5c
--- /dev/null
+++ b/data/pcm/snare.ewf
@@ -0,0 +1 @@
+~�|�yo�\hx��n���z���id�vee*M1>kN\Ze}wZy����������Ǿ������������������|l\ZL=C1?'% *0-1ENOWR]q�������ȿ���������ٿ�ſ�DZ�������������v��rp||^snufZqPaUN_S_V^MTUKX][OeX_i_^i]X`R^WJTXHQa[SZ\_YcuPggJZ}oiaQ�wj��������Ĝ���Ը���̿��ֳ�α���Ĵ���������������x���}�������q���y��d�{Z�vlghkhaMg]HTPQD6CB+6C,)&,0-7%,)2819)G95N?RHMXWjfzw����������˽����������������������������������������~��y~�v}pmttodvoYcew`WgiU_mQfW\gT^\KXSZXP]VN=]PRYQ[S^bZfcZydjqanontxq}z�����������������������������������������������������������|��������v��p{unehxmb_bZ[WZZSRXSKU;JY<GHEM>DUBG^PPSQa[a{rfxq~�������������ѻ���ǰ��˽�ٶ�Ǿ��������������������������y����yy{yw|jltlel]EYFQD(K;,42>6%1/0#6=.9:A1@R6:NGaUAe\[wxp���������������������������������������������������������������������x�q�zt{ryh��h�|vu��vlp�hu��qhyvp�c|�q|w{lymr�hooprww}l�zp�{~~y�y������������vv~z�����|~��u|��k�tw���~���~������y���t��vvsmcpmnhfldk[^dQgaZ[L_RQaW\a\cwr_{zl��z�����������������������������������}v�wuphkeoefck_UaYaS__QbRZa_ad{mqvw�p���������������������������������z����������������������������������������{���{�������������y��t}ovygdo{rfhnidnebfZio\f_^fdmeelkltolutnrwo{znqrv�qv}q��u���vz��~}����������������������������������|��z��w�|z�{p}�hhimngjajlisacejzq{ws|������������������������������y��y�j}{q�kyvnvp}rjjtooyowhty�}}x������~�����������~���������{�{s�y|ttuw{nrvwqjmpejpfjhjohnpnuqvxyy|x����������������������������������}�z{�wwy}|x{�v�~x}�zv�{z���������|}��}uvpuspuhkojqoedmnkjojhnnqirrmwnw{y{�|�����������������������������������v}�xzytxvz{s|vw|z�v��|����������������������z�}vvymovnkjoqggigjfnsmhmxmswwyw�����������������������������y�{��|y~|y}uywxztuwzw~w�~x���������������������������|�{{~{vywqruosmeqiejmd`mdkkdjjsqouqt}{||}���������������������������������y~�~}|~w|~|~xvzzw}|uz~||{}z���������������~���}|~{|�x{w}z|{uxy|w{ys~xv�vt}}~�~|��~�������������������}�~}}|}{|wyxyyzyv{w}|z�}~����������������������������������~}�~wwxwyuuvutuuswxvyvw{xz{z{�x���������������������������~}zw|{vwuvruwssqsqssoqoptrrstvvwvyz|{~��������������������������������������~����}|�~��}��|��}��������������~�~~~~{}|{y|{yzxzz{wxywwwxwzxyzvzzz}y{||}}}}~��������������������������������������������������������������������~|�}}|z|y{yyyvyxwxvwwvwyvxzxwx||zz}�}�����������������������������~���}}�~}���~~�~�~}���������������������������������~{�}{||}{}}}{{}~{{~~}|}���~����������������������������~~}}|~~~{~~~}~~~�����������������������������~�~~|}|}}|~}}}~}}}}}~}��������������������������}�~}}~~}}}}}}}}~}~~~������������������������������~~}~}}~}~~~~~~~~��������������������������������~�~~~~~~}~}~~}~||~~~|~~}~~~~��������������������������������~~~~|~~~}~}}|~~}}~~~���������������������������~~~}�~~~~�������������������������~~�~~�~~��������������������������������������~~~~~��~��������������������������~����������������~����������������������������������~�~���������������������������������������������~�����~����������������������������~�������������������������������~~����� \ No newline at end of file
diff --git a/src-68k/build.68k b/src-68k/build.68k
new file mode 100644
index 0000000..dc118b1
--- /dev/null
+++ b/src-68k/build.68k
@@ -0,0 +1,21 @@
+Echo_ProgFile equs "bin/prog-z80.bin"
+
+ include "src-68k/core/header.68k"
+ include "src-68k/core/entry.68k"
+ include "src-68k/core/menu.68k"
+ include "src-68k/core/songlist.68k"
+
+ include "src-68k/video/text.68k"
+ include "src-68k/video/bg.68k"
+ include "src-68k/video/vsync.68k"
+
+ include "src-68k/input/joypad.68k"
+
+ include "src-68k/sound/echo.68k"
+ include "src-68k/sound/list.68k"
+ include "src-68k/sound/bgms.68k"
+ include "src-68k/sound/sfxs.68k"
+
+ cnop 0, $8000
+
+ include "src-68k/core/vars.68k"
diff --git a/src-68k/core/entry.68k b/src-68k/core/entry.68k
new file mode 100644
index 0000000..b8a6f27
--- /dev/null
+++ b/src-68k/core/entry.68k
@@ -0,0 +1,170 @@
+;****************************************************************************
+; EntryPoint
+; Where the program starts
+;****************************************************************************
+
+EntryPoint:
+ move.w #$2700, sr ; Disable interrupts
+
+ move.b ($A10001), d0 ; Disable TMSS if needed
+ and.b #$0F, d0
+ beq.s @NoTMSS
+ move.l #"SEGA", ($A14000)
+@NoTMSS:
+
+ bsr InitJoypad ; Init joypad
+
+ lea ($C00004), a0 ; Init VDP
+ move.w #$8004, (a0) ; No IRQ4, no HV latch
+ move.w #$8114, (a0) ; Disable display
+ move.w #$8230, (a0) ; Scroll A: $C000
+ move.w #$8407, (a0) ; Scroll B: $E000
+ move.w #$8578, (a0) ; Sprites: $F000
+ move.w #$8700, (a0) ; Background: pal 0, color 0
+ move.w #$8B00, (a0) ; No IRQ2, full scroll
+ move.w #$8C81, (a0) ; H40, no S/H, no interlace
+ move.w #$8D3E, (a0) ; HScroll: $F800
+ move.w #$8F02, (a0) ; Autoincrement: 2 bytes
+ move.w #$9001, (a0) ; Scroll size: 64x32
+ move.w #$9100, (a0) ; Hide window plane
+ move.w #$9200, (a0) ; " " "
+
+ moveq #0, d0 ; Clear VRAM
+ move.l #$40000000, (a0)
+ lea ($C00000), a1
+ move.w #$800-1, d1
+@ClearVRAM:
+ move.l d0, (a1)
+ move.l d0, (a1)
+ move.l d0, (a1)
+ move.l d0, (a1)
+ move.l d0, (a1)
+ move.l d0, (a1)
+ move.l d0, (a1)
+ move.l d0, (a1)
+ dbf d1, @ClearVRAM
+
+ move.l #$C0000000, (a0) ; Clear CRAM
+ moveq #8-1, d1
+@ClearCRAM:
+ move.l d0, (a1)
+ move.l d0, (a1)
+ move.l d0, (a1)
+ move.l d0, (a1)
+ dbf d1, @ClearCRAM
+
+ move.l #$40000010, (a0) ; Clear VSRAM
+ move.l d0, (a1)
+
+ lea (PointerList), a0 ; Initialize Echo
+ bsr Echo_Init
+
+ bsr LoadFont ; Load font
+
+ lea ($C00004), a0 ; Load palette
+ lea ($C00000), a1
+ move.l #$C0020000, (a0)
+ move.l #$066600CE, (a1)
+ move.l #$000E000E, (a1)
+ move.l #$C0220000, (a0)
+ move.l #$06660EEE, (a1)
+ move.l #$000E000E, (a1)
+ move.l #$C0420000, (a0)
+ move.l #$04440E88, (a1)
+ move.l #$C0620000, (a0)
+ move.l #$04440888, (a1)
+
+ move.l #$C00A0000, (a0)
+ move.l #$02220444, (a1)
+ move.l #$02240024, (a1)
+ move.l #$02440020, (a1)
+ move.w #$0040, (a1)
+
+ move.l #$58000000, (a0) ; Load graphics
+ lea (@Gfx_Arrows), a2
+ moveq #12-1, d7
+@LoadGfx:
+ move.l (a2)+, (a1)
+ move.l (a2)+, (a1)
+ move.l (a2)+, (a1)
+ move.l (a2)+, (a1)
+ move.l (a2)+, (a1)
+ move.l (a2)+, (a1)
+ move.l (a2)+, (a1)
+ move.l (a2)+, (a1)
+ dbf d7, @LoadGfx
+
+ move.l #$44880003, (a0) ; Write left arrow
+ move.l #$00C000C1, (a1)
+ move.l #$45080003, (a0)
+ move.l #$00C200C3, (a1)
+
+ move.l #$44C40003, (a0) ; Write right arrow
+ move.l #$28C128C0, (a1)
+ move.l #$45440003, (a0)
+ move.l #$28C328C2, (a1)
+
+ moveq #2, d0 ; Write title
+ moveq #1, d1
+ move.w #$8000, d2
+ lea (@Str_Title1), a0
+ bsr WriteString
+ addq.w #2, d1
+ move.w #$E000, d2
+ lea (@Str_Title2), a0
+ bsr WriteString
+
+ moveq #20-(24/2), d0 ; Write instructions
+ moveq #23, d1
+ lea (@Str_Instr1), a0
+ bsr WriteString
+ moveq #20-(18/2), d0
+ addq.w #2, d1
+ lea (@Str_Instr2), a0
+ bsr WriteString
+
+ bsr DrawBG ; Draw background
+
+ move.w #$8154, ($C00004) ; Enable display
+ bra MainMenu ; Go into the main menu
+
+;****************************************************************************
+
+@Gfx_Arrows:
+ dc.l $00000000, $00000000, $00000000, $00000000
+ dc.l $00000034, $00004343, $00343434, $43434343
+ dc.l $00000034, $00004343, $00343434, $43434343
+ dc.l $34343434, $43434343, $34343434, $43434343
+ dc.l $34343434, $00434343, $00003434, $00000043
+ dc.l $00000000, $00000000, $00000000, $00000000
+ dc.l $34343434, $43434343, $34343434, $43434343
+ dc.l $34343434, $00434343, $00003434, $00000043
+
+@Gfx_BG:
+ dc.l $55555555, $55555555, $55555555, $55555555
+ dc.l $55555555, $55555555, $55555555, $55555555
+ dc.l $00000000, $00000000, $00000000, $00000000
+ dc.l $00000000, $00000000, $00000000, $00000000
+ dc.l $66666666, $66666666, $66666666, $66666666
+ dc.l $66666666, $66666666, $66666666, $66666666
+
+ dc.l $78787878, $87878787, $78787878, $87878787
+ dc.l $78787878, $87878787, $78787878, $87878787
+ dc.l $79797979, $97979797, $79797979, $97979797
+ dc.l $79797979, $97979797, $79797979, $97979797
+
+ dc.l $AAAAAAAA, $AAAAAAAA, $AAAAAAAA, $AAAAAAAA
+ dc.l $AAAAAAAA, $AAAAAAAA, $AAAAAAAA, $AAAAAAAA
+ dc.l $BABABABA, $ABABABAB, $BABABABA, $ABABABAB
+ dc.l $BABABABA, $ABABABAB, $BABABABA, $ABABABAB
+
+ dc.l $56565656, $65656565, $56565656, $65656565
+ dc.l $56565656, $65656565, $56565656, $65656565
+
+ ; 123456789012345678901234567890123456
+
+@Str_Title1: dc.b "Echo sound engine", 0
+@Str_Title2: dc.b "Version 0.8 by Sik", 0
+@Str_Instr1: dc.b "Use D-pad to select song", 0
+@Str_Instr2: dc.b "A/C: play, B: stop", 0
+ even
diff --git a/src-68k/core/header.68k b/src-68k/core/header.68k
new file mode 100644
index 0000000..fde18ee
--- /dev/null
+++ b/src-68k/core/header.68k
@@ -0,0 +1,37 @@
+;****************************************************************************
+; 68000 vectors
+;****************************************************************************
+
+ dc.l $1000000
+ dc.l EntryPoint
+ dcb.l 62, ErrorInt
+
+;****************************************************************************
+; Mega Drive header
+;****************************************************************************
+
+ dc.b "SEGA MEGA DRIVE "
+ dc.b "(C) SIK 2010.NOV"
+ dc.b "ECHO TESTER PROGRAM"
+ dcb.b $150-*, $20
+ dc.b "ECHO TESTER PROGRAM"
+ dcb.b $180-*, $20
+ dc.b "XX XXXXXXXX-00"
+ dc.w $0000
+ dc.b "J"
+ dcb.b $1A0-*, $20
+ dc.l $000000, $3FFFFF
+ dc.l $FF0000, $FFFFFF
+ dcb.b 12, $20
+ dcb.b 12, $20
+ dcb.b 40, $20
+ dc.b "JUE"
+ dcb.b $200-*, $20
+
+;****************************************************************************
+; ErrorInt
+; Generic error handler routine (hangs up)
+;****************************************************************************
+
+ErrorInt:
+ bra.s *
diff --git a/src-68k/core/menu.68k b/src-68k/core/menu.68k
new file mode 100644
index 0000000..d5f7914
--- /dev/null
+++ b/src-68k/core/menu.68k
@@ -0,0 +1,141 @@
+;****************************************************************************
+; MainMenu
+; Menu where you get to select the song and such
+; The main screen, bah...
+;****************************************************************************
+
+MainMenu:
+ move.w #0, (RAM_CurrSong) ; Selected song
+ bsr UpdateMenu ; Show description of first song
+
+ move.l #0, (RAM_LArrowAnim) ; Reset arrows anim
+ move.w #$8407, (RAM_BGAnim) ; Reset background anim
+
+@MainLoop:
+ move.b (RAM_JoyPress), d0 ; Get joypad input
+
+ btst.l #2, d0 ; Previous song?
+ beq.s @NoLeft
+
+ move.w (RAM_CurrSong), d7 ; Select previous song
+ bne.s @NotTooLeft
+ move.w #NumSongs, d7
+@NotTooLeft:
+ subq.w #1, d7
+ move.w d7, (RAM_CurrSong)
+
+ bsr UpdateMenu ; Update current song
+ move.w #28, (RAM_LArrowAnim) ; Animate left arrow
+ lea (SFX_Beep), a0 ; Beep!
+ bsr Echo_PlaySFX
+@NoLeft:
+
+ btst.l #3, d0 ; Next song?
+ beq.s @NoRight
+
+ move.w (RAM_CurrSong), d7 ; Select next song
+ addq.w #1, d7
+ cmp.w #NumSongs, d7
+ blt.s @NotTooRight
+ moveq #0, d7
+@NotTooRight:
+ move.w d7, (RAM_CurrSong)
+ bsr UpdateMenu ; Update current song
+
+ move.w #28, (RAM_RArrowAnim) ; Animate right arrow
+ lea (SFX_Beep), a0 ; Beep!
+ bsr Echo_PlaySFX
+@NoRight:
+
+ btst.l #5, d0 ; Play song?
+ bne.s @DoPlay
+ btst.l #6, d0
+ beq.s @NoPlay
+@DoPlay:
+ lea (SongList), a1 ; Get song address
+ move.w (RAM_CurrSong), d7
+ lsl.w #4, d7
+ lea (a1,d7.w), a1
+ move.l (a1), a0
+ bsr Echo_PlayBGM ; Play song
+@NoPlay:
+
+ btst.l #4, d0 ; Stop song?
+ beq.s @NoStop
+ bsr Echo_StopBGM
+@NoStop:
+
+ btst.l #7, d0 ; Debug key
+ beq.s @NoDebug
+ lea (BGM_Test), a0 ; Play test BGM
+ ;bsr Echo_PlayBGM
+ lea (SFX_Test), a0 ; Play test SFX
+ bsr Echo_PlaySFX
+@NoDebug:
+
+ lea (@ArrowPal), a0
+
+ move.w (RAM_LArrowAnim), d0 ; Animate left arrow
+ move.l (a0,d0.w), d1
+ move.l #$C0060000, ($C00004)
+ move.l d1, ($C00000)
+ subq.w #2, d0
+ bge.s @NoLArrowOver
+ moveq #0, d0
+@NoLArrowOver:
+ move.w d0, (RAM_LArrowAnim)
+
+ move.w (RAM_RArrowAnim), d0 ; Animate right arrow
+ move.l (a0,d0.w), d1
+ move.l #$C0260000, ($C00004)
+ move.l d1, ($C00000)
+ subq.w #2, d0
+ bge.s @NoRArrowOver
+ moveq #0, d0
+@NoRArrowOver:
+ move.w d0, (RAM_RArrowAnim)
+
+ move.w (RAM_BGAnim), d0 ; Animate background
+ bchg.l #1, d0
+ move.w d0, (RAM_BGAnim)
+ move.w d0, ($C00004)
+
+ bsr VSync ; Next frame
+ bsr ReadJoypad
+ bra @MainLoop
+
+;****************************************************************************
+
+@ArrowPal:
+ dc.w $00E, $00E, $02E, $02E, $04E, $04E, $06E, $06E
+ dc.w $28E, $28E, $4AE, $4AE, $6CE, $6CE, $8EE, $8EE
+
+;****************************************************************************
+; UpdateMenu
+; Shows the current option on screen
+;****************************************************************************
+
+UpdateMenu:
+ bsr ClearLines ; Clear lines
+
+ lea (SongList), a1 ; Get address of song data
+ move.w (RAM_CurrSong), d0
+ lsl.w #4, d0
+ lea (a1,d0.w), a1
+ addq.l #4, a1
+
+ moveq #7, d0 ; Write song title
+ moveq #9, d1
+ move.w #$A000, d2
+ move.l (a1)+, a0
+ bsr WriteString
+
+ addq.w #2, d1 ; Write song description
+ move.w #$C000, d2
+ move.l (a1)+, a0
+ bsr WriteString
+ addq.w #2, d1
+ move.l (a1)+, a0
+ bsr WriteString
+
+ rts ; End of subroutine
diff --git a/src-68k/core/songlist.68k b/src-68k/core/songlist.68k
new file mode 100644
index 0000000..69562af
--- /dev/null
+++ b/src-68k/core/songlist.68k
@@ -0,0 +1,28 @@
+;****************************************************************************
+; SongList
+; List of songs that appear on the menu
+;****************************************************************************
+
+SongList:
+ dc.l BGM_Minion, @Str_Minion_1, @Str_Minion_2, @Str_Minion_3
+ dc.l BGM_HoL, @Str_HoL_1, @Str_Null, @Str_Null
+
+ ; 012345678901234567890123456
+
+@Str_Null: dc.b 0
+@Str_Untitled: dc.b "[untitled]", 0
+
+@Str_Minion_1: dc.b "Evil's Minion", 0
+@Str_Minion_2: dc.b "My distortion guitar is", 0
+@Str_Minion_3: dc.b "better than Tiido's B)", 0
+
+@Str_HoL_1: dc.b "Hall of Loneliness", 0
+
+ even
+
+;****************************************************************************
+; NumSongs
+; Number of songs in song list
+;****************************************************************************
+
+NumSongs equ 2
diff --git a/src-68k/core/vars.68k b/src-68k/core/vars.68k
new file mode 100644
index 0000000..8097c73
--- /dev/null
+++ b/src-68k/core/vars.68k
@@ -0,0 +1,12 @@
+;****************************************************************************
+; Where program variables are defined
+;****************************************************************************
+
+ rsset $FF0000
+
+RAM_JoyHold rs.b 1 ; Current "held" joypad status
+RAM_JoyPress rs.b 1 ; Current "pressed" joypad status
+RAM_LArrowAnim rs.w 1 ; Animation for left arrow
+RAM_RArrowAnim rs.w 1 ; Animation for right arrow
+RAM_BGAnim rs.w 1 ; Animation for background
+RAM_CurrSong rs.w 1 ; Selected song
diff --git a/src-68k/input/joypad.68k b/src-68k/input/joypad.68k
new file mode 100644
index 0000000..f33d000
--- /dev/null
+++ b/src-68k/input/joypad.68k
@@ -0,0 +1,43 @@
+;****************************************************************************
+; InitJoypad
+; Initializes the joypad
+;****************************************************************************
+
+InitJoypad:
+ move.b #$40, ($A10009) ; Initialize I/O ports
+ move.b #$40, ($A10003)
+ move.w #0, (RAM_JoyHold) ; Initialize status
+ rts ; End of subroutine
+
+;****************************************************************************
+; ReadJoypad
+; Reads the joypad status
+;****************************************************************************
+
+ReadJoypad:
+ lea ($A10003), a6 ; I/O data port
+
+ move.b #$40, (a6) ; Read D-pad, B and C
+ nop
+ nop
+ move.b (a6), d7
+ move.b #$00, (a6) ; Read A and Start
+ nop
+ nop
+ move.b (a6), d6
+
+ and.b #$3F, d7 ; Process input
+ and.b #$30, d6
+ add.b d6, d6
+ add.b d6, d6
+ or.b d6, d7
+ not.b d7
+
+ lea (RAM_JoyHold), a6 ; Store new joypad status
+ move.b (a6), d6
+ move.b d7, (a6)+
+ not.b d6
+ and.b d6, d7
+ move.b d7, (a6)
+
+ rts ; End of subroutine
diff --git a/src-68k/sound/bgms.68k b/src-68k/sound/bgms.68k
new file mode 100644
index 0000000..7264048
--- /dev/null
+++ b/src-68k/sound/bgms.68k
@@ -0,0 +1,23 @@
+;****************************************************************************
+; BGM_Test
+; Generic BGM to test events
+;****************************************************************************
+
+BGM_Test:
+ dc.b $FF
+
+;****************************************************************************
+; BGM_Minion
+; "Evil's Minion"
+;****************************************************************************
+
+BGM_Minion:
+ incbin "data/music/minion.esf"
+
+;****************************************************************************
+; BGM_HoL
+; "Hall of Loneliness"
+;****************************************************************************
+
+BGM_HoL:
+ incbin "data/music/hol.esf"
diff --git a/src-68k/sound/echo.68k b/src-68k/sound/echo.68k
new file mode 100644
index 0000000..73eb82f
--- /dev/null
+++ b/src-68k/sound/echo.68k
@@ -0,0 +1,245 @@
+;****************************************************************************
+; Echo_Z80Request
+; Requests the Z80 bus
+;****************************************************************************
+
+Echo_Z80Request macro
+ move.w #$100, ($A11100) ; Request Z80 bus
+@Echo_WaitZ80\@:
+ btst.b #0, ($A11100) ; Did we get it yet?
+ bne.s @Echo_WaitZ80\@ ; Keep waiting
+ endm ; End of macro
+
+;****************************************************************************
+; Echo_Z80Release
+; Releases the Z80 bus
+;****************************************************************************
+
+Echo_Z80Release macro
+ move.w #$000, ($A11100) ; Release Z80 bus
+ endm ; End of macro
+
+;****************************************************************************
+; Echo_Z80Reset
+; Resets the Z80 and YM2612
+;****************************************************************************
+
+Echo_Z80Reset macro
+ move.w #$000, ($A11200) ; Assert reset line
+ rept $10 ; Wait until hardware resets
+ nop ; ...
+ endr ; ...
+ move.w #$100, ($A11200) ; Release reset line
+ endm ; End of macro
+
+;****************************************************************************
+; Echo_Init
+; Initializes Echo
+;
+; input a0.l ... Address of pointer list
+;****************************************************************************
+
+Echo_Init:
+ movem.l d0/a0-a1, -(sp) ; Save registers
+
+ Echo_Z80Reset ; May not work without this...
+ Echo_Z80Request ; We need the Z80 bus
+
+ move.b #$01, ($A01FFF) ; Command: load pointer list
+
+ move.l a0, d0 ; Easier to manipulate here
+ move.b d0, ($A01FFD) ; Store low address byte
+ lsr.l #7, d0 ; Get high address byte
+ lsr.b #1, d0 ; We skip one bit
+ bset.l #7, d0 ; Point into bank window
+ move.b d0, ($A01FFE) ; Store high address byte
+ lsr.w #8, d0 ; Get bank byte
+ move.w d0, d1 ; Parse 32X bit separately
+ lsr.w #1, d1 ; Put 32X bit in place
+ and.b #$7F, d0 ; Filter out unused bit from addresses
+ and.b #$80, d1 ; Filter out all but 32X bit
+ or.b d1, d0 ; Put everything together
+ move.b d0, ($A01FFC) ; Store bank byte
+
+ lea @Z80Program(pc), a0 ; Where Z80 program starts
+ lea ($A00000), a1 ; Where Z80 RAM starts
+ move.w #@Z80ProgSize-1, d0 ; Size of Z80 program (DBF adjusted)
+@LoadLoop: ; Go through all the program
+ move.b (a0)+, (a1)+ ; Copy byte into Z80 RAM
+ dbf d0, @LoadLoop ; Go for next byte
+
+ Echo_Z80Reset ; Now reset for real
+ Echo_Z80Release ; Let the Z80 go!
+
+ movem.l (sp)+, d0/a0-a1 ; Restore registers
+ rts ; End of subroutine
+
+;****************************************************************************
+; Echo Z80 program
+; It should be located wherever Echo_ProgFile was defined
+;****************************************************************************
+
+@Z80Program: incbin "\Echo_ProgFile"
+@Z80ProgSize equ *-@Z80Program
+ even
+
+;****************************************************************************
+; Echo_SendCommand
+; Sends an Echo command (no address parameter)
+;
+; input d0.b ... Echo command
+;****************************************************************************
+
+Echo_SendCommand:
+ move.w d1, -(sp) ; Save register
+
+ Echo_Z80Request ; We need the Z80 bus
+
+@Try:
+ tst.b ($A01FFF) ; Check if Echo is ready
+ beq.s @Ready ; Too busy?
+ Echo_Z80Release ; Let Echo continue
+ move.w #$FF, d1 ; Give it some time
+ dbf d1, * ; ...
+ Echo_Z80Request ; Get Z80 bus back
+ bra.s @Try ; Try again
+
+@Ready:
+ move.b d0, ($A01FFF) ; Write command ID
+ Echo_Z80Release ; We're done with the Z80 bus
+
+ move.w (sp)+, d1 ; Restore register
+ rts ; End of subroutine
+
+;****************************************************************************
+; Echo_SendCommandEx
+; Sends an Echo command (with address parameter)
+;
+; input d0.b ... Echo command
+; input a0.l ... Address parameter
+;****************************************************************************
+
+Echo_SendCommandEx:
+ movem.l d0-d1, -(sp) ; Save register
+
+ Echo_Z80Request ; We need the Z80 bus
+
+@Try:
+ tst.b ($A01FFF) ; Check if Echo is ready
+ beq.s @Ready ; Too busy?
+ Echo_Z80Release ; Let Echo continue
+ move.w #$FF, d1 ; Give it some time
+ dbf d1, * ; ...
+ Echo_Z80Request ; Get Z80 bus back
+ bra.s @Try ; Try again
+
+@Ready:
+ move.b d0, ($A01FFF) ; Write command ID
+
+ move.l a0, d0 ; Easier to manipulate here
+ move.b d0, ($A01FFD) ; Store low address byte
+ lsr.l #7, d0 ; Get high address byte
+ lsr.b #1, d0 ; We skip one bit
+ bset.l #7, d0 ; Point into bank window
+ move.b d0, ($A01FFE) ; Store high address byte
+
+ lsr.w #8, d0 ; Get bank byte
+ move.w d0, d1 ; Parse 32X bit separately
+ lsr.w #1, d1 ; Put 32X bit in place
+ and.b #$7F, d0 ; Filter out unused bit from addresses
+ and.b #$80, d1 ; Filter out all but 32X bit
+ or.b d1, d0 ; Put everything together
+ move.b d0, ($A01FFC) ; Store bank byte
+
+ Echo_Z80Release ; We're done with the Z80 bus
+
+ movem.l (sp)+, d0-d1 ; Restore register
+ rts ; End of subroutine
+
+;****************************************************************************
+; Echo_PlaySFX
+; Plays a SFX
+;
+; input a0.l ... Pointer to SFX data
+;****************************************************************************
+
+Echo_PlaySFX:
+ move.w d0, -(sp) ; Save register
+ move.b #$02, d0 ; Command $02 = play SFX
+ bsr Echo_SendCommandEx ; Send command to Echo
+ move.w (sp)+, d0 ; Restore register
+ rts ; End of subroutine
+
+;****************************************************************************
+; Echo_StopSFX
+; Stops SFX playback
+;****************************************************************************
+
+Echo_StopSFX:
+ move.w d0, -(sp) ; Save register
+ move.b #$03, d0 ; Command $03 = stop SFX
+ bsr Echo_SendCommand ; Send command to Echo
+ move.w (sp)+, d0 ; Restore register
+ rts ; End of subroutine
+
+;****************************************************************************
+; Echo_PlayBGM
+; Plays a BGM
+;
+; input a0.l ... Pointer to BGM data
+;****************************************************************************
+
+Echo_PlayBGM:
+ move.w d0, -(sp) ; Save register
+ move.b #$04, d0 ; Command $04 = play BGM
+ bsr Echo_SendCommandEx ; Send command to Echo
+ move.w (sp)+, d0 ; Restore register
+ rts ; End of subroutine
+
+;****************************************************************************
+; Echo_StopBGM
+; Stops BGM playback
+;****************************************************************************
+
+Echo_StopBGM:
+ move.w d0, -(sp) ; Save register
+ move.b #$05, d0 ; Command $05 = stop BGM
+ bsr Echo_SendCommand ; Send command to Echo
+ move.w (sp)+, d0 ; Restore register
+ rts ; End of subroutine
+
+;****************************************************************************
+; Echo_GetStatus
+; Gets the current status of Echo
+;
+; output d0.b ... Echo status
+; Bit #0: SFX is playing
+; Bit #1: BGM is playing
+;****************************************************************************
+
+Echo_GetStatus:
+ Echo_Z80Request ; We need the Z80 bus
+ move.b ($A01FF0), d0 ; Just get the status
+ Echo_Z80Release ; Let the Z80 go!
+ rts ; End of subroutine
+
+;****************************************************************************
+; Echo_ListEntry
+; Defines an entry in a pointer list
+;****************************************************************************
+
+Echo_ListEntry macro addr
+ dc.b $80|((addr)>>8&$7F) ; High byte of address
+ dc.b (addr)&$FF ; Low byte of address
+ dc.b ((addr)>>15&$7F)|((addr)>>16&$80) ; Bank number
+ endm
+
+;****************************************************************************
+; Echo_ListEnd
+; Ends a pointer list
+;****************************************************************************
+
+Echo_ListEnd macro
+ dc.b $00 ; End of list mark
+ even ; Just in case...
+ endm
diff --git a/src-68k/sound/list.68k b/src-68k/sound/list.68k
new file mode 100644
index 0000000..f871b0a
--- /dev/null
+++ b/src-68k/sound/list.68k
@@ -0,0 +1,85 @@
+;****************************************************************************
+; PointerList
+; Pointer list used by Echo
+;****************************************************************************
+
+PointerList:
+ Echo_ListEntry Instr_PSGFlat ; $00 [PSG] Flat PSG instrument
+ Echo_ListEntry Instr_DGuitar ; $01 [FM] Distortion guitar
+ Echo_ListEntry Instr_Snare ; $02 [PCM] Snare drum
+ Echo_ListEntry Instr_Kick ; $03 [PCM] Bass drum (kick)
+ Echo_ListEntry Instr_Strings ; $04 [FM] String ensemble
+ Echo_ListEntry Instr_Bass ; $05 [FM] Standard bass
+ Echo_ListEntry Instr_SoftPSG ; $06 [PSG] Soft PSG envelope
+ Echo_ListEntry Instr_PianoPSG ; $07 [PSG] Piano PSG instrument
+ Echo_ListEnd
+
+;****************************************************************************
+; Instrument $00 [PSG]
+; Flat PSG instrument (no envelope)
+;****************************************************************************
+
+Instr_PSGFlat:
+ dc.b $FE,$00,$FF
+
+;****************************************************************************
+; Instrument $01 [FM]
+; Distortion guitar
+;****************************************************************************
+
+Instr_DGuitar:
+ incbin "data/fm/dguitar.eif"
+
+;****************************************************************************
+; Instrument $02 [PCM]
+; Snare drum
+;****************************************************************************
+
+Instr_Snare:
+ incbin "data/pcm/snare.ewf"
+
+;****************************************************************************
+; Instrument $03 [PCM]
+; Bass drum
+;****************************************************************************
+
+Instr_Kick:
+ incbin "data/pcm/kick.ewf"
+
+;****************************************************************************
+; Instrument $04 [FM]
+; String ensemble
+;****************************************************************************
+
+Instr_Strings:
+ incbin "data/fm/string.eif"
+
+;****************************************************************************
+; Instrument $05 [FM]
+; Standard bass
+;****************************************************************************
+
+Instr_Bass:
+ incbin "data/fm/bass.eif"
+
+;****************************************************************************
+; Instrument $06 [PSG]
+; "Soft" PSG envelope
+;****************************************************************************
+
+Instr_SoftPSG:
+ dc.b $00,$01,$01,$02,$02,$02,$03,$03,$03,$03,$FE,$04,$FF
+
+;****************************************************************************
+; Instrument $07 [PSG]
+; Piano-like PSG instrument
+;****************************************************************************
+
+Instr_PianoPSG:
+ dc.b $00,$01,$02,$03,$04,$04,$05,$05
+ dc.b $06,$06,$07,$07,$08,$08,$08,$08
+ dc.b $09,$09,$09,$09,$0A,$0A,$0A,$0A
+ dc.b $0B,$0B,$0B,$0B,$0C,$0C,$0C,$0C
+ dc.b $0C,$0C,$0C,$0C,$0D,$0D,$0D,$0D
+ dc.b $0D,$0D,$0D,$0D,$0E,$0E,$0E,$0E
+ dc.b $0E,$0E,$0E,$0E,$FE,$0F,$FF
diff --git a/src-68k/sound/sfxs.68k b/src-68k/sound/sfxs.68k
new file mode 100644
index 0000000..ac969dc
--- /dev/null
+++ b/src-68k/sound/sfxs.68k
@@ -0,0 +1,26 @@
+;****************************************************************************
+; SFX_Test
+; Generic SFX to test events
+;****************************************************************************
+
+SFX_Test:
+ dc.b $E8,$E9,$EA
+ dc.b $28,$00,$48,$00
+ dc.b $29,$08,$49,$00
+ dc.b $2A,$08,$4A,$00
+
+ dc.b $08,12,$09,24,$0A,36, $FE,$10, $18,$19,$1A, $FE,$10
+ dc.b $08,12,$09,24,$0A,36, $FE,$10, $18,$19,$1A, $FE,$10
+ dc.b $08,14,$09,26,$0A,38, $FE,$10, $18,$19,$1A, $FE,$10
+
+ dc.b $FF
+
+;****************************************************************************
+; SFX_Beep
+; Beep SFX
+;****************************************************************************
+
+SFX_Beep:
+ dc.b $EA,$1A,$4A,$00,$2A,$00
+ dc.b $0A,2*36,$FE,4
+ dc.b $FF
diff --git a/src-68k/video/bg.68k b/src-68k/video/bg.68k
new file mode 100644
index 0000000..c64e287
--- /dev/null
+++ b/src-68k/video/bg.68k
@@ -0,0 +1,43 @@
+;****************************************************************************
+; DrawBG
+; Draws the background
+;****************************************************************************
+
+DrawBG:
+ lea ($C00004), a2
+ lea ($C00000), a1
+
+ lea (@BGData), a0
+ move.l #$60000003, d0
+ moveq #28-1, d6
+@YLoop:
+ move.l d0, (a2)
+ moveq #40-1, d7
+@XLoop:
+ move.w (a0)+, (a1)
+ dbf d7, @XLoop
+ add.l #$00800000, d0
+ dbf d6, @YLoop
+
+ lea (@BGData), a0
+ move.l #$60000002, d0
+ moveq #28-1, d6
+@YLoop2:
+ move.l d0, (a2)
+ moveq #40-1, d7
+@XLoop2:
+ move.w (a0)+, d1
+ bset.l #11, d1
+ move.w d1, (a1)
+ dbf d7, @XLoop2
+ add.l #$00800000, d0
+ dbf d6, @YLoop2
+
+ rts ; End of subroutine
+
+;****************************************************************************
+; Background data
+;****************************************************************************
+
+@BGData:
+ incbin "data/bg.bin"
diff --git a/src-68k/video/text.68k b/src-68k/video/text.68k
new file mode 100644
index 0000000..832605e
--- /dev/null
+++ b/src-68k/video/text.68k
@@ -0,0 +1,108 @@
+;****************************************************************************
+; LoadFont
+; Loads the font in VRAM
+;****************************************************************************
+
+LoadFont:
+ move.l #$40000000, ($C00004) ; Where font will be stored
+
+ lea (@Font), a0 ; Font data
+ lea ($C00000), a1 ; VDP data port
+ move.w #96*16-1, d7 ; Go through all lines
+@Loop:
+
+ move.b (a0)+, d0 ; Fetch next line
+ moveq #0, d1 ; Initial color
+ moveq #8-1, d6 ; Go through all pixels
+@ILoop:
+
+ add.b d0, d0 ; Get pixel color
+ bcc.s @Transparent
+ moveq #3, d1
+@Transparent:
+ subq.b #1, d1
+ bgt.s @NoUnderflow
+ moveq #0, d1
+@NoUnderflow:
+
+ lsl.l #4, d2 ; Make room for pixel
+ or.b d1, d2 ; Store pixel
+ dbf d6, @ILoop ; Next pixel
+
+ move.l d2, (a1) ; Store line in VRAM
+ dbf d7, @Loop ; Next line
+
+ rts ; End of subroutine
+
+@Font:
+ incbin "data/font.bin"
+
+;****************************************************************************
+; WriteString
+; Writes a string on screen
+;
+; input d0.w ... X coordinate
+; input d1.w ... Y coordinate
+; input d2.w ... FX and such
+; input a0.l ... String
+;****************************************************************************
+
+WriteString:
+ movem.l d0-d2, -(sp) ; Save registers
+
+ lsl.w #6, d1 ; Calculate address
+ add.w d1, d0
+ add.w d0, d0
+
+ and.l #$FFFF, d0 ; Tell VDP the address
+ or.l #$00034000, d0
+ swap d0
+ move.l d0, ($C00004)
+
+ lea ($C00000), a5 ; VDP data port
+ moveq #2-1, d7 ; Go through both lines
+@Loop:
+
+ move.l a0, a6
+@ILoop:
+ move.b (a6)+, d1 ; Get next character
+ beq.s @End ; End of string?
+
+ sub.b #$20, d1 ; Write tile in VRAM
+ and.w #$7F, d1
+ add.w d1, d1
+ add.w d2, d1
+ move.w d1, (a5)
+
+ bra.s @ILoop ; Next character
+
+@End:
+ add.l #$80<<16, d0
+ move.l d0, ($C00004)
+ addq.w #1, d2 ; Next line
+ dbf d7, @Loop
+
+ movem.l (sp)+, d0-d2 ; Restore registers
+ rts ; End of subroutine
+
+;****************************************************************************
+; ClearLines
+; Clears the description lines
+;****************************************************************************
+
+ClearLines:
+ move.l #$448E0003, d0 ; Initial position to clear
+ lea ($C00004), a0 ; VDP control port
+ lea ($C00000), a1 ; VDP data port
+
+ moveq #7-1, d1 ; Clear all lines
+ moveq #0, d2
+@Loop:
+ move.l d0, (a0)
+ rept 26/2
+ move.l d2, (a1)
+ endr
+ add.l #$80<<16, d0
+ dbf d1, @Loop
+
+ rts ; End of subroutine
diff --git a/src-68k/video/vsync.68k b/src-68k/video/vsync.68k
new file mode 100644
index 0000000..ed5ac17
--- /dev/null
+++ b/src-68k/video/vsync.68k
@@ -0,0 +1,19 @@
+;****************************************************************************
+; VSync
+; Waits until the next frame
+;****************************************************************************
+
+VSync:
+ lea ($C00004), a6
+
+@Loop1: ; Wait until current VBlank is over
+ move.w (a6), d7
+ btst.l #3, d7
+ bne.s @Loop1
+
+@Loop2: ; Wait until next VBlank starts
+ move.w (a6), d7
+ btst.l #3, d7
+ beq.s @Loop2
+
+ rts ; End of subroutine
diff --git a/src-z80/build.z80 b/src-z80/build.z80
new file mode 100644
index 0000000..88932ac
--- /dev/null
+++ b/src-z80/build.z80
@@ -0,0 +1,10 @@
+ include "src-z80/core/main.z80"
+ include "src-z80/player/pcm.z80"
+ include "src-z80/core/bgm.z80"
+ include "src-z80/core/sfx.z80"
+ include "src-z80/player/fm.z80"
+ include "src-z80/player/psg.z80"
+ include "src-z80/player/misc.z80"
+
+ include "src-z80/player/freq.z80"
+ include "src-z80/core/vars.z80"
diff --git a/src-z80/core/bgm.z80 b/src-z80/core/bgm.z80
new file mode 100644
index 0000000..f69235d
--- /dev/null
+++ b/src-z80/core/bgm.z80
@@ -0,0 +1,290 @@
+;****************************************************************************
+; PlayBGM [command $04]
+; Plays a BGM
+;****************************************************************************
+
+PlayBGM:
+ PollPCM
+ call ClearBGM ; Clear BGM resources
+ PollPCM
+
+ ld a, (RAM_Status) ; Show BGM playback in Echo's status
+ or $02
+ ld (RAM_Status), a
+
+ PollPCM
+
+ ld hl, RAM_ComBank ; Get command parameters
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+
+ PollPCM
+
+ xor a ; Command parsed
+ ld (RAM_Command), a
+
+ ld hl, RAM_BGMData ; Set BGM as playing
+ ld (hl), $01
+ inc l ; No delays!
+ ld (hl), $01
+ inc l ; Store BGM start bank
+ ld (hl), c
+ inc l ; Store BGM start address (low)
+ ld (hl), e
+ inc l ; Store BGM start address (high)
+ ld (hl), d
+
+ PollPCM
+
+ ld hl, ProcessBGM ; Tell Echo to process BGM
+ ld (DoTick_BGM+1), hl
+
+ PollPCM
+ jp IdleLoop ; End of subroutine
+
+;****************************************************************************
+; ProcessBGM
+; Processes a tick for a BGM
+;****************************************************************************
+
+ProcessBGM:
+ PollPCM
+
+ ld hl, RAM_BGMData+1 ; BGM data address
+
+ ld a, (hl) ; Delaying?
+ dec a
+ jp z, .nodelay
+ ld (hl), a
+
+ jp DoTick_BGMSkip ; End of subroutine
+
+.nodelay:
+ PollPCM
+
+ inc l
+ ld c, (hl) ; Get current address
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+ProcessBGMRun:
+ PollPCM ; Fetch next event
+ call GetParam
+ PollPCM
+
+ ld a, b ; Parse byte
+
+ cp $08
+ jp c, NoteOnFMBGM ; Events $00-$07: note on FM
+ cp $0B
+ jp c, NoteOnPSGBGM ; Events $08-$0A: note on PSG (square)
+ jp z, NoteOnNoiseBGM ; Event $0B: note on PSG (noise)
+ cp $0C
+ jp z, PlayPCMBGM ; Event $0C: play PCM
+
+ PollPCM
+ ld a, b
+
+ cp $18
+ jp c, NoteOffFMBGM ; Events $10-$17: note off FM
+ cp $1C
+ jp c, NoteOffPSGBGM ; Events $18-$1B: note off PSG
+ jp z, StopPCMBGM ; Event $1C: note off PCM
+
+ PollPCM
+ ld a, b
+
+ cp $FE
+ jp z, SetDelayBGM ; Event $FE: set delay
+ cp $FF
+ jp z, StopBGMEvent ; Event $FF: stop BGM
+ cp $FC
+ jp z, LoopBGM ; Event $FC: loop BGM
+ cp $FD
+ jp z, SetLoopBGM ; Event $FD: set loop point
+
+ PollPCM
+ ld a, b
+
+ cp $28
+ jp c, SetFMVolBGM ; Events $28-$2B: set FM volume
+ cp $2C
+ jp c, SetPSGVolBGM ; Events $28-$2B: set PSG volume
+
+ PollPCM
+ ld a, b
+
+ cp $38
+ jp c, SetNoteFMBGM ; Events $30-$37: set FM note
+ cp $3B
+ jp c, SetNotePSGBGM ; Events $38-$3A: set PSG note (square)
+ jp z, SetNoteNoiseBGM ; Event $3B: set PSG note (noise)
+
+ PollPCM
+ ld a, b
+
+ cp $48
+ jp c, LoadFMBGM ; Events $40-$47: load FM instrument
+ cp $4C
+ jp c, LoadPSGBGM ; Events $48-$4B: load PSG instrument
+
+ PollPCM
+ ld a, b
+
+ cp $F8 ; Events $F0-$F7: set FM parameters
+ jp c, SetFMParamBGM
+
+ PollPCM ; FFFFFFFFF bad event >:(
+ jp StopBGMEvent ; End of subroutine
+
+ProcessBGMSkip2: ; This is where we land after a locked event
+ PollPCM ; that had two bytes for the parameter
+ inc l ; Skip first byte
+ jp nz, .nobankskip2
+ inc h
+ jp nz, .nobankskip2
+ ld h, $80
+ inc c
+.nobankskip2:
+
+ProcessBGMSkip1: ; This is where we land after a locked event
+ PollPCM ; that had one byte for the parameter
+ inc l ; Skip byte
+ jp nz, .nobankskip1
+ inc h
+ jp nz, .nobankskip1
+ ld h, $80
+ inc c
+.nobankskip1:
+
+ProcessBGMSkip: ; This is where we land after a locked event
+ PollPCM ; without parameters
+
+ jp ProcessBGMRun ; Keep processing
+
+;****************************************************************************
+; StopBGM* [command $05, event $FF]
+; Stops BGM playback
+;****************************************************************************
+
+StopBGMEvent:
+ call StopBGM ; We're just a wrapper
+ jp DoTick_BGMSkip ; End of subroutine
+
+StopBGMCmd:
+ xor a ; Command parsed
+ ld (RAM_Command), a
+ call StopBGM ; We're just a wrapper
+ jp IdleLoop ; End of subroutine
+
+StopBGM:
+ ld a, (RAM_Status) ; Hide BGM playback in Echo's status
+ and $FD
+ ld (RAM_Status), a
+
+ PollPCM
+
+ call ClearBGM ; Clear BGM resources
+ PollPCM
+
+ xor a ; Stop playback
+ ld (RAM_BGMPlaying), a
+ ld hl, DoTick_BGMSkip
+ ld (DoTick_BGM+1), hl
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; ClearBGM
+; Clears BGM resources
+;****************************************************************************
+
+ClearBGM:
+ ld a, (RAM_Locked+6) ; Stop PCM playback if needed
+ or a
+ call z, StopPCM
+
+ ld b, 4 ; Mute all non-locked PSG channels
+ ld de, RAM_PSGData+48
+ ld hl, RAM_Locked+11
+.mutepsg:
+ PollPCM
+ ld a, (hl)
+ or a
+ jr nz, .nopsgmute
+ xor a
+ ld (de), a
+.nopsgmute:
+ PollPCM
+ ld a, e
+ sub 16
+ ld e, a
+ dec l
+ djnz .mutepsg
+
+ ld b, 8 ; Mute all non-locked FM channels
+.mutefm:
+ PollPCM
+ ld a, (hl)
+ or a
+ jr nz, .nofmmute
+ PollPCM
+ dec b
+ ld (ix+0), $28
+ ld (ix+1), b
+ inc b
+.nofmmute:
+ dec l
+ djnz .mutefm
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; LoopBGM [event $FC]
+; Makes a BGM loop
+;****************************************************************************
+
+LoopBGM:
+ PollPCM
+
+ ex de, hl ; Get looping address
+ inc l
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ dec l
+ dec l
+ dec l
+ ex de, hl
+
+ jp ProcessBGMRun ; End of subroutine
+
+;****************************************************************************
+; SetLoopBGM [event $FD]
+; Sets the BGM loop point
+;****************************************************************************
+
+SetLoopBGM:
+ PollPCM
+
+ ex de, hl
+ inc l ; Store loop point address
+ ld (hl), c
+ inc l
+ ld (hl), e
+ inc l
+ ld (hl), d
+ dec l
+ dec l
+ dec l
+ ex de, hl
+
+ jp ProcessBGMRun ; End of subroutine
diff --git a/src-z80/core/main.z80 b/src-z80/core/main.z80
new file mode 100644
index 0000000..4018a1b
--- /dev/null
+++ b/src-z80/core/main.z80
@@ -0,0 +1,330 @@
+;****************************************************************************
+; EntryPoint
+; Where the program starts
+;****************************************************************************
+
+EntryPoint:
+ ld sp, RAM_Stack ; Init stack
+
+ xor a ; Reset Echo status
+ ld (RAM_Status), a
+
+ ld hl, $7F11 ; Mute PSG
+ ld (hl), $9F
+ ld (hl), $BF
+ ld (hl), $DF
+ ld (hl), $FF
+ xor a
+ ld (RAM_PSGData), a
+ ld (RAM_PSGData+16), a
+ ld (RAM_PSGData+32), a
+ ld (RAM_PSGData+48), a
+
+ ld hl, $6000 ; Set default bank
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+
+ ;ex af, af' ; Set 9th bank bit to 0 by default
+ ;xor a ; (0 = $000000-$7FFFFF range aka MD mode)
+ ;ex af, af' ; (1 = $800000-$FFFFFF range aka 32X mode)
+
+ ld ix, $4000 ; YM2612 I/O ports base address
+ ld iyh, $40
+
+ exx ; Init PCM playback status
+ ld bc, $0000 ; B = playing, C = bank
+ ld de, $0000 ; DE = address
+ ld hl, $6000 ; HL = always $6000
+ exx
+
+ ld (ix+0), $2B ; Disable DAC by default
+ ld (ix+1), $00
+
+ ld e, $7F ; Mute all FM channels
+ ld a, $40
+ ld b, 4
+.mutefm:
+ ld (ix+0), a
+ ld (ix+1), e
+ ld (ix+2), a
+ ld (ix+3), e
+ inc a
+ ld (ix+0), a
+ ld (ix+1), e
+ ld (ix+2), a
+ ld (ix+3), e
+ inc a
+ ld (ix+0), a
+ ld (ix+1), e
+ ld (ix+2), a
+ ld (ix+3), e
+ inc a
+ inc a
+ djnz .mutefm
+
+ ld (ix+0), $B4 ; Ensure all channels can be heard from both
+ ld (ix+1), $C0 ; speakers (by default they're mute!)
+ ld (ix+0), $B5
+ ld (ix+1), $C0
+ ld (ix+0), $B6
+ ld (ix+1), $C0
+ ld (ix+2), $B4
+ ld (ix+3), $C0
+ ld (ix+2), $B5
+ ld (ix+3), $C0
+ ld (ix+2), $B6
+ ld (ix+3), $C0
+
+ ld (ix+0), $24 ; Init timers
+ ld (ix+1), $FE
+ ld (ix+0), $25
+ ld (ix+1), $03
+ ld (ix+0), $26
+ ld (ix+0), $C9
+ ld (ix+1), $FF
+ ld (ix+0), $27
+ ld (ix+1), $3F
+
+ jp IdleLoop ; Go into idle loop
+
+;****************************************************************************
+; PollPCM
+; Used to update PCM while not idle
+;****************************************************************************
+
+PollPCM: macro
+ ld a, ($4000)
+ bit 0, a
+ call nz, UpdatePCM
+ endm
+
+;****************************************************************************
+; RunCommand
+; Checks which command to run
+;****************************************************************************
+
+RunCommand:
+ dec a ; Command $01: load list
+ jp z, LoadList
+ dec a ; Command $02: play SFX
+ jp z, PlaySFX
+ dec a ; Command $03: stop SFX
+ jp z, StopSFXCmd
+ dec a ; Command $04: play BGM
+ jp z, PlayBGM
+ dec a ; Command $05: stop BGM
+ jp z, StopBGMCmd
+
+ PollPCM
+
+ xor a ; Bad command, ignore >:(
+ ld (RAM_Command), a
+
+ PollPCM
+
+;****************************************************************************
+; IdleLoop
+; Loop that runs when not processing SFX or BGM
+;****************************************************************************
+
+IdleLoop:
+ ld a, (RAM_Command) ; Look for commands
+ or a
+ jr nz, RunCommand
+
+ PollPCM ; Poll PCM
+
+ ld a, ($4000) ; Tick?
+ bit 1, a
+ jr nz, DoTick
+ bit 0, a ; Poll PCM again
+ call nz, UpdatePCM ; Not using macro for optimization purposes
+
+ jp IdleLoop ; Keep idling
+
+;****************************************************************************
+; DoTick
+; Called whenever a new tick triggers
+;****************************************************************************
+
+DoTick:
+ ld a, (ix+0)
+ bit 0, a
+ call nz, UpdatePCM
+
+ ld (ix+0), $26 ; Reset timer
+.timerset:
+ ld (ix+1), $C8
+ ld (ix+0), $27
+ ld (ix+1), $2F
+
+ ld a, (.timerset+3) ; $C8 is too fast, $C9 is too slow
+ xor $01 ; So, we alternate between them to compensate
+ ld (.timerset+3), a
+
+ PollPCM
+
+DoTick_SFX: ; Process SFXs
+ jp DoTick_SFXSkip
+DoTick_SFXSkip:
+
+ PollPCM
+
+DoTick_BGM: ; Process BGMs
+ jp DoTick_BGMSkip
+DoTick_BGMSkip:
+
+ PollPCM
+
+ jp UpdatePSG ; Update PSG envelopes
+DoTick_PSGSkip:
+
+ PollPCM
+
+ jp IdleLoop ; End of subroutine
+
+;****************************************************************************
+; BankSwitch
+; Switches into a new bank (won't update player status!)
+;
+; input A .... New bank to switch into
+; input HL ... Must be $6000
+; breaks ..... AF
+;****************************************************************************
+
+BankSwitch: macro
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ ld (hl), l
+ rrca
+ ld (hl), a
+ endm
+
+;****************************************************************************
+; LoadList [command $01]
+; Loads the pointer list
+;****************************************************************************
+
+LoadList:
+ ld hl, RAM_ComBank ; Get command parameters
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+ xor a ; Command parsed
+ ld (RAM_Command), a
+
+ ld a, c ; Do initial bank switch
+ ld de, $6000
+ ex de, hl
+ BankSwitch
+ ex de, hl
+
+ ld de, RAM_PointerList ; Where the pointer list starts
+
+.loop:
+ ld a, (hl) ; Get high address byte
+ or a ; Is it the end of the list?
+ jp z, .end ; If so, stop parsing list
+ ld (de), a ; Nope, store it in list
+
+ inc d ; Get address for next byte
+ inc l
+ jr nz, .noswitch1
+ inc h
+ jr nz, .noswitch1
+ inc c
+ ld a, c
+ ld h, $60
+ BankSwitch
+ ld h, $80
+.noswitch1:
+
+ ld a, (hl) ; Get low address byte
+ ld (de), a ; Store it in pointer list
+
+ inc d ; Get address for next byte
+ inc l
+ jr nz, .noswitch2
+ inc h
+ jr nz, .noswitch2
+ inc c
+ ld a, c
+ ld h, $60
+ BankSwitch
+ ld h, $80
+.noswitch2:
+
+ ld a, (hl) ; Get bank byte
+ ld (de), a ; Store it in pointer list
+
+ dec d ; Get address for next byte
+ dec d
+ inc e
+ inc l
+ jr nz, .noswitch3
+ inc h
+ jr nz, .noswitch3
+ inc c
+ ld a, c
+ ld h, $60
+ BankSwitch
+ ld h, $80
+.noswitch3:
+
+ jp .loop ; Go for next byte
+
+.end:
+ ld a, c ; Store current bank
+ ld (RAM_LastBank), a
+ jp IdleLoop ; End of subroutine
+
+;****************************************************************************
+; GetParam
+; Subroutine for getting the parameter byte
+;****************************************************************************
+
+GetParam:
+ ld a, (RAM_LastBank) ; Bank switch?
+ cp c
+ jp z, .noswitchp
+ ld a, c
+ ld (RAM_LastBank), a
+ exx
+ BankSwitch
+ exx
+.noswitchp:
+ ld b, (hl) ; Get volume
+
+ PollPCM
+
+ inc l ; Get next address
+ jp nz, .nonewbankp
+ inc h
+ jp nz, .nonewbankp
+ ld h, $80
+ inc c
+.nonewbankp:
+
+ ret ; End of subroutine
diff --git a/src-z80/core/sfx.z80 b/src-z80/core/sfx.z80
new file mode 100644
index 0000000..b7e26aa
--- /dev/null
+++ b/src-z80/core/sfx.z80
@@ -0,0 +1,316 @@
+;****************************************************************************
+; PlaySFX [command $02]
+; Plays a SFX
+;****************************************************************************
+
+PlaySFX:
+ PollPCM
+ call ClearSFX ; Clear SFX resources
+ PollPCM
+
+ ld a, (RAM_Status) ; Show SFX playback in Echo's status
+ or $01
+ ld (RAM_Status), a
+
+ PollPCM
+
+ ld hl, RAM_ComBank ; Get command parameters
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+
+ PollPCM
+
+ xor a ; Command parsed
+ ld (RAM_Command), a
+
+ ld hl, RAM_SFXData ; Set SFX as playing
+ ld (hl), $01
+ inc l ; No delays!
+ ld (hl), $01
+ inc l ; Store SFX start bank
+ ld (hl), c
+ inc l ; Store SFX start address (low)
+ ld (hl), e
+ inc l ; Store SFX start address (high)
+ ld (hl), d
+
+ PollPCM
+
+ ld hl, ProcessSFX ; Tell Echo to process SFX
+ ld (DoTick_SFX+1), hl
+
+ PollPCM
+ jp IdleLoop ; End of subroutine
+
+;****************************************************************************
+; ProcessSFX
+; Processes a tick for a SFX
+;****************************************************************************
+
+ProcessSFX:
+ PollPCM
+
+ ld hl, RAM_SFXData+1 ; SFX data address
+
+ ld a, (hl) ; Delaying?
+ dec a
+ jp z, .nodelay
+ ld (hl), a
+
+ jp DoTick_SFXSkip ; End of subroutine
+
+.nodelay:
+ PollPCM
+
+ inc l ; Get current address
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+ProcessSFXRun:
+ PollPCM ; Fetch next event
+ call GetParam
+ PollPCM
+
+ ld a, b ; Parse byte
+
+ cp $08
+ jp c, NoteOnFMSFX ; Events $00-$07: note on FM
+ cp $0B
+ jp c, NoteOnPSGSFX ; Events $08-$0A: note on PSG (square)
+ jp z, NoteOnNoiseSFX ; Event $0B: note on PSG (noise)
+ cp $0C
+ jp z, PlayPCMSFX ; Event $0C: note on PCM
+
+ PollPCM
+ ld a, b
+
+ cp $18
+ jp c, NoteOffFMSFX ; Events $10-$17: note off FM
+ cp $1C
+ jp c, NoteOffPSGSFX ; Events $18-$1B: note off PSG
+ jp z, StopPCMSFX ; Event $1C: note off PCM
+
+ PollPCM
+ ld a, b
+
+ cp $FE
+ jp z, SetDelaySFX ; Event $FE: set delay
+ cp $FF
+ jp z, StopSFXEvent ; Event $FF: stop SFX
+
+ PollPCM
+ ld a, b
+
+ cp $28
+ jp c, SetFMVolSFX ; Events $28-$2B: set FM volume
+ cp $2C
+ jp c, SetPSGVolSFX ; Events $28-$2B: set PSG volume
+
+ PollPCM
+ ld a, b
+
+ cp $38
+ jp c, SetNoteFMSFX ; Events $30-$37: set FM note
+ cp $3B
+ jp c, SetNotePSGSFX ; Events $38-$3A: set PSG note (square)
+ jp z, SetNoteNoiseSFX ; Event $3B: set PSG note (noise)
+
+ PollPCM
+ ld a, b
+
+ cp $48
+ jp c, LoadFMSFX ; Events $40-$47: load FM instrument
+ cp $4C
+ jp c, LoadPSGSFX ; Events $48-$4B: load PSG instrument
+
+ PollPCM
+ ld a, b
+
+ cp $E8
+ jp c, LockChannelFM ; Events $E0-$E7: lock FM channel
+ cp $EC
+ jp c, LockChannelPSG ; Events $E8-$EB: lock PSG channel
+ jp z, LockChannelPCM ; Event $EC: lock PCM channel
+
+ PollPCM
+ ld a, b
+
+ cp $F8 ; Events $F0-$F7: set FM parameters
+ jp c, SetFMParamSFX
+
+;****************************************************************************
+; StopSFX* [command $03, event $FF]
+; Stops SFX playback
+;****************************************************************************
+
+StopSFXEvent:
+ call StopSFX ; We're just a wrapper
+ jp DoTick_SFXSkip ; End of subroutine
+
+StopSFXCmd:
+ xor a ; Command parsed
+ ld (RAM_Command), a
+ call StopSFX ; We're just a wrapper
+ jp IdleLoop ; End of subroutine
+
+StopSFX:
+ PollPCM
+
+ ld a, (RAM_Status) ; Hide SFX playback in Echo's status
+ and $FE
+ ld (RAM_Status), a
+
+ PollPCM
+
+ xor a ; Stop playback
+ ld (RAM_SFXPlaying), a
+ ld hl, DoTick_SFXSkip
+ ld (DoTick_SFX+1), hl
+
+ PollPCM
+ call ClearSFX ; Clear SFX resources
+
+ PollPCM
+ ret ; End of subroutine
+
+;****************************************************************************
+; ClearSFX
+; Clears SFX resources
+;****************************************************************************
+
+ClearSFX:
+ ld a, (RAM_Locked+6) ; Stop PCM playback if needed
+ or a
+ call nz, StopPCM
+
+;----------------------------------------------------------------------------
+
+ ld b, 4 ; Look for locked PSG channels
+ ld de, RAM_Locked+11
+.unlockpsg:
+
+ PollPCM
+
+ ld a, (de) ; Check if this channel needs unlocking
+ or a
+ jr z, .psgfree
+ xor a
+ ld (de), a
+
+ PollPCM
+
+ ld a, b ; Restore BGM volume
+ rrca
+ rrca
+ rrca
+ rrca
+ dec a
+ ld h, RAM_PSGData>>8
+ ld l, a
+ ld c, (hl)
+ sub 15
+ ld l, a
+ ld (hl), c
+
+ PollPCM
+ push de
+
+ ld a, l ; Restore BGM envelope
+ add 8
+ ld l, a
+ add 12-8
+ ld e, a
+ ld d, h
+
+ PollPCM
+
+ ld a, (de)
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+
+ pop de
+ PollPCM
+
+.psgfree:
+ dec e ; Go for next PSG channel to unlock
+ djnz .unlockpsg
+
+;----------------------------------------------------------------------------
+
+ ld b, 8 ; Look for locked FM channels
+.unlockfm:
+
+ PollPCM
+
+ ld a, (de) ; Check if this channel needs unlocking
+ or a
+ jp z, .fmfree
+ xor a
+ ld (de), a
+
+ dec b ; Mute FM channel
+ ld (ix+0), $28
+ ld (ix+1), b
+
+ PollPCM
+
+ ld a, b
+ and $04 ; Determine which port to write
+ rrca
+ ld iyl, a
+
+ ld a, b ; Kill ADSR
+ call KillFM
+
+ PollPCM
+
+ ld hl, RAM_BGMFMVol ; Restore BGM FM volume
+ ld a, b
+ add l
+ ld l, a
+ ld c, (hl)
+ ld a, l
+ add 8
+ ld l, a
+ ld (hl), c
+
+ PollPCM
+
+ ld a, l ; Restore BGM FM instrument
+ sub 8*2
+ ld l, a
+ push bc
+ push de
+ push hl
+ ld a, b
+ ld b, (hl)
+ call LoadFMDirect
+ pop hl
+ pop de
+ pop bc
+
+ PollPCM
+ inc b
+.fmfree:
+ PollPCM
+ dec e ; Go for next FM channel to unlock
+ dec b
+ jp nz, .unlockfm
+
+;----------------------------------------------------------------------------
+
+ ret ; End of subroutine
diff --git a/src-z80/core/vars.z80 b/src-z80/core/vars.z80
new file mode 100644
index 0000000..ef9a676
--- /dev/null
+++ b/src-z80/core/vars.z80
@@ -0,0 +1,70 @@
+;****************************************************************************
+; Player variables
+;****************************************************************************
+
+ ds $100-($&$FF), $FF
+RAM_PSGData: ds 4*16 ; PSG envelope data
+ ; ds 1 ... Channel volume
+ ; ds 1 ... Global volume
+ ; ds 3 ... Current address
+ ; ds 3 ... Looping address
+ ; ds 3 ... Start address
+ ; ds 1 ... Padding
+ ; ds 3 ... BGM instrument address
+ ; ds 1 ... BGM channel volume
+
+RAM_BGMFMInstr: ds 8 ; FM instruments used by BGM
+RAM_BGMFMVol: ds 8 ; FM volumes used by BGM
+RAM_FMVolume: ds 8 ; Volume of each FM channel
+RAM_FMData: ds 8*5 ; FM info (for volume handling)
+ ; ds 8*1 ... Register $B0
+ ; ds 8*1 ... Register $40
+ ; ds 8*1 ... Register $44
+ ; ds 8*1 ... Register $48
+ ; ds 8*1 ... Register $4C
+
+RAM_LastBank: ds 1 ; Last accessed bank
+
+RAM_BGMData: ; Where BGM data starts
+RAM_BGMPlaying: ds 1 ; Set if a BGM is playing
+RAM_BGMDelay: ds 1 ; How many ticks to wait
+RAM_BGMBank: ds 1 ; Current BGM bank
+RAM_BGMAddress: ds 2 ; Current BGM address
+RAM_BGMLoopPoint: ds 3 ; BGM loop point
+
+RAM_SFXData: ; Where SFX data starts
+RAM_SFXPlaying: ds 1 ; Set if a SFX is playing
+RAM_SFXDelay: ds 1 ; How many ticks to wait
+RAM_SFXBank: ds 1 ; Current SFX bank
+RAM_SFXAddress: ds 2 ; Current SFX address
+
+RAM_Locked: ds 12 ; Locked channels
+
+RAM_Scratch: ds 32 ; Scratch bytes, may be useful when
+ ; buffering to speed up to avoid bank
+ ; switching conflicts
+
+;****************************************************************************
+; Pointer list starts being stored from here
+; $300 (768) bytes are needed to store the pointer list
+;
+; Format for a pointer list entry is as follows:
+; RAM_PointerList[$000+n] = address high
+; RAM_PointerList[$100+n] = address low
+; RAM_PointerList[$200+n] = bank
+;****************************************************************************
+
+ ds $100-($&$FF), $FF
+
+RAM_PointerList: equ $1C00
+
+;****************************************************************************
+; 68000 communication variables
+;****************************************************************************
+
+RAM_Stack: equ $1FF0 ; Where stack starts
+
+RAM_Status: equ $1FF0 ; Current playback status
+RAM_Command: equ $1FFF ; Command type
+RAM_ComAddr: equ $1FFD ; Command address parameter
+RAM_ComBank: equ $1FFC ; Command bank parameter
diff --git a/src-z80/player/fm.z80 b/src-z80/player/fm.z80
new file mode 100644
index 0000000..40cf5da
--- /dev/null
+++ b/src-z80/player/fm.z80
@@ -0,0 +1,784 @@
+;****************************************************************************
+; NoteOnFM
+; Does a "note on" for a FM channel
+;****************************************************************************
+
+NoteOnFMSFX:
+ call NoteOnFM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOnFMBGM:
+ ld b, a
+ PollPCM
+
+ push hl
+ ld a, b
+ and $07 ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play if locked
+
+ ld a, b
+ call NoteOnFM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOnFM:
+ and $07 ; Get channel ID
+ ld (ix+0), $28 ; Note off
+ ld (ix+1), a
+
+ ld b, a
+ ex af, af'
+ PollPCM
+
+ ld a, b ; Determine which port to write
+ and $04
+ rrca
+ ld iyl, a
+
+ PollPCM
+ call GetParam ; Get note
+ PollPCM
+
+ ex af, af'
+ push de
+ push hl
+
+ ld d, a
+ and $03 ; Index of first frequency register
+ add $A4
+ ld e, a
+
+ PollPCM
+
+ ld h, FMFreqTable>>8 ; Get address of note
+ ld a, b
+ and $1F
+ add FMFreqTable&$FF
+ ld l, a
+
+ PollPCM
+
+ ld a, b ; Set new frequency
+ and $E0
+ rrca
+ rrca
+ ld b, a
+ ld a, (hl)
+ or b
+ ld (iy+0), e
+ ld (iy+1), a
+
+ PollPCM
+
+ ld a, e
+ sub 4
+ ld e, a
+ dec l
+ ld a, (hl)
+ ld (iy+0), e
+ ld (iy+1), a
+
+ PollPCM
+
+ ld a, d ; Note on!
+ or $F0
+ ld (ix+0), $28
+ ld (ix+1), a
+
+ pop hl
+ pop de
+ ret ; End of subroutine
+
+;****************************************************************************
+; NoteOffFM
+; Does a "note off" for a FM channel
+;****************************************************************************
+
+NoteOffFMSFX:
+ call NoteOffFM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOffFMBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $07 ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip ; Don't stop if locked
+
+ ld a, b
+ call NoteOffFM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOffFM:
+ and $07 ; Get channel ID
+ ld (ix+0), $28 ; Note off
+ ld (ix+1), a
+ ret ; End of subroutine
+
+;****************************************************************************
+; SetFMNote*
+; Sets the note of a FM channel without "note on"
+;****************************************************************************
+
+SetNoteFMSFX:
+ call SetNoteFM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetNoteFMBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $07 ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip2 ; Don't change if locked
+
+ ld a, b
+ call SetNoteFM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+SetNoteFM:
+ push af
+ ld b, a
+ PollPCM
+
+ ld a, b ; Determine which port to write
+ and $04
+ rrca
+ ld iyl, a
+
+ PollPCM
+ call GetParam ; Get high byte
+ PollPCM
+
+ pop af
+ push de
+
+ and $07
+ ld d, a
+ and $03 ; Index of first frequency register
+ add $A4
+ ld e, a
+
+ PollPCM
+
+ ld (iy+0), e ; Load high byte
+ ld (iy+1), b
+
+ PollPCM ; Load low byte
+ call GetParam
+ PollPCM
+
+ ld a, e
+ sub 4
+ ld e, a
+
+ ld (iy+0), e
+ ld (iy+1), b
+
+ PollPCM
+ pop de
+ ret ; End of subroutine
+
+;****************************************************************************
+; LoadFM*
+; Loads a FM instrument
+;****************************************************************************
+
+LoadFMSFX:
+ call LoadFMEvent ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+LoadFMBGM:
+ and $07 ; Get channel ID
+ ld b, a
+ PollPCM
+
+ push de
+ push bc
+ ld a, b
+ ld de, RAM_BGMFMInstr ; Store instrument ID
+ add e
+ ld e, a
+ PollPCM
+ call GetParam
+ PollPCM
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ ld e, c
+ pop bc
+ ld c, e
+ pop de
+
+ PollPCM
+
+ push hl ; Check if channel is free
+ ld hl, RAM_Locked
+ ld a, b
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip ; Don't load if locked
+
+ PollPCM
+
+ ld a, b
+ and $07
+ push hl
+ ld hl, RAM_BGMFMInstr
+ add l
+ ld l, a
+ ld b, (hl)
+ pop hl
+
+ push af
+ PollPCM
+ pop af
+
+ call LoadFMDirect ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+LoadFMEvent:
+ and $07 ; Get channel ID
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get instrument ID
+ PollPCM
+ ex af, af'
+LoadFMDirect:
+
+ push af
+ and $04 ; Determine which port to write
+ rrca
+ ld iyl, a
+ PollPCM
+ pop af
+
+ push bc
+ push de
+ push hl
+
+ ld h, RAM_PointerList>>8 ; Get instrument address
+ ld l, b
+ ld d, (hl)
+ inc h
+ ld e, (hl)
+ inc h
+ ld c, (hl)
+ ex de, hl
+
+ push af
+ PollPCM
+
+ ld b, 28/4 ; Load FM instrument into our scratch buffer,
+ ld de, RAM_Scratch ; so we don't conflict with PCM bank
+.getinstr: ; switch (which would slow down A LOT)
+ ld a, b
+ ex af, af'
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ inc e
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ inc e
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ inc e
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ inc e
+ ex af, af'
+ ld b, a
+ djnz .getinstr
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+
+ PollPCM
+
+ pop af ; Kill ADSR
+ ld b, a
+ call KillFM
+ ld a, b
+
+ ld de, RAM_FMData ; Get address of FM data
+ and $07
+ add e
+ ld e, a
+
+ push af
+ and $03 ; Register index for operator
+ add $B0
+
+ ld hl, RAM_Scratch ; Get address of buffer with the instrument
+ ; data
+
+ ex af, af'
+ PollPCM ; Write operator
+ ex af, af'
+ ld (iy+0), a
+ ld b, (hl)
+ inc l
+ ld (iy+1), b
+
+ ex af, af' ; Store $B0 register in FM data buffer
+ ld a, b ; Also store it in the FM channel information
+ ld (de), a ; table since we need it when changing the
+ ld a, e ; FM channel volume
+ add 8
+ ld e, a
+ PollPCM
+ ex af, af'
+
+ sub $B0-$30
+
+ ex af, af' ; Write registers $30-$3C
+ PollPCM
+ ex af, af'
+
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+
+ ld b, 4 ; Write registers $40-$4C
+.reg40: ; Also store the registers in the FM channel
+ ld c, a ; information field since we'll need them to
+ push af ; set the FM channel volume
+ PollPCM
+ ld (iy+0), c
+ ld a, (hl)
+ ld (iy+1), a
+ ld (de), a
+ ld a, e
+ add 8
+ ld e, a
+ pop af
+ add 4
+ inc l
+ djnz .reg40
+
+ ld b, 20/4 ; Write registers $50-$5C
+.reg50:
+ ld c, b
+ ex af, af'
+ PollPCM
+ ex af, af'
+
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+
+ inc l
+ ld b, c
+ djnz .reg50
+
+ PollPCM
+
+ ex de, hl
+ ld a, l
+ sub 8*6
+ ld l, a
+ ld b, (hl)
+ add 8
+ ld l, a
+
+ PollPCM
+
+ pop af
+ pop hl
+ pop de
+ pop bc
+ ret
+
+ ;jp SetFMVolLoad ; End of subroutine
+
+;****************************************************************************
+; SetFMVol*
+; Sets the volume of a FM channel
+;****************************************************************************
+
+SetFMVolSFX:
+ call SetFMVolEvent ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetFMVolBGM:
+ and $07
+ ld b, a
+
+ PollPCM
+
+ push de
+ push bc
+ ld a, b
+ ld de, RAM_BGMFMVol ; Store BGM volume
+ add e
+ ld e, a
+ PollPCM
+ call GetParam
+ PollPCM
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ ld e, c
+ pop bc
+ ld c, e
+ pop de
+
+ PollPCM
+
+ push hl
+ ld a, b ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip ; Don't change if locked
+
+ PollPCM
+
+ ld a, b
+ push hl
+ ld hl, RAM_BGMFMVol
+ add l
+ ld l, a
+ ld b, (hl)
+ pop hl
+
+ push af
+ PollPCM
+ pop af
+
+ call SetFMVolBGMEvent ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+SetFMVolEvent:
+ push af
+ PollPCM
+ call GetParam ; Get new volume
+ PollPCM
+ pop af
+
+SetFMVolBGMEvent:
+ push bc
+ push de
+ push hl
+
+ ld hl, RAM_FMVolume ; Store new volume
+ and $07
+ add l
+ ld l, a
+ ld (hl), b
+
+ ex af, af'
+ PollPCM
+ ld a, l ; Get address of FM data
+ add 8
+ ld l, a
+ ex af, af'
+
+SetFMVolLoad:
+ push af
+ ;ld iyh, $40 ; Determine which port to write
+ and $04
+ rrca
+ ld iyl, a
+ PollPCM
+ pop af
+
+ and $03 ; Index of first volume register
+ add $40
+ ld c, a
+
+ PollPCM
+
+ ld a, (hl) ; Get algorithm
+ and $07
+ ld e, a
+
+ PollPCM
+
+ ld a, l
+ add 8
+ ld l, a
+ ld a, e ; Process operator #1
+ cp $07
+ jr c, .noop1
+ ld a, (hl)
+ add b
+ cp $7F
+ jr c, .notooloud1
+ ld a, $7F
+.notooloud1:
+ ld (iy+0), c
+ ld (iy+1), a
+.noop1:
+ ld a, c
+ add 4
+ ld c, a
+
+ PollPCM
+
+ ld a, l
+ add 8
+ ld l, a
+ ld a, e ; Process operator #2
+ cp $05
+ jr c, .noop2
+ ld a, (hl)
+ add b
+ cp $7F
+ jr c, .notooloud2
+ ld a, $7F
+.notooloud2:
+ ld (iy+0), c
+ ld (iy+1), a
+.noop2:
+ ld a, c
+ add 4
+ ld c, a
+
+ PollPCM
+
+ ld a, l
+ add 8
+ ld l, a
+ ld a, e ; Process operator #3
+ cp $04
+ jr c, .noop3
+ ld a, (hl)
+ add b
+ cp $7F
+ jr c, .notooloud3
+ ld a, $7F
+.notooloud3:
+ ld (iy+0), c
+ ld (iy+1), a
+.noop3:
+ ld a, c
+ add 4
+ ld c, a
+
+ PollPCM
+
+ ld a, l
+ add 8
+ ld l, a
+ ld a, (hl) ; Process operator #4
+ add b
+ cp $7F
+ jr c, .notooloud4
+ ld a, $7F
+.notooloud4:
+ ld (iy+0), c
+ ld (iy+1), a
+
+ PollPCM
+
+ pop hl
+ pop de
+ pop bc
+ ret ; End of subroutine
+
+;****************************************************************************
+; SetFMParam*
+; Sets the different parameters of a FM channel
+;****************************************************************************
+
+SetFMParamSFX:
+ call SetFMParam ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetFMParamBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $07 ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't modify if locked
+
+ ld a, b
+ call SetFMParam ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+SetFMParam:
+ ld b, a
+ PollPCM
+
+ ;ld iyh, $40 ; Determine which port to write
+ ld a, b
+ and $04
+ rrca
+ ld iyl, a
+
+ ld a, b
+ ex af, af'
+ PollPCM
+ call GetParam ; Get parameters
+ PollPCM
+ ex af, af'
+
+ and $03 ; Get channel ID
+ add $B4
+ ld (iy+0), a ; Set new parameters
+ ld (iy+1), b
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; LockChannelFM [events $E0-$E7]
+; Locks a FM channel
+;****************************************************************************
+
+LockChannelFM:
+ and $07
+ ld b, a
+ PollPCM
+
+ push hl
+ ld h, RAM_Locked>>8 ; Get address of channel to lock
+ ld a, b
+ add RAM_Locked&$FF
+ ld l, a
+ ld (hl), $01 ; Lock channel
+ pop hl
+
+ PollPCM
+
+ ld a, b ; Determine which port to write
+ and $04
+ rrca
+ ld iyl, a
+
+ ld a, b ; Reset stereo
+ and $03
+ add a
+ add a
+ add $B4
+ ld (iy+0), a
+ ld (iy+1), $C0
+
+ jp ProcessSFXRun ; End of subroutine
+
+;****************************************************************************
+; KillFM
+; Kills a FM channel
+;****************************************************************************
+
+KillFM:
+ push af
+ push de
+
+ and $03 ; Load dummy FM instrument
+ add $40
+ ld c, 6
+ ld hl, DummyFMInstr
+.loaddummy:
+ ex af, af'
+ PollPCM
+ ex af, af'
+ ld e, (hl)
+
+ ld (iy+0), a
+ ld (iy+1), e
+ add 4
+ ld (iy+0), a
+ ld (iy+1), e
+ add 4
+ ld (iy+0), a
+ ld (iy+1), e
+ add 4
+ ld (iy+0), a
+ ld (iy+1), e
+ add 4
+
+ inc l
+ dec c
+ jp nz, .loaddummy
+
+ pop de
+ PollPCM
+ pop af
+
+ ld c, a ; Reset ADSR
+ or $F0
+ ld (ix+0), $28
+ ld (ix+1), a
+ ld (ix+0), $28
+ ld (ix+1), c
+
+ PollPCM
+ ret ; End of subroutine
diff --git a/src-z80/player/freq.z80 b/src-z80/player/freq.z80
new file mode 100644
index 0000000..a469c0b
--- /dev/null
+++ b/src-z80/player/freq.z80
@@ -0,0 +1,105 @@
+;****************************************************************************
+; PSGFreqTable
+; Frequency table for all PSG notes
+;****************************************************************************
+
+ ds $100-($&$FF), $FF
+PSGFreqTable:
+ db $83, $35 ; C-4 - 851
+ db $83, $32 ; C#4 - 803
+ db $86, $2F ; D-4 - 758
+ db $8B, $2C ; D#4 - 715
+ db $83, $2A ; E-4 - 675
+ db $8D, $27 ; F-4 - 637
+ db $89, $25 ; F#4 - 601
+ db $88, $23 ; G-4 - 568
+ db $88, $21 ; G#4 - 536
+ db $8A, $1F ; A-4 - 506
+ db $8D, $1D ; A#4 - 477
+ db $82, $1C ; B-4 - 450
+ db $89, $1A ; C-5 - 425
+ db $81, $19 ; C#5 - 401
+ db $8B, $17 ; D-5 - 379
+ db $85, $16 ; D#5 - 357
+ db $81, $15 ; E-5 - 337
+ db $8E, $13 ; F-5 - 318
+ db $8C, $12 ; F#5 - 300
+ db $8C, $11 ; G-5 - 284
+ db $8C, $10 ; G#5 - 268
+ db $8D, $0F ; A-5 - 253
+ db $8E, $0E ; A#5 - 238
+ db $81, $0E ; B-5 - 225
+ db $84, $0D ; C-6 - 212
+ db $88, $0C ; C#6 - 200
+ db $8D, $0B ; D-6 - 189
+ db $82, $0B ; D#6 - 178
+ db $88, $0A ; E-6 - 168
+ db $8F, $09 ; F-6 - 159
+ db $86, $09 ; F#6 - 150
+ db $8E, $08 ; G-6 - 142
+ db $86, $08 ; G#6 - 134
+ db $8E, $07 ; A-6 - 126
+ db $87, $07 ; A#6 - 119
+ db $80, $07 ; B-6 - 112
+ db $8A, $06 ; C-7 - 106
+ db $84, $06 ; C#7 - 100
+ db $8E, $05 ; D-7 - 94
+ db $89, $05 ; D#7 - 89
+ db $84, $05 ; E-7 - 84
+ db $8F, $04 ; F-7 - 79
+ db $8B, $04 ; F#7 - 75
+ db $87, $04 ; G-7 - 71
+ db $83, $04 ; G#7 - 67
+ db $8F, $03 ; A-7 - 63
+ db $8B, $03 ; A#7 - 59
+ db $88, $03 ; B-7 - 56
+ db $85, $03 ; C-8 - 53
+ db $82, $03 ; C#8 - 50
+ db $8F, $02 ; D-8 - 47
+ db $8C, $02 ; D#8 - 44
+ db $8A, $02 ; E-8 - 42
+ db $87, $02 ; F-8 - 39
+ db $85, $02 ; F#8 - 37
+ db $83, $02 ; G-8 - 35
+ db $81, $02 ; G#8 - 33
+ db $8F, $01 ; A-8 - 31
+ db $8D, $01 ; A#8 - 29
+ db $8C, $01 ; B-8 - 28
+ db $8A, $01 ; C-9 - 26
+ db $89, $01 ; C#9 - 25
+ db $87, $01 ; D-9 - 23
+ db $86, $01 ; D#9 - 22
+ db $85, $01 ; E-9 - 21
+ db $83, $01 ; F-9 - 19
+ db $82, $01 ; F#9 - 18
+ db $81, $01 ; G-9 - 17
+ db $80, $01 ; G#9 - 16
+ db $8F, $00 ; A-9 - 15
+ db $8E, $00 ; A#9 - 14
+ db $8E, $00 ; B-9 - 14
+
+;****************************************************************************
+; FMFreqTable
+; Frequency table for all FM notes
+;****************************************************************************
+
+FMFreqTable:
+ dw 644, 681, 722, 765
+ dw 810, 858, 910, 964
+ dw 1021, 1081, 1146, 1214
+
+;****************************************************************************
+; DummyFMInstr
+; Dummy FM instrument to mute FM channels...
+;
+; To-do: put this in its own file? Although I'd like for this table to stay
+; in this area in memory
+;****************************************************************************
+
+DummyFMInstr:
+ db $7F ; $40..$4C
+ db $1F ; $50..$5C
+ db $1F ; $60..$6C
+ db $1F ; $70..$7C
+ db $0F ; $80..$8C
+ db $00 ; $90..$9C
diff --git a/src-z80/player/misc.z80 b/src-z80/player/misc.z80
new file mode 100644
index 0000000..6373ab0
--- /dev/null
+++ b/src-z80/player/misc.z80
@@ -0,0 +1,31 @@
+;****************************************************************************
+; SetDelay* [event $FE]
+; Adds a delay in playback
+;****************************************************************************
+
+SetDelaySFX:
+ call SetDelay ; We're just a wrapper
+ jp DoTick_SFXSkip ; End of subroutine
+
+SetDelayBGM:
+ call SetDelay ; We're just a wrapper
+ jp DoTick_BGMSkip ; End of subroutine
+
+SetDelay:
+ PollPCM
+ call GetParam ; Get delay
+ PollPCM
+
+ ex de, hl
+ ld (hl), d ; Store new address
+ dec l
+ ld (hl), e
+ dec l
+ ld (hl), c
+
+ PollPCM
+
+ dec l ; Store new delay
+ ld (hl), b
+
+ ret ; End of subroutine
diff --git a/src-z80/player/pcm.z80 b/src-z80/player/pcm.z80
new file mode 100644
index 0000000..44bc236
--- /dev/null
+++ b/src-z80/player/pcm.z80
@@ -0,0 +1,139 @@
+;****************************************************************************
+; PlayPCM*
+; Plays a PCM sample
+;****************************************************************************
+
+PlayPCMSFX:
+ call PlayPCM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+PlayPCMBGM:
+ PollPCM
+
+ ld a, (RAM_Locked+6) ; Check if channel is free
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play sample if locked
+
+ call PlayPCM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+PlayPCM:
+ call GetParam ; Get sample ID
+
+ ld a, b
+ exx ; We'll modify PCM data now
+ ld b, $01 ; Play PCM!
+
+ ld h, RAM_PointerList>>8 ; Get offset in pointer list
+ ld l, a
+
+ ld d, (hl) ; Get PCM address
+ inc h
+ ld e, (hl)
+ inc h
+ ld c, (hl)
+
+ ld hl, $6000 ; Restore $6000 back to HL
+ exx ; Back to standard variables
+
+ ld (ix+0), $2B ; Turn on DAC
+ ld (ix+1), $80
+ ld (ix+0), $2A
+ ld (ix+1), $80
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; UpdatePCM
+; Updates PCM output upon a timer event
+;****************************************************************************
+
+UpdatePCM:
+ ;ld (ix+0), $24 ; Reset timer
+ ;ld (ix+1), $FE
+ ;ld (ix+0), $25
+ ;ld (ix+1), $03
+ ld (ix+0), $27
+ ld (ix+1), $1F
+
+ exx ; Switch to PCM registers
+
+ ld a, b ; Do any playback?
+ or a
+ jr z, .nopcm
+
+ ld a, (RAM_LastBank) ; Bank switch?
+ cp c
+ jp z, .noswitchu
+ ld a, c
+ ld (RAM_LastBank), a
+ BankSwitch
+.noswitchu:
+
+ ld a, (de) ; Get sample
+ inc a ; Is it end of waveform?
+ jr z, .stop ; If so, stop
+ ld (ix+0), $2A ; Nope, send sample to YM2612
+ ld (ix+1), a
+
+ inc e ; Get address for next sample
+ jr nz, .nopcm
+ inc d
+ jr nz, .nopcm
+ ld d, $80
+ inc c
+
+.nopcm:
+ exx ; Go back to normal registers
+ ret ; End of subroutine
+
+.stop:
+ ld b, $00 ; Stop playback
+ ld (ix+0), $2A ; Turn off DAC
+ ld (ix+1), $80
+ ld (ix+0), $2B
+ ld (ix+1), $00
+ exx ; Go back to normal registers
+ ret ; End of subroutine
+
+;****************************************************************************
+; StopPCM*
+; Stops a PCM sample
+;****************************************************************************
+
+StopPCMSFX:
+ call StopPCM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+StopPCMBGM:
+ PollPCM
+
+ ld a, (RAM_Locked+6) ; Check if channel is free
+ or a
+ jp nz, ProcessBGMRun ; Don't stop sample if locked
+
+ call StopPCM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+StopPCM:
+ exx ; Stop PCM playback
+ ld b, $00
+ exx
+
+ ld (ix+0), $2B ; Disable DAC
+ ld (ix+1), $00
+
+ ret ; End of subroutine
+
+
+;****************************************************************************
+; LockChannelPCM [event $EC]
+; Locks the PCM channel
+;****************************************************************************
+
+LockChannelPCM:
+ ld a, $01 ; Lock PCM channel
+ ld (RAM_Locked+6), a
+
+ call StopPCM ; Stop PCM playback
+ jp ProcessSFXRun ; End of subroutine
diff --git a/src-z80/player/psg.z80 b/src-z80/player/psg.z80
new file mode 100644
index 0000000..46df82a
--- /dev/null
+++ b/src-z80/player/psg.z80
@@ -0,0 +1,667 @@
+;****************************************************************************
+; UpdatePSG
+; Updates PSG output
+;****************************************************************************
+
+UpdatePSG:
+ ld hl, RAM_PSGData+48 ; PSG envelope data of *last* channel
+ ld b, 3 ; Go through all channels
+.loop:
+ push bc
+
+ ld a, (hl) ; Get channel volume
+ bit 7, a
+ jr nz, .noskip
+ ld b, $0F
+ inc l
+ jp .skip
+.noskip:
+ and $7F
+ ld b, a
+
+ inc l ; Add global volume
+ ld a, (hl)
+ add b
+ ld b, a
+
+ PollPCM
+ push bc
+
+ inc l ; Get current address of envelope
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+.readenv:
+ PollPCM
+ call GetParam ; Get next byte
+ PollPCM
+
+ ld a, b
+ cp $FE ; Set loop point?
+ jp z, .envsetloop
+ cp $FF ; Loop envelope?
+ jp z, .envloop
+
+ ld iyl, b ; Keep byte safe somewhere...
+ PollPCM
+
+ ex de, hl ; Store new address
+ ld (hl), d
+ dec l
+ ld (hl), e
+ dec l
+ ld (hl), c
+ dec l
+
+ pop bc
+ PollPCM
+
+ db $FD,$7D ; ld a, iyl ; Mix envelope with volume
+ add b
+ ld b, a
+
+ cp $10 ; Check for overflow
+ jr c, .notmute
+ ld b, $0F
+.notmute:
+
+.skip:
+ PollPCM
+
+ ld a, b ; Set PSG channel volume
+ rlca
+ rlca
+ rlca
+ pop bc
+ or b
+ rrca
+ rrca
+ rrca
+ or $90
+ ld ($7F11), a
+
+ ld a, l ; Go for next channel
+ sub 16+1
+ ld l, a
+ PollPCM
+ dec b
+ jp p, .loop
+
+ jp DoTick_PSGSkip ; End of subroutine
+
+.envsetloop:
+ PollPCM
+ inc e ; Where we store the loop point
+
+ ex de, hl ; Store loop point
+ ld (hl), c
+ inc l
+ ld (hl), e
+ inc l
+ ld (hl), d
+ ex de, hl
+
+ dec e ; Back to where we were...
+ dec e
+ dec e
+ jp .readenv ; Go for next byte
+
+.envloop:
+ PollPCM
+ inc e ; Where we store the loop point
+
+ ex de, hl ; Retrieve loop point
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+ dec e ; Back to where we were...
+ dec e
+ dec e
+ jp .readenv ; Go for next byte
+
+;****************************************************************************
+; NoteOnPSG*
+; Does a "note on" for a PSG channel
+;****************************************************************************
+
+NoteOnPSGSFX:
+ call NoteOnPSG ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOnPSGBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $03 ; Check if channel is free
+ ld hl, RAM_Locked+8
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play if locked
+
+ ld a, b
+ call NoteOnPSG ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOnPSG:
+ and $03
+ ld b, a
+ ;push af
+ ex af, af'
+ PollPCM
+
+ push hl ; Set channel volume
+ ld h, RAM_PSGData>>8
+ ld a, b
+ rrca
+ rrca
+ rrca
+ rrca
+ ld l, a
+ ld a, (hl)
+ or $80
+ ld (hl), a
+
+ PollPCM
+ push de
+
+ inc l ; Now we'll reset the envelope address...
+ inc l
+ ld d, h
+ ld a, l
+ add 6
+ ld e, a
+
+ PollPCM
+
+ ld a, (de) ; Reset envelope address
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+
+ pop de
+ pop hl
+
+ PollPCM
+ call GetParam ; Get note
+ PollPCM
+
+ ;pop af
+ ex af, af'
+ push hl
+ push de
+ ld h, PSGFreqTable>>8 ; Get address of frequency data
+ ld l, b
+ ld de, $7F11
+
+ rrca ; Set new frequency
+ rrca
+ rrca
+ ld b, (hl)
+ or b
+ ld (de), a
+ inc l
+ ld a, (hl)
+ ld (de), a
+ pop de
+ pop hl
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; NoteOnNoise*
+; Does a "note on" for the noise PSG channel
+;****************************************************************************
+
+NoteOnNoiseSFX:
+ call NoteOnNoise ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOnNoiseBGM:
+ ld a, (RAM_Locked+11) ; Check if channel is free
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play if locked
+
+ call NoteOnNoise ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOnNoise:
+ PollPCM
+ push hl
+
+ ld hl, RAM_PSGData+48 ; Set channel volume
+ ld a, (hl)
+ or $80
+ ld (hl), a
+
+ PollPCM
+ push de
+
+ inc l ; Now we'll reset the envelope address...
+ inc l
+ ld d, h
+ ld a, l
+ add 6
+ ld e, a
+
+ PollPCM
+
+ ld a, (de) ; Reset envelope address
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+
+ pop de
+ pop hl
+
+SetNoteNoise:
+ PollPCM
+ call GetParam ; Get noise type
+ PollPCM
+
+ ld a, $E0 ; Set new noise type
+ or b
+ ld ($7F11), a
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; NoteOffPSG*
+; Does a "note off" for a PSG channel
+;****************************************************************************
+
+NoteOffPSGSFX:
+ call NoteOffPSG ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOffPSGBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $03 ; Check if channel is free
+ ld hl, RAM_Locked+8
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMRun ; Don't stop if locked
+
+ ld a, b
+ call NoteOffPSG ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOffPSG:
+ and $03
+ ld b, a
+ PollPCM
+
+ push hl ; Mark channel as not playing
+ ld h, RAM_PSGData>>8
+ ld a, b
+ rrca
+ rrca
+ rrca
+ rrca
+ ld l, a
+ ld a, (hl)
+ and $7F
+ ld (hl), a
+ pop hl
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; SetPSGVol*
+; Sets the volume of a PSG channel
+;****************************************************************************
+
+SetPSGVolSFX:
+ and $03 ; Get channel ID
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get volume
+ PollPCM
+ ex af, af'
+
+ push hl
+ ld h, RAM_PSGData>>8 ; Set new volume
+ rrca
+ rrca
+ rrca
+ rrca
+ ld l, a
+ ld a, (hl)
+ and $80
+ or b
+ ld (hl), a
+ pop hl
+
+ jp ProcessSFXRun ; End of subroutine
+
+SetPSGVolBGM:
+ and $03 ; Get channel ID
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get volume
+ PollPCM
+ ex af, af'
+
+ push de
+ push hl
+
+ push af
+ ld de, RAM_Locked+8 ; Check if channel is locked
+ add e ; Keep results for later
+ ld e, a
+ ld a, (de)
+ ld e, a
+
+ PollPCM
+ pop af
+
+ ld h, RAM_PSGData>>8 ; Store new volume
+ rrca
+ rrca
+ rrca
+ rrca
+ add 15
+ ld l, a
+ ld (hl), b
+
+ PollPCM
+
+ ld a, e ; Is channel locked?
+ or a
+ jr nz, .nosetvol
+
+ ld a, l ; Set new volume
+ sub 15
+ ld l, a
+ ld a, (hl)
+ and $80
+ or b
+ ld (hl), a
+
+ PollPCM
+
+.nosetvol:
+ pop hl
+ pop de
+
+ jp ProcessBGMRun ; End of subroutine
+
+;****************************************************************************
+; LoadPSG*
+; Loads a PSG instrument
+;****************************************************************************
+
+LoadPSGSFX:
+ and $03 ; Get channel number
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get instrument ID
+ PollPCM
+ ex af, af'
+
+ push de
+ push hl
+
+ ld d, RAM_PointerList>>8 ; Get position in pointer list
+ ld e, b
+
+ ld h, RAM_PSGData>>8 ; Where to store address
+ rrca
+ rrca
+ rrca
+ rrca
+ add 8+2+(RAM_PSGData&$FF)
+ ld l, a
+
+ PollPCM
+
+ ld a, (de) ; Store PSG envelope start address
+ ld (hl), a
+ inc d
+ dec l
+ ld a, (de)
+ ld (hl), a
+ inc d
+ dec l
+ ld a, (de)
+ ld (hl), a
+
+ PollPCM
+
+ ld a, l ; Reset volume
+ sub 8
+ ld l, a
+ ld (hl), $00
+
+ pop hl
+ pop de
+ jp ProcessSFXRun ; End of subroutine
+
+;----------------------------------------------------------------------------
+
+LoadPSGBGM:
+ and $03 ; Get channel number
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get instrument ID
+ PollPCM
+ ex af, af'
+
+ push de
+ push hl
+
+ ld d, RAM_PointerList>>8 ; Get position in pointer list
+ ld e, b
+
+ ld hl, RAM_Locked+8 ; Get if channel is locked
+ push af
+ add l
+ ld l, a
+ pop af
+ ld b, (hl)
+
+ ld h, RAM_PSGData>>8 ; Where to store BGM instrument data
+ rrca
+ rrca
+ rrca
+ rrca
+ add 15
+ ld l, a
+
+ PollPCM
+
+ ld (hl), a ; Reset volume for BGM
+ dec l
+
+ ld a, (de) ; Store PSG envelope address for BGM
+ ld (hl), a
+ inc d
+ dec l
+ ld a, (de)
+ ld (hl), a
+ inc d
+ dec l
+ ld a, (de)
+ ld (hl), a
+
+ PollPCM
+
+ ld a, b ; Don't set PSG envelope if locked
+ or a
+ jp z, .noloadlocked
+ pop hl
+ pop de
+ jp ProcessBGMRun
+.noloadlocked:
+ PollPCM
+
+ ld d, h ; Set PSG envelope
+ ld a, l
+ sub 12-8
+ ld e, a
+
+ ld a, (hl)
+ ld (de), a
+ inc l
+ inc e
+ ld a, (hl)
+ ld (de), a
+ inc l
+ inc e
+ ld a, (hl)
+ ld (de), a
+
+ PollPCM
+
+ ld a, l ; Reset volume
+ sub 8
+ ld l, a
+ ld (hl), $00
+
+ pop hl
+ pop de
+ jp ProcessBGMRun ; End of subroutine
+
+;****************************************************************************
+; SetNotePSG*
+; Sets the note of a PSG channel without "note on"
+;****************************************************************************
+
+SetNotePSGSFX:
+ call SetNotePSG ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetNotePSGBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $0F ; Check if channel is free
+ ;ld hl, RAM_Locked
+ ;add l
+ ld h, RAM_Locked>>8
+ add RAM_Locked&$FF
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip2 ; Don't play if locked
+
+ ld a, b
+ call SetNotePSG ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+SetNotePSG:
+ and $03 ; Get channel number
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get first byte
+ PollPCM
+ ex af, af'
+
+ push de ; PSG port address
+ ld de, $7F11
+
+ rrca ; Set first frequency byte
+ rrca
+ rrca
+ or b
+ or $80
+ ld (de), a
+
+ PollPCM
+ call GetParam ; Get second byte
+ PollPCM
+
+ ex de, hl
+ ld (hl), b ; Set second frequency byte
+ ex de, hl
+ pop de
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; SetNoteNoise*
+; Sets the note of the noise PSG channel without "note on"
+;****************************************************************************
+
+SetNoteNoiseSFX:
+ call SetNoteNoise ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetNoteNoiseBGM:
+ ld a, (RAM_Locked+11) ; Check if channel is free
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play if locked
+
+ call SetNoteNoise ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+;****************************************************************************
+; LockChannelPSG [events $E8-$EB]
+; Locks a PSG channel
+;****************************************************************************
+
+LockChannelPSG:
+ ld b, a
+ PollPCM
+ push hl
+
+ ld h, RAM_Locked>>8 ; Get address of channel to lock
+ ld a, b
+ and $0F
+ add RAM_Locked&$FF
+ ld l, a
+
+ ld (hl), $01 ; Lock channel
+
+ PollPCM
+
+ ld a, b ; Stop channel
+ rrca
+ rrca
+ rrca
+ rrca
+ ld l, a
+ ld h, RAM_PSGData>>8
+ ld (hl), $00
+
+ pop hl
+ jp ProcessSFXRun ; End of subroutine