├── data ├── fm │ ├── dguitar.eif │ ├── saw.eif │ ├── sawf.eif │ ├── square.eif │ ├── squaref.eif │ ├── flute.eif │ ├── string.eif │ ├── bass.eif │ ├── ebass.eif │ ├── eguitar.eif │ └── piano.eif ├── bg.bin ├── font.bin ├── music │ ├── hol.esf │ ├── midnas.esf │ ├── minion.esf │ ├── nelpel.esf │ ├── doomsday.esf │ ├── test-psg.esf │ ├── megajysays.esf │ ├── test-drums.esf │ ├── test-flute.esf │ ├── test-piano.esf │ ├── test-squsaw-1ch.esf │ └── test-squsaw-2ch.esf └── pcm │ ├── kick.ewf │ └── snare.ewf ├── built ├── prog-z80.bin ├── old-builds │ ├── prog-z80-1.0.bin │ ├── prog-z80-1.1.bin │ ├── prog-z80-1.2.bin │ ├── prog-z80-1.21.bin │ ├── prog-z80-1.3.bin │ ├── prog-z80-1.4.bin │ ├── prog-z80-1.5.bin │ ├── prog-z80-1.62.bin │ ├── prog-z80-1.63.bin │ ├── prog-z80-1.64.bin │ └── prog-z80-1.65.bin └── broken-builds │ ├── massive-crash.bin │ ├── echo-timer-version-a.bin │ ├── echo-timer-version-b.bin │ ├── echo-timer-version-c.bin │ └── echo-timer-version-d.bin ├── doc ├── ewf.txt ├── eif.txt ├── eef.txt ├── api-asm.68k ├── api-c.txt └── esf.txt ├── src-z80 ├── build.z80 ├── core │ ├── macro.z80 │ ├── direct.z80 │ ├── vars.z80 │ ├── sfx.z80 │ ├── main.z80 │ └── bgm.z80 └── player │ ├── misc.z80 │ ├── freq.z80 │ ├── pcm.z80 │ └── psg.z80 ├── tester ├── core │ ├── vars.68k │ ├── header.68k │ ├── songlist.68k │ ├── menu.68k │ └── entry.68k ├── video │ ├── vsync.68k │ ├── bg.68k │ └── text.68k ├── build.68k ├── sound │ ├── sfxs.68k │ ├── bgms.68k │ ├── esf.68k │ ├── list.68k │ └── echo.68k └── input │ └── joypad.68k ├── LICENSE ├── c ├── echo.h ├── tool │ └── blob2c.c └── echo.c ├── README └── src-68k └── esf.68k /data/fm/dguitar.eif: -------------------------------------------------------------------------------- 1 | ,  -------------------------------------------------------------------------------- /data/fm/saw.eif: -------------------------------------------------------------------------------- 1 | 8  -------------------------------------------------------------------------------- /data/fm/sawf.eif: -------------------------------------------------------------------------------- 1 | 8$$$  -------------------------------------------------------------------------------- /data/fm/square.eif: -------------------------------------------------------------------------------- 1 | 8$$$ -------------------------------------------------------------------------------- /data/fm/squaref.eif: -------------------------------------------------------------------------------- 1 | 8$$$  -------------------------------------------------------------------------------- /data/fm/flute.eif: -------------------------------------------------------------------------------- 1 | " 2 | '' -------------------------------------------------------------------------------- /data/fm/string.eif: -------------------------------------------------------------------------------- 1 |  2 | 3 | && -------------------------------------------------------------------------------- /data/bg.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/bg.bin -------------------------------------------------------------------------------- /data/font.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/font.bin -------------------------------------------------------------------------------- /data/fm/bass.eif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/fm/bass.eif -------------------------------------------------------------------------------- /built/prog-z80.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/prog-z80.bin -------------------------------------------------------------------------------- /data/fm/ebass.eif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/fm/ebass.eif -------------------------------------------------------------------------------- /data/fm/eguitar.eif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/fm/eguitar.eif -------------------------------------------------------------------------------- /data/fm/piano.eif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/fm/piano.eif -------------------------------------------------------------------------------- /data/music/hol.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/hol.esf -------------------------------------------------------------------------------- /data/pcm/kick.ewf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/pcm/kick.ewf -------------------------------------------------------------------------------- /data/pcm/snare.ewf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/pcm/snare.ewf -------------------------------------------------------------------------------- /data/music/midnas.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/midnas.esf -------------------------------------------------------------------------------- /data/music/minion.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/minion.esf -------------------------------------------------------------------------------- /data/music/nelpel.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/nelpel.esf -------------------------------------------------------------------------------- /data/music/doomsday.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/doomsday.esf -------------------------------------------------------------------------------- /data/music/test-psg.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/test-psg.esf -------------------------------------------------------------------------------- /data/music/megajysays.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/megajysays.esf -------------------------------------------------------------------------------- /data/music/test-drums.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/test-drums.esf -------------------------------------------------------------------------------- /data/music/test-flute.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/test-flute.esf -------------------------------------------------------------------------------- /data/music/test-piano.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/test-piano.esf -------------------------------------------------------------------------------- /data/music/test-squsaw-1ch.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/test-squsaw-1ch.esf -------------------------------------------------------------------------------- /data/music/test-squsaw-2ch.esf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/data/music/test-squsaw-2ch.esf -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.0.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.1.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.2.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.21.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.21.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.3.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.4.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.5.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.5.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.62.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.62.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.63.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.63.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.64.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.64.bin -------------------------------------------------------------------------------- /built/old-builds/prog-z80-1.65.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/old-builds/prog-z80-1.65.bin -------------------------------------------------------------------------------- /built/broken-builds/massive-crash.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/broken-builds/massive-crash.bin -------------------------------------------------------------------------------- /built/broken-builds/echo-timer-version-a.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/broken-builds/echo-timer-version-a.bin -------------------------------------------------------------------------------- /built/broken-builds/echo-timer-version-b.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/broken-builds/echo-timer-version-b.bin -------------------------------------------------------------------------------- /built/broken-builds/echo-timer-version-c.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/broken-builds/echo-timer-version-c.bin -------------------------------------------------------------------------------- /built/broken-builds/echo-timer-version-d.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sikthehedgehog/Echo/HEAD/built/broken-builds/echo-timer-version-d.bin -------------------------------------------------------------------------------- /doc/ewf.txt: -------------------------------------------------------------------------------- 1 | ============================================================================= 2 | 3 | OVERVIEW 4 | 5 | EWF stands for "Echo Waveform Format" and it's the format in which PCM 6 | instruments are stored. 7 | 8 | FORMAT 9 | 10 | There isn't much to it. PCM data is stored as unsigned 8-bit, 10250 Hz, 11 | mono. The bytes specifying the waveform data contain values ranging from 12 | $00 to $FE. When a byte with value $FF is found, this is the end of the 13 | waveform. 14 | 15 | ============================================================================= 16 | -------------------------------------------------------------------------------- /src-z80/build.z80: -------------------------------------------------------------------------------- 1 | jp EntryPoint ; Jump to the entry point 2 | db 0,0,0,0,0 ; Padding to 0008h 3 | 4 | include "src-z80/core/macro.z80" 5 | include "src-z80/player/pcm.z80" 6 | include "src-z80/core/main.z80" 7 | include "src-z80/core/bgm.z80" 8 | include "src-z80/core/sfx.z80" 9 | include "src-z80/core/direct.z80" 10 | include "src-z80/player/fm.z80" 11 | include "src-z80/player/psg.z80" 12 | include "src-z80/player/misc.z80" 13 | 14 | include "src-z80/player/freq.z80" 15 | include "src-z80/core/vars.z80" 16 | -------------------------------------------------------------------------------- /tester/core/vars.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; Where program variables are defined 3 | ;**************************************************************************** 4 | 5 | rsset $FF0000 6 | 7 | RAM_JoyHold rs.b 1 ; Current "held" joypad status 8 | RAM_JoyPress rs.b 1 ; Current "pressed" joypad status 9 | RAM_LArrowAnim rs.w 1 ; Animation for left arrow 10 | RAM_RArrowAnim rs.w 1 ; Animation for right arrow 11 | RAM_BGAnim rs.w 1 ; Animation for background 12 | RAM_CurrSong rs.w 1 ; Selected song 13 | -------------------------------------------------------------------------------- /tester/video/vsync.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; VSync 3 | ; Waits until the next frame 4 | ;**************************************************************************** 5 | 6 | VSync: 7 | lea ($C00004), a6 8 | 9 | @Loop1: ; Wait until current VBlank is over 10 | move.w (a6), d7 11 | btst.l #3, d7 12 | bne.s @Loop1 13 | 14 | @Loop2: ; Wait until next VBlank starts 15 | move.w (a6), d7 16 | btst.l #3, d7 17 | beq.s @Loop2 18 | 19 | rts ; End of subroutine 20 | -------------------------------------------------------------------------------- /tester/build.68k: -------------------------------------------------------------------------------- 1 | Echo_ProgFile equs "bin/prog-z80.bin" 2 | 3 | include "tester/core/header.68k" 4 | include "tester/core/entry.68k" 5 | include "tester/core/menu.68k" 6 | include "tester/core/songlist.68k" 7 | 8 | include "tester/video/text.68k" 9 | include "tester/video/bg.68k" 10 | include "tester/video/vsync.68k" 11 | 12 | include "tester/input/joypad.68k" 13 | 14 | include "tester/sound/echo.68k" 15 | include "tester/sound/esf.68k" 16 | include "tester/sound/list.68k" 17 | include "tester/sound/bgms.68k" 18 | include "tester/sound/sfxs.68k" 19 | 20 | cnop 0, $8000 21 | 22 | include "tester/core/vars.68k" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | © 2010-2019,2025 Javier Degirolmo 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /tester/sound/sfxs.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; SFX_Test 3 | ; Generic SFX to test events 4 | ;**************************************************************************** 5 | 6 | SFX_Test: 7 | dc.b $E8,$E9,$EA 8 | dc.b $28,$00,$48,$00 9 | dc.b $29,$08,$49,$00 10 | dc.b $2A,$08,$4A,$00 11 | 12 | dc.b $08,12,$09,24,$0A,36, $FE,$10, $18,$19,$1A, $FE,$10 13 | dc.b $08,12,$09,24,$0A,36, $FE,$10, $18,$19,$1A, $FE,$10 14 | dc.b $08,14,$09,26,$0A,38, $FE,$10, $18,$19,$1A, $FE,$10 15 | 16 | dc.b $FF 17 | 18 | ;**************************************************************************** 19 | ; SFX_Beep 20 | ; Beep SFX 21 | ;**************************************************************************** 22 | 23 | SFX_Beep: 24 | dc.b $EA,$1A,$4A,$00,$2A,$00 25 | dc.b $0A,2*36,$FE,4 26 | dc.b $FF 27 | -------------------------------------------------------------------------------- /tester/video/bg.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; DrawBG 3 | ; Draws the background 4 | ;**************************************************************************** 5 | 6 | DrawBG: 7 | lea ($C00004), a2 8 | lea ($C00000), a1 9 | 10 | lea (@BGData), a0 11 | move.l #$60000003, d0 12 | moveq #28-1, d6 13 | @YLoop: 14 | move.l d0, (a2) 15 | moveq #40-1, d7 16 | @XLoop: 17 | move.w (a0)+, (a1) 18 | dbf d7, @XLoop 19 | add.l #$00800000, d0 20 | dbf d6, @YLoop 21 | 22 | lea (@BGData), a0 23 | move.l #$60000002, d0 24 | moveq #28-1, d6 25 | @YLoop2: 26 | move.l d0, (a2) 27 | moveq #40-1, d7 28 | @XLoop2: 29 | move.w (a0)+, d1 30 | bset.l #11, d1 31 | move.w d1, (a1) 32 | dbf d7, @XLoop2 33 | add.l #$00800000, d0 34 | dbf d6, @YLoop2 35 | 36 | rts ; End of subroutine 37 | 38 | ;**************************************************************************** 39 | ; Background data 40 | ;**************************************************************************** 41 | 42 | @BGData: 43 | incbin "data/bg.bin" 44 | -------------------------------------------------------------------------------- /src-z80/core/macro.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; PollPCM 3 | ; Used to update PCM while not idle 4 | ;---------------------------------------------------------------------------- 5 | ; breaks: af 6 | ;**************************************************************************** 7 | 8 | PollPCM: macro 9 | ld a, ($4000) 10 | rrca 11 | rst $08 12 | endm 13 | 14 | ;**************************************************************************** 15 | ; BankSwitch 16 | ; Switches into a new bank (won't update player status!) 17 | ;---------------------------------------------------------------------------- 18 | ; input A .... New bank to switch into 19 | ; input HL ... Must be $60xx 20 | ;---------------------------------------------------------------------------- 21 | ; breaks ..... AF 22 | ;**************************************************************************** 23 | 24 | BankSwitch: macro 25 | ld (hl), a 26 | rrca 27 | ld (hl), a 28 | rrca 29 | ld (hl), a 30 | rrca 31 | ld (hl), a 32 | rrca 33 | ld (hl), a 34 | rrca 35 | ld (hl), a 36 | rrca 37 | ld (hl), a 38 | ld (hl), h 39 | rrca 40 | ld (hl), a 41 | endm 42 | -------------------------------------------------------------------------------- /tester/core/header.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; 68000 vectors 3 | ;**************************************************************************** 4 | 5 | dc.l $1000000 6 | dc.l EntryPoint 7 | dcb.l 62, ErrorInt 8 | 9 | ;**************************************************************************** 10 | ; Mega Drive header 11 | ;**************************************************************************** 12 | 13 | dc.b "SEGA MEGA DRIVE " 14 | dc.b "(C) SIK 2010.NOV" 15 | dc.b "ECHO TESTER PROGRAM" 16 | dcb.b $150-*, $20 17 | dc.b "ECHO TESTER PROGRAM" 18 | dcb.b $180-*, $20 19 | dc.b "XX XXXXXXXX-00" 20 | dc.w $0000 21 | dc.b "J" 22 | dcb.b $1A0-*, $20 23 | dc.l $000000, $3FFFFF 24 | dc.l $FF0000, $FFFFFF 25 | dcb.b 12, $20 26 | dcb.b 12, $20 27 | dcb.b 40, $20 28 | dc.b "JUE" 29 | dcb.b $200-*, $20 30 | 31 | ;**************************************************************************** 32 | ; ErrorInt 33 | ; Generic error handler routine (hangs up) 34 | ;**************************************************************************** 35 | 36 | ErrorInt: 37 | bra.s * 38 | -------------------------------------------------------------------------------- /doc/eif.txt: -------------------------------------------------------------------------------- 1 | ============================================================================= 2 | 3 | OVERVIEW 4 | 5 | EIF stands for "Echo Instrument Format" and it's the format in which FM 6 | instruments are stored. 7 | 8 | FORMAT 9 | 10 | EIF instruments are essentially raw dumps of the YM2612 registers. They 11 | consist of 29 bytes, where each byte belongs to a different YM2612 12 | register. The registers are stored in the following order (assuming the 13 | first FM channel): 14 | 15 | $B4 -- Algorithm and feedback 16 | $30, $34, $38, $3C -- Multiplier and detune 17 | $40, $44, $48, $4C -- Total level 18 | $50, $54, $58, $5C -- Attack rate 19 | $60, $64, $68, $6C -- Decay rate 20 | $70, $74, $78, $7C -- Sustain rate 21 | $80, $84, $88, $8C -- Release rate and sustain level 22 | $90, $94, $98, $9C -- SSG-EG 23 | 24 | Some bits are unused and ignored by the YM2612. In an EIF instrument, 25 | they *must* be 0, since Echo will rely on this for optimization purposes. 26 | (note: this refers to bits unused by the YM2612 itself, *not* Echo) 27 | 28 | ============================================================================= 29 | -------------------------------------------------------------------------------- /tester/sound/bgms.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; BGM_Test 3 | ; Generic BGM to test events 4 | ;**************************************************************************** 5 | 6 | BGM_Test: 7 | dc.b $FF 8 | 9 | ;**************************************************************************** 10 | ; Some conversions 11 | ;**************************************************************************** 12 | 13 | BGM_Midnas: 14 | incbin "data/music/midnas.esf" 15 | BGM_Nelpel: 16 | incbin "data/music/nelpel.esf" 17 | BGM_Megajysays: 18 | incbin "data/music/megajysays.esf" 19 | BGM_Doomsday: 20 | incbin "data/music/doomsday.esf" 21 | 22 | ;**************************************************************************** 23 | ; Several tests used for midi2esf 24 | ;**************************************************************************** 25 | 26 | BGM_PianoTest: 27 | incbin "data/music/test-piano.esf" 28 | BGM_SquSawTest2: 29 | incbin "data/music/test-squsaw-2ch.esf" 30 | BGM_SquSawTest1: 31 | incbin "data/music/test-squsaw-1ch.esf" 32 | BGM_PSGTest: 33 | incbin "data/music/test-psg.esf" 34 | BGM_DrumTest: 35 | incbin "data/music/test-drums.esf" 36 | BGM_FluteTest: 37 | incbin "data/music/test-flute.esf" 38 | 39 | -------------------------------------------------------------------------------- /src-z80/core/direct.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; ProcessDirect 3 | ; Processes the direct event stream. 4 | ;---------------------------------------------------------------------------- 5 | ; breaks: all 6 | ;**************************************************************************** 7 | 8 | ProcessDirect: 9 | ld a, ($1F00) ; Are there even events to process? 10 | inc a 11 | ret z 12 | 13 | ld a, $FF ; Put bogus length for direct stream 14 | ld (RAM_DirectLen), a ; so 68000 knows to wait 15 | 16 | PollPCM 17 | 18 | ld hl, ProcessDirectEnd ; Override $FF event 19 | ld (ProcessBGMEventFF+1), hl 20 | 21 | ld hl, $1F00 ; Where event data is stored 22 | ld a, (RAM_LastBank) ; To avoid wasting time with bank 23 | ld c, a ; switching 24 | 25 | jp ProcessBGMRun ; Start processing the event 26 | 27 | ProcessDirectEnd: 28 | ld hl, StopBGMEvent ; Restore $FF event 29 | ld (ProcessBGMEventFF+1), hl 30 | 31 | ld a, $FF ; Reset the stream 32 | ld ($1F00), a 33 | inc a 34 | ld (RAM_DirectLen), a 35 | 36 | ret ; Return to the main loop 37 | -------------------------------------------------------------------------------- /tester/input/joypad.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; InitJoypad 3 | ; Initializes the joypad 4 | ;**************************************************************************** 5 | 6 | InitJoypad: 7 | move.b #$40, ($A10009) ; Initialize I/O ports 8 | move.b #$40, ($A10003) 9 | move.w #0, (RAM_JoyHold) ; Initialize status 10 | rts ; End of subroutine 11 | 12 | ;**************************************************************************** 13 | ; ReadJoypad 14 | ; Reads the joypad status 15 | ;**************************************************************************** 16 | 17 | ReadJoypad: 18 | lea ($A10003), a6 ; I/O data port 19 | 20 | move.b #$40, (a6) ; Read D-pad, B and C 21 | nop 22 | nop 23 | move.b (a6), d7 24 | move.b #$00, (a6) ; Read A and Start 25 | nop 26 | nop 27 | move.b (a6), d6 28 | 29 | and.b #$3F, d7 ; Process input 30 | and.b #$30, d6 31 | add.b d6, d6 32 | add.b d6, d6 33 | or.b d6, d7 34 | not.b d7 35 | 36 | lea (RAM_JoyHold), a6 ; Store new joypad status 37 | move.b (a6), d6 38 | move.b d7, (a6)+ 39 | not.b d6 40 | and.b d6, d7 41 | move.b d7, (a6) 42 | 43 | rts ; End of subroutine 44 | -------------------------------------------------------------------------------- /doc/eef.txt: -------------------------------------------------------------------------------- 1 | ============================================================================= 2 | 3 | OVERVIEW 4 | 5 | EEF stands for "Echo Envelope Format" and it's the format in which PSG 6 | instruments are stored. 7 | 8 | FORMAT 9 | 10 | EEF instruments consist of a byte per tick (1/60th of a second). The 11 | bottom nibble is the volume level (relative to the note's volume), 12 | ranging from $x0 (loudest) to $xF (quietest). 13 | 14 | The upper nibble is a "semitone shift", which is added to the current 15 | note's semitone. These can be useful for things like vibrato (e.g. for 16 | whistles) and such. The amount the semitone is shifted is as follows: 17 | 18 | $0x ... 0 | $1x ... +1 | $8x ... -1 19 | | $2x ... +2 | $9x ... -2 20 | | $3x ... +3 | $Ax ... -3 21 | | $4x ... +4 | $Bx ... -4 22 | | $5x ... +6 | $Cx ... -6 23 | | $6x ... +8 | $Dx ... -8 24 | | $7x ... +12 | $Ex ... -12 25 | 26 | Looping is possible. The start of the loop is marked by a byte with value 27 | $FE, while the end of the loop is marked by a byte with value $FF. There 28 | must be at least one tick byte between them or Echo will hang. 29 | 30 | To make a non-looping PSG instrument, just put the last volume value 31 | inside the loop. 32 | 33 | ============================================================================= 34 | 35 | NOTES 36 | 37 | Since PSG instruments are required to use PSG channels and I know many of 38 | you don't want to mess with them at all, here's a flat PSG instrument 39 | (i.e. no envelope): 40 | 41 | $FE,$00,$FF 42 | 43 | There's a flavor of the Set Frequency event that doesn't play nice with 44 | semitone shifting (in which case it'll just act as if it was always $0x). 45 | It will work again once a new note starts or the other flavor is used. 46 | 47 | ============================================================================= 48 | -------------------------------------------------------------------------------- /c/echo.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_H 2 | #define ECHO_H 3 | 4 | /* Required headers */ 5 | #include 6 | 7 | /* Echo commands */ 8 | enum { 9 | ECHO_CMD_NONE, /* 0x00 - No command */ 10 | ECHO_CMD_LOADLIST, /* 0x01 - Load instrument list */ 11 | ECHO_CMD_PLAYSFX, /* 0x02 - Play a SFX */ 12 | ECHO_CMD_STOPSFX, /* 0x03 - Stop SFX playback */ 13 | ECHO_CMD_PLAYBGM, /* 0x04 - Play a BGM */ 14 | ECHO_CMD_STOPBGM, /* 0x05 - Stop BGM playback */ 15 | ECHO_CMD_RESUMEBGM, /* 0x06 - Resume BGM playback */ 16 | ECHO_CMD_SETPCMRATE, /* 0x07 - Set PCM rate */ 17 | ECHO_CMD_PAUSEBGM, /* 0x08 - Pause BGM playback */ 18 | ECHO_CMD_SETSTEREO, /* 0x09 - Toggle stereo */ 19 | }; 20 | 21 | /* Echo status flags */ 22 | #define ECHO_STAT_BGM 0x0002 /* Background music is playing */ 23 | #define ECHO_STAT_SFX 0x0001 /* Sound effect is playing */ 24 | #define ECHO_STAT_PCM 0x2000 /* PCM playback ongoing */ 25 | #define ECHO_STAT_DIRBUSY 0x4000 /* Echo isn't done with direct events */ 26 | #define ECHO_STAT_BUSY 0x8000 /* Echo still didn't parse command */ 27 | 28 | /* Function prototypes */ 29 | void echo_init(const void* const* list); 30 | void echo_play_bgm(const void *esf); 31 | void echo_stop_bgm(void); 32 | void echo_pause_bgm(void); 33 | void echo_resume_bgm(void); 34 | void echo_play_sfx(const void *esf); 35 | void echo_stop_sfx(void); 36 | void echo_play_direct(const void *esf); 37 | void echo_set_volume(uint8_t vol); 38 | void echo_set_volume_ex(const uint8_t *vol_list); 39 | void echo_set_pcm_rate(uint8_t rate); 40 | uint16_t echo_get_status(void); 41 | uint8_t echo_get_flags(void); 42 | void echo_set_flags(uint8_t flags); 43 | void echo_clear_flags(uint8_t flags); 44 | void echo_send_command(uint8_t cmd); 45 | void echo_send_command_addr(uint8_t cmd, const void *addr); 46 | void echo_send_command_byte(uint8_t cmd, uint8_t byte); 47 | 48 | /* Deprecated functions */ 49 | static void (* const echo_send_command_ex)(uint8_t, const void *) = 50 | echo_send_command_addr; 51 | 52 | /* Look-up tables */ 53 | extern const uint8_t echo_fm_vol_table[0x40]; 54 | extern const uint8_t echo_psg_vol_table[0x40]; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /c/tool/blob2c.c: -------------------------------------------------------------------------------- 1 | // Required headers 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Where the Z80 blob is stored 8 | #define MAX_SIZE 0x2000 9 | uint8_t blob[MAX_SIZE]; 10 | size_t blob_size; 11 | 12 | //*************************************************************************** 13 | // Program entry point 14 | //*************************************************************************** 15 | 16 | int main(int argc, char **argv) { 17 | // Check number of arguments 18 | if (argc != 3) { 19 | fprintf(stderr, "Usage: %s \n", argv[0]); 20 | return EXIT_FAILURE; 21 | } 22 | 23 | // Open input file 24 | FILE *file = fopen(argv[1], "rb"); 25 | if (file == NULL) { 26 | fputs("Error: couldn't open input file\n", stderr); 27 | return EXIT_FAILURE; 28 | } 29 | 30 | // Read the blob into memory and get its filesize 31 | // Use ferror() to check for failure since most likely we will be 32 | // attempting to read more data than is actually in the blob 33 | blob_size = fread(blob, 1, MAX_SIZE, file); 34 | if (ferror(file) && !feof(file)) { 35 | fputs("Error: couldn't read input file\n", stderr); 36 | fclose(file); 37 | return EXIT_FAILURE; 38 | } 39 | 40 | // Done with input file 41 | fclose(file); 42 | 43 | // Open output file 44 | file = fopen(argv[2], "w"); 45 | if (file == NULL) { 46 | fputs("Error: couldn't open output file\n", stderr); 47 | return EXIT_FAILURE; 48 | } 49 | 50 | // Start the table 51 | fputs("static const uint8_t echo_blob[] = {", file); 52 | 53 | // Output the blob data as a C array 54 | // Pretty formatting too! 55 | unsigned i; 56 | for (i = 0; i < blob_size; i++) { 57 | fprintf(file, "%s%3u%s", 58 | i % 0x10 == 0 ? "\n " : "", blob[i], 59 | i == blob_size-1 ? "" : ","); 60 | } 61 | 62 | // End the table 63 | fputs("\n};\n", file); 64 | 65 | // Check if there was an error (to output an error message) 66 | if (ferror(file)) { 67 | fputs("Error: couldn't write output file\n", stderr); 68 | fclose(file); 69 | return EXIT_FAILURE; 70 | } 71 | 72 | // Done with output file 73 | fclose(file); 74 | 75 | // Done 76 | return EXIT_SUCCESS; 77 | } 78 | -------------------------------------------------------------------------------- /tester/core/songlist.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; SongList 3 | ; List of songs that appear on the menu 4 | ;**************************************************************************** 5 | 6 | SongList: 7 | dc.l BGM_Midnas, @Str_Midnas_1, @Str_Midnas_2, @Str_Midnas_3 8 | dc.l BGM_Nelpel, @Str_Nelpel_1, @Str_Nelpel_2, @Str_Nelpel_3 9 | dc.l BGM_Megajysays, @Str_Megajysays_1, @Str_Megajysays_2, @Str_Megajysays_3 10 | dc.l BGM_Doomsday, @Str_Doomsday_1, @Str_Doomsday_2, @Str_Doomsday_3 11 | dc.l BGM_PianoTest, @Str_PianoTest_1, @Str_PianoTest_2, @Str_Null 12 | dc.l BGM_SquSawTest2, @Str_SquSawTest_1a, @Str_SquSawTest_2, @Str_SquSawTest_3a 13 | dc.l BGM_SquSawTest1, @Str_SquSawTest_1b, @Str_SquSawTest_2, @Str_SquSawTest_3b 14 | dc.l BGM_PSGTest, @Str_PSGTest_1, @Str_PSGTest_2, @Str_Null 15 | dc.l BGM_DrumTest, @Str_DrumTest_1, @Str_DrumTest_2, @Str_Null 16 | dc.l BGM_FluteTest, @Str_FluteTest_1, @Str_FluteTest_2, @Str_FluteTest_3 17 | 18 | ; 012345678901234567890123456 19 | @Str_Null: dc.b 0 20 | @Str_Untitled: dc.b "[untitled]", 0 21 | 22 | ; 012345678901234567890123456 23 | @Str_Midnas_1: dc.b "Midna's Desperate Hour", 0 24 | @Str_Midnas_2: dc.b "Composed by Koji Kondo", 0 25 | @Str_Midnas_3: dc.b "Transcribed by Aivi Tran", 0 26 | 27 | ; 012345678901234567890123456 28 | @Str_Nelpel_1: dc.b "Nelpel Four (crappy ver.)", 0 29 | @Str_Nelpel_2: dc.b "XM > MIDI > ESF conversion", 0 30 | @Str_Nelpel_3: dc.b "and really bad instruments", 0 31 | 32 | ; 012345678901234567890123456 33 | @Str_Megajysays_1: dc.b "Megajysays", 0 34 | @Str_Megajysays_2: dc.b "That second A should have", 0 35 | @Str_Megajysays_3: dc.b "two dots on top of it.", 0 36 | 37 | ; 012345678901234567890123456 38 | @Str_Doomsday_1: dc.b "The Doomsday Project", 0 39 | @Str_Doomsday_2: dc.b "Another module conversion.", 0 40 | @Str_Doomsday_3: dc.b "Like I give a crap :P", 0 41 | 42 | ; 012345678901234567890123456 43 | @Str_PianoTest_1: dc.b "test-piano.mid", 0 44 | @Str_PianoTest_2: dc.b "Some generic piano...", 0 45 | 46 | ; 012345678901234567890123456 47 | @Str_SquSawTest_1a: dc.b "test-squsaw-2ch.mid", 0 48 | @Str_SquSawTest_1b: dc.b "test-squsaw-1ch.mid", 0 49 | @Str_SquSawTest_2: dc.b "FM square and sawtooth", 0 50 | @Str_SquSawTest_3a: dc.b "2 channels version", 0 51 | @Str_SquSawTest_3b: dc.b "1 channel version", 0 52 | 53 | ; 012345678901234567890123456 54 | @Str_PSGTest_1: dc.b "test-psg.mid", 0 55 | @Str_PSGTest_2: dc.b "Two square PSG channels.", 0 56 | 57 | ; 012345678901234567890123456 58 | @Str_DrumTest_1: dc.b "test-drums.mid", 0 59 | @Str_DrumTest_2: dc.b "Snare and kicks!", 0 60 | 61 | ; 012345678901234567890123456 62 | @Str_FluteTest_1: dc.b "test-flute.mid", 0 63 | @Str_FluteTest_2: dc.b "Flute and a seashore.", 0 64 | @Str_FluteTest_3: dc.b "Feels so calm...", 0 65 | 66 | even 67 | 68 | ;**************************************************************************** 69 | ; NumSongs 70 | ; Number of songs in song list 71 | ;**************************************************************************** 72 | 73 | NumSongs equ 10 74 | -------------------------------------------------------------------------------- /tester/video/text.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; LoadFont 3 | ; Loads the font in VRAM 4 | ;**************************************************************************** 5 | 6 | LoadFont: 7 | move.l #$40000000, ($C00004) ; Where font will be stored 8 | 9 | lea (@Font), a0 ; Font data 10 | lea ($C00000), a1 ; VDP data port 11 | move.w #96*16-1, d7 ; Go through all lines 12 | @Loop: 13 | 14 | move.b (a0)+, d0 ; Fetch next line 15 | moveq #0, d1 ; Initial color 16 | moveq #8-1, d6 ; Go through all pixels 17 | @ILoop: 18 | 19 | add.b d0, d0 ; Get pixel color 20 | bcc.s @Transparent 21 | moveq #3, d1 22 | @Transparent: 23 | subq.b #1, d1 24 | bgt.s @NoUnderflow 25 | moveq #0, d1 26 | @NoUnderflow: 27 | 28 | lsl.l #4, d2 ; Make room for pixel 29 | or.b d1, d2 ; Store pixel 30 | dbf d6, @ILoop ; Next pixel 31 | 32 | move.l d2, (a1) ; Store line in VRAM 33 | dbf d7, @Loop ; Next line 34 | 35 | rts ; End of subroutine 36 | 37 | @Font: 38 | incbin "data/font.bin" 39 | 40 | ;**************************************************************************** 41 | ; WriteString 42 | ; Writes a string on screen 43 | ; 44 | ; input d0.w ... X coordinate 45 | ; input d1.w ... Y coordinate 46 | ; input d2.w ... FX and such 47 | ; input a0.l ... String 48 | ;**************************************************************************** 49 | 50 | WriteString: 51 | movem.l d0-d2, -(sp) ; Save registers 52 | 53 | lsl.w #6, d1 ; Calculate address 54 | add.w d1, d0 55 | add.w d0, d0 56 | 57 | and.l #$FFFF, d0 ; Tell VDP the address 58 | or.l #$00034000, d0 59 | swap d0 60 | move.l d0, ($C00004) 61 | 62 | lea ($C00000), a5 ; VDP data port 63 | moveq #2-1, d7 ; Go through both lines 64 | @Loop: 65 | 66 | move.l a0, a6 67 | @ILoop: 68 | move.b (a6)+, d1 ; Get next character 69 | beq.s @End ; End of string? 70 | 71 | sub.b #$20, d1 ; Write tile in VRAM 72 | and.w #$7F, d1 73 | add.w d1, d1 74 | add.w d2, d1 75 | move.w d1, (a5) 76 | 77 | bra.s @ILoop ; Next character 78 | 79 | @End: 80 | add.l #$80<<16, d0 81 | move.l d0, ($C00004) 82 | addq.w #1, d2 ; Next line 83 | dbf d7, @Loop 84 | 85 | movem.l (sp)+, d0-d2 ; Restore registers 86 | rts ; End of subroutine 87 | 88 | ;**************************************************************************** 89 | ; ClearLines 90 | ; Clears the description lines 91 | ;**************************************************************************** 92 | 93 | ClearLines: 94 | move.l #$448E0003, d0 ; Initial position to clear 95 | lea ($C00004), a0 ; VDP control port 96 | lea ($C00000), a1 ; VDP data port 97 | 98 | moveq #7-1, d1 ; Clear all lines 99 | moveq #0, d2 100 | @Loop: 101 | move.l d0, (a0) 102 | rept 26/2 103 | move.l d2, (a1) 104 | endr 105 | add.l #$80<<16, d0 106 | dbf d1, @Loop 107 | 108 | rts ; End of subroutine 109 | -------------------------------------------------------------------------------- /src-z80/player/misc.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; SetDelay* [event $FE, events $D0-$DF] 3 | ; Adds a delay in playback 4 | ;---------------------------------------------------------------------------- 5 | ; breaks: c, de, hl 6 | ;**************************************************************************** 7 | 8 | SetDelaySFX: 9 | call SetDelay ; We're just a wrapper 10 | jp DoTick_SFXSkip ; End of subroutine 11 | 12 | SetDelayBGM: 13 | call SetDelay ; We're just a wrapper 14 | jp DoTick_BGMSkip ; End of subroutine 15 | 16 | SetDelaySFXShort: 17 | and $0F 18 | inc a 19 | ld b, a 20 | call SetDelayShort ; We're just a wrapper 21 | jp DoTick_SFXSkip ; End of subroutine 22 | 23 | SetDelayBGMShort: 24 | and $0F 25 | inc a 26 | ld b, a 27 | call SetDelayShort ; We're just a wrapper 28 | jp DoTick_BGMSkip ; End of subroutine 29 | 30 | SetDelay: 31 | PollPCM 32 | call GetParam ; Get delay 33 | PollPCM 34 | 35 | SetDelayShort: 36 | ex de, hl 37 | ld (hl), d ; Store new address 38 | dec l 39 | ld (hl), e 40 | dec l 41 | ld (hl), c 42 | 43 | PollPCM 44 | 45 | dec l ; Store new delay 46 | ld (hl), b 47 | 48 | ret ; End of subroutine 49 | 50 | ;**************************************************************************** 51 | ; SetFlags [event $FA] 52 | ; Sets some of the flags. 53 | ;---------------------------------------------------------------------------- 54 | ; breaks: c, de, hl 55 | ;**************************************************************************** 56 | 57 | SetFlagsSFX: 58 | call SetFlags 59 | jp ProcessSFXRun 60 | 61 | SetFlagsBGM: 62 | call SetFlags 63 | jp ProcessBGMRun 64 | 65 | SetFlags: 66 | PollPCM ; Get which flags to set 67 | call GetParam 68 | PollPCM 69 | 70 | ld a, (RAM_Flags) ; Set the flags 71 | or b 72 | ld (RAM_Flags), a 73 | 74 | ret ; End of subroutine 75 | 76 | ;**************************************************************************** 77 | ; ClearFlags [event $FB] 78 | ; Clears some of the flags. 79 | ;---------------------------------------------------------------------------- 80 | ; breaks: c, de, hl 81 | ;**************************************************************************** 82 | 83 | ClearFlagsSFX: 84 | call ClearFlags 85 | jp ProcessSFXRun 86 | 87 | ClearFlagsBGM: 88 | call ClearFlags 89 | jp ProcessBGMRun 90 | 91 | ClearFlags: 92 | PollPCM ; Get which flags to clear 93 | call GetParam 94 | PollPCM 95 | 96 | ld a, (RAM_Flags) ; Clear the flags 97 | and b 98 | ld (RAM_Flags), a 99 | 100 | ret ; End of subroutine 101 | 102 | ;**************************************************************************** 103 | ; RefreshVolume 104 | ; Reloads the volume for all channels. 105 | ;---------------------------------------------------------------------------- 106 | ; breaks: all 107 | ;**************************************************************************** 108 | 109 | RefreshVolume: 110 | ld hl, $1FF0 ; Update FM volume 111 | ld de, RAM_FMVol 112 | ld c, 8 113 | .fixfmvol: 114 | ld a, (de) 115 | ld b, (hl) 116 | add b 117 | jp p, .fixfmvolok 118 | ld a, $7F 119 | .fixfmvolok: 120 | ld b, a 121 | ld a, l 122 | and $07 123 | call SetFMVolTempLoad 124 | inc l 125 | inc e 126 | dec c 127 | PollPCM 128 | jr nz, .fixfmvol 129 | 130 | ld hl, $1FE8 ; Update PSG volume 131 | ld de, RAM_PSGData+1 132 | ld bc, $0410 133 | .fixpsgvol: 134 | ld a, (hl) 135 | ld (de), a 136 | inc l 137 | ld a, e 138 | add a, c 139 | ld e, a 140 | PollPCM 141 | djnz .fixpsgvol 142 | 143 | xor a ; Mark that volume was refreshed 144 | ld ($1FF1), a 145 | 146 | ret ; End of subroutine 147 | -------------------------------------------------------------------------------- /src-z80/player/freq.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; PSGFreqTable 3 | ; Frequency table for all PSG notes 4 | ;**************************************************************************** 5 | 6 | ds $100-($&$FF), $FF 7 | PSGFreqTable: 8 | db $83, $35 ; C-3 - 851 9 | db $83, $32 ; C#3 - 803 10 | db $86, $2F ; D-3 - 758 11 | db $8B, $2C ; D#3 - 715 12 | db $83, $2A ; E-3 - 675 13 | db $8D, $27 ; F-3 - 637 14 | db $89, $25 ; F#3 - 601 15 | db $88, $23 ; G-3 - 568 16 | db $88, $21 ; G#3 - 536 17 | db $8A, $1F ; A-3 - 506 18 | db $8D, $1D ; A#3 - 477 19 | db $82, $1C ; B-3 - 450 20 | db $89, $1A ; C-4 - 425 21 | db $81, $19 ; C#4 - 401 22 | db $8B, $17 ; D-4 - 379 23 | db $85, $16 ; D#4 - 357 24 | db $81, $15 ; E-4 - 337 25 | db $8E, $13 ; F-4 - 318 26 | db $8C, $12 ; F#4 - 300 27 | db $8C, $11 ; G-4 - 284 28 | db $8C, $10 ; G#4 - 268 29 | db $8D, $0F ; A-4 - 253 30 | db $8E, $0E ; A#4 - 238 31 | db $81, $0E ; B-4 - 225 32 | db $84, $0D ; C-5 - 212 33 | db $88, $0C ; C#5 - 200 34 | db $8D, $0B ; D-5 - 189 35 | db $82, $0B ; D#5 - 178 36 | db $88, $0A ; E-5 - 168 37 | db $8F, $09 ; F-5 - 159 38 | db $86, $09 ; F#5 - 150 39 | db $8E, $08 ; G-5 - 142 40 | db $86, $08 ; G#5 - 134 41 | db $8E, $07 ; A-5 - 126 42 | db $87, $07 ; A#5 - 119 43 | db $80, $07 ; B-5 - 112 44 | db $8A, $06 ; C-6 - 106 45 | db $84, $06 ; C#6 - 100 46 | db $8E, $05 ; D-6 - 94 47 | db $89, $05 ; D#6 - 89 48 | db $84, $05 ; E-6 - 84 49 | db $8F, $04 ; F-6 - 79 50 | db $8B, $04 ; F#6 - 75 51 | db $87, $04 ; G-6 - 71 52 | db $83, $04 ; G#6 - 67 53 | db $8F, $03 ; A-6 - 63 54 | db $8B, $03 ; A#6 - 59 55 | db $88, $03 ; B-6 - 56 56 | db $85, $03 ; C-7 - 53 57 | db $82, $03 ; C#7 - 50 58 | db $8F, $02 ; D-7 - 47 59 | db $8C, $02 ; D#7 - 44 60 | db $8A, $02 ; E-7 - 42 61 | db $87, $02 ; F-7 - 39 62 | db $85, $02 ; F#7 - 37 63 | db $83, $02 ; G-7 - 35 64 | db $81, $02 ; G#7 - 33 65 | db $8F, $01 ; A-7 - 31 66 | db $8D, $01 ; A#7 - 29 67 | db $8C, $01 ; B-7 - 28 68 | db $8A, $01 ; C-8 - 26 69 | db $89, $01 ; C#8 - 25 70 | db $87, $01 ; D-8 - 23 71 | db $86, $01 ; D#8 - 22 72 | db $85, $01 ; E-8 - 21 73 | db $83, $01 ; F-8 - 19 74 | db $82, $01 ; F#8 - 18 75 | db $81, $01 ; G-8 - 17 76 | db $80, $01 ; G#8 - 16 77 | db $8F, $00 ; A-8 - 15 78 | db $8E, $00 ; A#8 - 14 79 | db $8E, $00 ; B-8 - 14 80 | 81 | ;**************************************************************************** 82 | ; FMFreqTable 83 | ; Frequency table for all FM notes 84 | ;**************************************************************************** 85 | 86 | FMFreqTable: 87 | dw 644, 681, 722, 765 88 | dw 810, 858, 910, 964 89 | dw 1021, 1081, 1146, 1214 90 | 91 | ;**************************************************************************** 92 | ; PSGShiftTable 93 | ; Semitone shifting table for PSG instruments 94 | ;**************************************************************************** 95 | 96 | PSGShiftTable: 97 | db 0*2 ; $0x 98 | db 1*2, 2*2, 3*2, 4*2, 6*2, 8*2, 12*2 ; $1x..$7x 99 | db -1*2, -2*2, -3*2, -4*2, -6*2, -8*2, -12*2 ; $8x..$Ex 100 | 101 | ;**************************************************************************** 102 | ; DummyFMInstr 103 | ; Dummy FM instrument to mute FM channels... 104 | ; 105 | ; To-do: put this in its own file? Although I'd like for this table to stay 106 | ; in this area in memory 107 | ;**************************************************************************** 108 | 109 | DummyFMInstr: 110 | db $7F ; $40..$4C 111 | db $00 ; $50..$5C 112 | db $1F ; $60..$6C 113 | db $1F ; $70..$7C 114 | db $FF ; $80..$8C 115 | db $00 ; $90..$9C 116 | -------------------------------------------------------------------------------- /tester/core/menu.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; MainMenu 3 | ; Menu where you get to select the song and such 4 | ; The main screen, bah... 5 | ;**************************************************************************** 6 | 7 | MainMenu: 8 | move.w #0, (RAM_CurrSong) ; Selected song 9 | bsr UpdateMenu ; Show description of first song 10 | 11 | move.l #0, (RAM_LArrowAnim) ; Reset arrows anim 12 | move.w #$8407, (RAM_BGAnim) ; Reset background anim 13 | 14 | @MainLoop: 15 | move.b (RAM_JoyPress), d0 ; Get joypad input 16 | 17 | btst.l #2, d0 ; Previous song? 18 | beq.s @NoLeft 19 | 20 | move.w (RAM_CurrSong), d7 ; Select previous song 21 | bne.s @NotTooLeft 22 | move.w #NumSongs, d7 23 | @NotTooLeft: 24 | subq.w #1, d7 25 | move.w d7, (RAM_CurrSong) 26 | 27 | bsr UpdateMenu ; Update current song 28 | move.w #28, (RAM_LArrowAnim) ; Animate left arrow 29 | lea (SFX_Beep), a0 ; Beep! 30 | bsr Echo_PlaySFX 31 | @NoLeft: 32 | 33 | btst.l #3, d0 ; Next song? 34 | beq.s @NoRight 35 | 36 | move.w (RAM_CurrSong), d7 ; Select next song 37 | addq.w #1, d7 38 | cmp.w #NumSongs, d7 39 | blt.s @NotTooRight 40 | moveq #0, d7 41 | @NotTooRight: 42 | move.w d7, (RAM_CurrSong) 43 | bsr UpdateMenu ; Update current song 44 | 45 | move.w #28, (RAM_RArrowAnim) ; Animate right arrow 46 | lea (SFX_Beep), a0 ; Beep! 47 | bsr Echo_PlaySFX 48 | @NoRight: 49 | 50 | btst.l #5, d0 ; Play song? 51 | bne.s @DoPlay 52 | btst.l #6, d0 53 | beq.s @NoPlay 54 | @DoPlay: 55 | lea (SongList), a1 ; Get song address 56 | move.w (RAM_CurrSong), d7 57 | lsl.w #4, d7 58 | lea (a1,d7.w), a1 59 | move.l (a1), a0 60 | bsr Echo_PlayBGM ; Play song 61 | @NoPlay: 62 | 63 | btst.l #4, d0 ; Stop song? 64 | beq.s @NoStop 65 | bsr Echo_StopBGM 66 | @NoStop: 67 | 68 | btst.l #7, d0 ; Debug key 69 | beq.s @NoDebug 70 | lea (BGM_Test), a0 ; Play test BGM 71 | ;bsr Echo_PlayBGM 72 | lea (SFX_Test), a0 ; Play test SFX 73 | bsr Echo_PlaySFX 74 | @NoDebug: 75 | 76 | lea (@ArrowPal), a0 77 | 78 | move.w (RAM_LArrowAnim), d0 ; Animate left arrow 79 | move.l (a0,d0.w), d1 80 | move.l #$C0060000, ($C00004) 81 | move.l d1, ($C00000) 82 | subq.w #2, d0 83 | bge.s @NoLArrowOver 84 | moveq #0, d0 85 | @NoLArrowOver: 86 | move.w d0, (RAM_LArrowAnim) 87 | 88 | move.w (RAM_RArrowAnim), d0 ; Animate right arrow 89 | move.l (a0,d0.w), d1 90 | move.l #$C0260000, ($C00004) 91 | move.l d1, ($C00000) 92 | subq.w #2, d0 93 | bge.s @NoRArrowOver 94 | moveq #0, d0 95 | @NoRArrowOver: 96 | move.w d0, (RAM_RArrowAnim) 97 | 98 | move.w (RAM_BGAnim), d0 ; Animate background 99 | bchg.l #1, d0 100 | move.w d0, (RAM_BGAnim) 101 | move.w d0, ($C00004) 102 | 103 | bsr VSync ; Next frame 104 | bsr ReadJoypad 105 | bra @MainLoop 106 | 107 | ;**************************************************************************** 108 | 109 | @ArrowPal: 110 | dc.w $00E, $00E, $02E, $02E, $04E, $04E, $06E, $06E 111 | dc.w $28E, $28E, $4AE, $4AE, $6CE, $6CE, $8EE, $8EE 112 | 113 | ;**************************************************************************** 114 | ; UpdateMenu 115 | ; Shows the current option on screen 116 | ;**************************************************************************** 117 | 118 | UpdateMenu: 119 | bsr ClearLines ; Clear lines 120 | 121 | lea (SongList), a1 ; Get address of song data 122 | move.w (RAM_CurrSong), d0 123 | lsl.w #4, d0 124 | lea (a1,d0.w), a1 125 | addq.l #4, a1 126 | 127 | moveq #7, d0 ; Write song title 128 | moveq #9, d1 129 | move.w #$A000, d2 130 | move.l (a1)+, a0 131 | bsr WriteString 132 | 133 | addq.w #2, d1 ; Write song description 134 | move.w #$C000, d2 135 | move.l (a1)+, a0 136 | bsr WriteString 137 | addq.w #2, d1 138 | move.l (a1)+, a0 139 | bsr WriteString 140 | 141 | rts ; End of subroutine 142 | -------------------------------------------------------------------------------- /src-z80/core/vars.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; Player variables 3 | ;**************************************************************************** 4 | 5 | ds $100-($&$FF), $FF 6 | RAM_PSGData: ds 4*16 ; PSG envelope data 7 | ; ds 1 ... Channel volume 8 | ; ds 1 ... Global volume 9 | ; ds 3 ... Current address 10 | ; ds 3 ... Looping address 11 | ; ds 3 ... Start address 12 | ; ds 1 ... Semitone ($FF if void) 13 | ; ds 3 ... BGM instrument address 14 | ; ds 1 ... BGM channel volume 15 | 16 | RAM_BGMFMInstr: ds 8 ; FM instruments used by BGM 17 | RAM_BGMFMVol: ds 8, $7F ; FM volumes used by BGM 18 | RAM_BGMFMPan: ds 8, $C0 ; FM panning used by BGM 19 | 20 | RAM_FMVol: ds 8 ; FM volume of each channel 21 | RAM_FMData: ds 8*5 ; FM info (for volume handling) 22 | ; ds 8*1 ... Register $B0 23 | ; ds 8*1 ... Register $40 24 | ; ds 8*1 ... Register $44 25 | ; ds 8*1 ... Register $48 26 | ; ds 8*1 ... Register $4C 27 | 28 | RAM_Locked: ds 12 ; Locked channels 29 | RAM_PSGNote: ds 4 ; Current PSG notes 30 | 31 | RAM_LastBank: ds 1 ; Last accessed bank 32 | 33 | RAM_BGMData: ; Where BGM data starts 34 | RAM_BGMPlaying: ds 1 ; Set if a BGM is playing 35 | RAM_BGMDelay: ds 1 ; How many ticks to wait 36 | RAM_BGMBank: ds 1 ; Current BGM bank 37 | RAM_BGMAddress: ds 2 ; Current BGM address 38 | RAM_BGMLoopPoint: ds 3 ; BGM loop point 39 | 40 | RAM_SFXData: ; Where SFX data starts 41 | RAM_SFXPlaying: ds 1 ; Set if a SFX is playing 42 | RAM_SFXDelay: ds 1 ; How many ticks to wait 43 | RAM_SFXBank: ds 1 ; Current SFX bank 44 | RAM_SFXAddress: ds 2 ; Current SFX address 45 | RAM_SFXLoopPoint: ds 3 ; SFX loop point 46 | 47 | RAM_Paused: ds 1 ; Set if BGM stream is paused 48 | RAM_Mono: ds 1 ; Set if panning is disabled 49 | 50 | RAM_PCMBank1: db 1 ; (not implemented yet) 51 | RAM_PCMAddr1: dw 1 ; (not implemented yet) 52 | RAM_PCMBank2: db 1 ; (not implemented yet) 53 | RAM_PCMAddr2: dw 1 ; (not implemented yet) 54 | 55 | RAM_Scratch: ds 32 ; Scratch bytes, may be useful when 56 | ; buffering to speed up to avoid bank 57 | ; switching conflicts 58 | 59 | ds $F0-($&$FF), $FF 60 | RAM_PCMBuffer: ds 0 ; PCM buffer 61 | RAM_PCMBuffer1: ds 8 ; (not implemented yet) 62 | RAM_PCMBuffer2: ds 8 ; (not implemented yet) 63 | 64 | ;**************************************************************************** 65 | ; Pointer list starts being stored from here 66 | ; $300 (768) bytes are needed to store the pointer list 67 | ; 68 | ; Format for a pointer list entry is as follows: 69 | ; RAM_PointerList[$000+n] = address high 70 | ; RAM_PointerList[$100+n] = address low 71 | ; RAM_PointerList[$200+n] = bank 72 | ;**************************************************************************** 73 | 74 | RAM_PointerList: equ $1C00 75 | 76 | ;**************************************************************************** 77 | ; 68000 communication variables 78 | ;**************************************************************************** 79 | 80 | RAM_DirectLen: equ $1F80 ; Length of direct event stream 81 | RAM_Stack: equ $1FE0 ; Where stack starts 82 | 83 | RAM_GlobalVol: equ $1FE0 ; Global volume for all channels 84 | RAM_Status: equ $1FF0 ; Current playback status 85 | RAM_RefreshVol: equ $1FF1 ; Set to refresh all volumes 86 | RAM_Flags: equ $1FF2 ; Track flags 87 | 88 | RAM_Command: equ $1FFF ; Command type 89 | RAM_ComAddr: equ $1FFD ; Command address parameter 90 | RAM_ComBank: equ $1FFC ; Command bank parameter 91 | 92 | RAM_Command2: equ $1FFB ; (second slot for all the above) 93 | RAM_ComAddr2: equ $1FF9 94 | RAM_ComBank2: equ $1FF8 95 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ============================================================================= 2 | ___________ ________ ___ ___ _________ 3 | | _______| _| ___ |_ | | | | _| _____ |_ 4 | | | _| _| |___| | | | | | | | | 5 | | |_____ | | | |_____| | | | | | 6 | | _____| | | | _____ | | | | | 7 | | | |_ |_ ___ | | | | | | | | 8 | | |_______ |_ |___| _| | | | | |_ |_____| _| 9 | |___________| |________| |___| |___| |_________| 10 | ___ ___ _ _ ___ __ ___ ___ ___ _ ___ ___ _ ___ ___ 11 | | _| | | | | \ | _| | _| | | _| | | | _| | 12 | | |_| | | | | | | | | | |_| | | |_| | | | |_ _ _ ___ ___ | | | |_| | |___ 13 | |_ | | | | | | | | | | _| | | | | | | | _| | | | __| _ \ | | | | | . | 14 | _| | | | | | | | | | | |_| | | | | | | | |_ | | | __| /_ | |_| | | | | | 15 | |___|___|___|_|_|__/ |___|_|_|___|_|_|_|___| \_/|___|_|_\_| |_|_|___|___|_|_| 16 | 17 | ============================================================================= 18 | 19 | WARNING: if you used Echo_SetVolumeEx in Echo 1.4, beware that the ABI 20 | changed to require 16 bytes instead of 13. Currently the extra bytes are 21 | ignored, but consider adapting your code (just set them to zeroes). 22 | 23 | ============================================================================= 24 | 25 | Echo is available under the zlib license. Please see the LICENSE file for 26 | details. All files here as-is are made available under that license unless 27 | explicitly stated otherwise. 28 | 29 | mdtools repo (includes several tools for use with Echo): 30 | https://github.com/sikthehedgehog/mdtools 31 | 32 | Converters from Deflemask to Echo's format: 33 | https://github.com/BigEvilCorporation/dmf2esf 34 | https://github.com/minerscale/dmf2esf-2.0 35 | 36 | Some more tools from Oerg866 (XM to ESF converter and ESF optimizer): 37 | https://github.com/oerg866/xm2esf 38 | 39 | ============================================================================= 40 | 41 | *** How to use *** 42 | 43 | If you want to use Echo from asm, take the blob from "built/prog-z80.bin" and 44 | the "src-68k/echo.68k" asm file. Include the latter in your source code, then 45 | go to the @Z80Program label (should be near the end of the file) and replace 46 | the string with the path to the blob. 47 | 48 | If you want to use Echo from C, check the "c" subdirectory. You will find the 49 | required files. It should work with any Mega Drive C devkit that provides the 50 | stdint.h header (and you should ditch it if it doesn't!). 51 | 52 | The documentation for the asm API is in "doc/api-asm.txt", while the 53 | documentation for the C API is in "doc/c-api.txt". 54 | 55 | ============================================================================= 56 | 57 | *** How to build *** 58 | 59 | * Make sure the root of this source code tree is the current directory 60 | * Assemble src-z80/build.z80 to bin/prog-z80.bin 61 | * Assemble tester/build.68k to bin/prog-68k.bin (optional) 62 | 63 | If you're using Linux, you can use build.sh to build the Echo binary (you'll 64 | need z80asm for this). I still need to provide scripts for the tester ROM 65 | though, sorry :/ (I used asm68k, if you wonder) 66 | 67 | Also no source for the data blobs yet, for similar reasons, although I have 68 | more control over those tools at least so I may come up with some good 69 | replacements (or even just rebuild the current tools, pretty sure one of them 70 | (mdtiler) already has a replacement) :/ 71 | 72 | The last step builds the Echo tester ROM, which is some generic ROM I use to 73 | test the tools and some other stuff I make, and generally contains garbage 74 | tunes in it you shouldn't pay attention to. You don't have to build it to 75 | make use of Echo :P 76 | 77 | ============================================================================= 78 | 79 | *** Making sound effects *** 80 | 81 | If you are using assembly and don't feel like using the full-blown tools to 82 | make sound effects, you can use the macros in the "src-68k/esf.68k" file to 83 | create sound effects. For now there is not documentation for it :/ (check the 84 | comments in the source file to see the syntax for each macro) 85 | 86 | An equivalent C API may be available in the future. 87 | 88 | ============================================================================= 89 | 90 | CREDITS: 91 | 92 | Main programmer ........ Sik 93 | Auxiliary programmer ... Oerg866 94 | 95 | THANKS FOR TESTING: 96 | 97 | * TµEE (general hardware testing) 98 | * Eke Eke (timer shenanigans IIRC?) 99 | * MarkeyJester (*insert comment here*) 100 | * John Springer (you heart his pizza) 101 | * Flygon (sorry for your headphones :P) 102 | * djcouchycouch (for using an API I didn't even check if it worked) 103 | * BigEvilCorporation (helped with testing Echo 1.4) 104 | * Ashley (found that echo_blob in the C API wasn't marked const) 105 | 106 | OTHER THANKS: 107 | 108 | * Titan (yay demoscene!) 109 | 110 | ============================================================================= 111 | -------------------------------------------------------------------------------- /tester/sound/esf.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; Channel ID constants 3 | ;**************************************************************************** 4 | 5 | ESF_FM1 equ $00 ; FM channel #1 6 | ESF_FM2 equ $01 ; FM channel #2 7 | ESF_FM3 equ $02 ; FM channel #3 8 | ESF_FM4 equ $04 ; FM channel #4 9 | ESF_FM5 equ $05 ; FM channel #5 10 | ESF_FM6 equ $06 ; FM channel #6 11 | ESF_PSG1 equ $08 ; PSG square channel #1 12 | ESF_PSG2 equ $09 ; PSG square channel #2 13 | ESF_PSG3 equ $0A ; PSG square channel #3 14 | ESF_PSG4 equ $0B ; PSG noise channel 15 | ESF_PCM equ $0C ; PCM channel 16 | 17 | ;**************************************************************************** 18 | ; ESF_NoteOn 19 | ; Start playing a note. 20 | ;---------------------------------------------------------------------------- 21 | ; For FM channels: 22 | ; ESF_NoteOn channel, octave, semitone 23 | ; For square PSG channels: 24 | ; ESF_NoteOn channel, octave, semitone 25 | ; For noise PSG channel: 26 | ; ESF_NoteOn channel, type 27 | ; For PCM channel: 28 | ; ESF_NoteOn channel, instrument 29 | ;---------------------------------------------------------------------------- 30 | ; param channel ...... channel to play on 31 | ; param octave ....... octave (0 to 7 for FM, 0 to 5 for PSG) 32 | ; param semitone ..... semitone (0 to 11) 33 | ; param type ......... noise type ($00 to $07) 34 | ; param instrument ... drum instrument ID ($00 to $FF) 35 | ;**************************************************************************** 36 | 37 | ESF_NoteOn macro 38 | dc.b $00+(\1) 39 | if (\1)>8) 99 | dc.b (\3)&$FF 100 | elseif (\1)>6 103 | else 104 | dc.b (\2) 105 | endc 106 | endm 107 | 108 | ;**************************************************************************** 109 | ; ESF_SetInstr 110 | ; Set the instrument of a channel. 111 | ;---------------------------------------------------------------------------- 112 | ; Format: 113 | ; ESF_SetInstr channel, instrument 114 | ;---------------------------------------------------------------------------- 115 | ; param channel ...... Channel to lock 116 | ; param instrument ... Instrument ID ($00 to $FF) 117 | ;**************************************************************************** 118 | 119 | ESF_SetInstr macro 120 | dc.b $40+(\1) 121 | dc.b (\2) 122 | endm 123 | 124 | ;**************************************************************************** 125 | ; ESF_Lock 126 | ; Lock SFX channel. 127 | ;---------------------------------------------------------------------------- 128 | ; Format: 129 | ; ESF_Lock channel 130 | ;---------------------------------------------------------------------------- 131 | ; param channel ... Channel to lock 132 | ;**************************************************************************** 133 | 134 | ESF_Lock macro 135 | dc.b $E0 136 | dc.b (\1) 137 | endm 138 | 139 | ;**************************************************************************** 140 | ; ESF_Delay 141 | ; Stop event. 142 | ;---------------------------------------------------------------------------- 143 | ; Format: 144 | ; ESF_Delay ticks 145 | ;---------------------------------------------------------------------------- 146 | ; param ticks ... Ticks to wait (60 = 1 second) 147 | ;**************************************************************************** 148 | 149 | ESF_Delay macro 150 | dc.b $FE 151 | dc.b (\1) 152 | endm 153 | 154 | ;**************************************************************************** 155 | ; ESF_Stop 156 | ; Stop event. 157 | ;**************************************************************************** 158 | 159 | ESF_Stop macro 160 | dc.b $FF 161 | endm 162 | -------------------------------------------------------------------------------- /tester/core/entry.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; EntryPoint 3 | ; Where the program starts 4 | ;**************************************************************************** 5 | 6 | EntryPoint: 7 | move.w #$2700, sr ; Disable interrupts 8 | 9 | move.b ($A10001), d0 ; Disable TMSS if needed 10 | and.b #$0F, d0 11 | beq.s @NoTMSS 12 | move.l #"SEGA", ($A14000) 13 | @NoTMSS: 14 | 15 | bsr InitJoypad ; Init joypad 16 | 17 | lea ($C00004), a0 ; Init VDP 18 | move.w #$8004, (a0) ; No IRQ4, no HV latch 19 | move.w #$8114, (a0) ; Disable display 20 | move.w #$8230, (a0) ; Scroll A: $C000 21 | move.w #$8407, (a0) ; Scroll B: $E000 22 | move.w #$8578, (a0) ; Sprites: $F000 23 | move.w #$8700, (a0) ; Background: pal 0, color 0 24 | move.w #$8B00, (a0) ; No IRQ2, full scroll 25 | move.w #$8C81, (a0) ; H40, no S/H, no interlace 26 | move.w #$8D3E, (a0) ; HScroll: $F800 27 | move.w #$8F02, (a0) ; Autoincrement: 2 bytes 28 | move.w #$9001, (a0) ; Scroll size: 64x32 29 | move.w #$9100, (a0) ; Hide window plane 30 | move.w #$9200, (a0) ; " " " 31 | 32 | moveq #0, d0 ; Clear VRAM 33 | move.l #$40000000, (a0) 34 | lea ($C00000), a1 35 | move.w #$800-1, d1 36 | @ClearVRAM: 37 | move.l d0, (a1) 38 | move.l d0, (a1) 39 | move.l d0, (a1) 40 | move.l d0, (a1) 41 | move.l d0, (a1) 42 | move.l d0, (a1) 43 | move.l d0, (a1) 44 | move.l d0, (a1) 45 | dbf d1, @ClearVRAM 46 | 47 | move.l #$C0000000, (a0) ; Clear CRAM 48 | moveq #8-1, d1 49 | @ClearCRAM: 50 | move.l d0, (a1) 51 | move.l d0, (a1) 52 | move.l d0, (a1) 53 | move.l d0, (a1) 54 | dbf d1, @ClearCRAM 55 | 56 | move.l #$40000010, (a0) ; Clear VSRAM 57 | move.l d0, (a1) 58 | 59 | lea (PointerList), a0 ; Initialize Echo 60 | bsr Echo_Init 61 | 62 | bsr LoadFont ; Load font 63 | 64 | lea ($C00004), a0 ; Load palette 65 | lea ($C00000), a1 66 | move.l #$C0020000, (a0) 67 | move.l #$066600CE, (a1) 68 | move.l #$000E000E, (a1) 69 | move.l #$C0220000, (a0) 70 | move.l #$06660EEE, (a1) 71 | move.l #$000E000E, (a1) 72 | move.l #$C0420000, (a0) 73 | move.l #$04440E88, (a1) 74 | move.l #$C0620000, (a0) 75 | move.l #$04440888, (a1) 76 | 77 | move.l #$C00A0000, (a0) 78 | move.l #$02220444, (a1) 79 | move.l #$02240024, (a1) 80 | move.l #$02440020, (a1) 81 | move.w #$0040, (a1) 82 | 83 | move.l #$58000000, (a0) ; Load graphics 84 | lea (@Gfx_Arrows), a2 85 | moveq #12-1, d7 86 | @LoadGfx: 87 | move.l (a2)+, (a1) 88 | move.l (a2)+, (a1) 89 | move.l (a2)+, (a1) 90 | move.l (a2)+, (a1) 91 | move.l (a2)+, (a1) 92 | move.l (a2)+, (a1) 93 | move.l (a2)+, (a1) 94 | move.l (a2)+, (a1) 95 | dbf d7, @LoadGfx 96 | 97 | move.l #$44880003, (a0) ; Write left arrow 98 | move.l #$00C000C1, (a1) 99 | move.l #$45080003, (a0) 100 | move.l #$00C200C3, (a1) 101 | 102 | move.l #$44C40003, (a0) ; Write right arrow 103 | move.l #$28C128C0, (a1) 104 | move.l #$45440003, (a0) 105 | move.l #$28C328C2, (a1) 106 | 107 | moveq #2, d0 ; Write title 108 | moveq #1, d1 109 | move.w #$8000, d2 110 | lea (@Str_Title1), a0 111 | bsr WriteString 112 | addq.w #2, d1 113 | move.w #$E000, d2 114 | lea (@Str_Title2), a0 115 | bsr WriteString 116 | 117 | moveq #20-(24/2), d0 ; Write instructions 118 | moveq #23, d1 119 | lea (@Str_Instr1), a0 120 | bsr WriteString 121 | moveq #20-(18/2), d0 122 | addq.w #2, d1 123 | lea (@Str_Instr2), a0 124 | bsr WriteString 125 | 126 | bsr DrawBG ; Draw background 127 | 128 | move.w #$8154, ($C00004) ; Enable display 129 | bra MainMenu ; Go into the main menu 130 | 131 | ;**************************************************************************** 132 | 133 | @Gfx_Arrows: 134 | dc.l $00000000, $00000000, $00000000, $00000000 135 | dc.l $00000034, $00004343, $00343434, $43434343 136 | dc.l $00000034, $00004343, $00343434, $43434343 137 | dc.l $34343434, $43434343, $34343434, $43434343 138 | dc.l $34343434, $00434343, $00003434, $00000043 139 | dc.l $00000000, $00000000, $00000000, $00000000 140 | dc.l $34343434, $43434343, $34343434, $43434343 141 | dc.l $34343434, $00434343, $00003434, $00000043 142 | 143 | @Gfx_BG: 144 | dc.l $55555555, $55555555, $55555555, $55555555 145 | dc.l $55555555, $55555555, $55555555, $55555555 146 | dc.l $00000000, $00000000, $00000000, $00000000 147 | dc.l $00000000, $00000000, $00000000, $00000000 148 | dc.l $66666666, $66666666, $66666666, $66666666 149 | dc.l $66666666, $66666666, $66666666, $66666666 150 | 151 | dc.l $78787878, $87878787, $78787878, $87878787 152 | dc.l $78787878, $87878787, $78787878, $87878787 153 | dc.l $79797979, $97979797, $79797979, $97979797 154 | dc.l $79797979, $97979797, $79797979, $97979797 155 | 156 | dc.l $AAAAAAAA, $AAAAAAAA, $AAAAAAAA, $AAAAAAAA 157 | dc.l $AAAAAAAA, $AAAAAAAA, $AAAAAAAA, $AAAAAAAA 158 | dc.l $BABABABA, $ABABABAB, $BABABABA, $ABABABAB 159 | dc.l $BABABABA, $ABABABAB, $BABABABA, $ABABABAB 160 | 161 | dc.l $56565656, $65656565, $56565656, $65656565 162 | dc.l $56565656, $65656565, $56565656, $65656565 163 | 164 | ; 123456789012345678901234567890123456 165 | 166 | @Str_Title1: dc.b "Echo sound engine", 0 167 | @Str_Title2: dc.b "Version 1.2 by Sik", 0 168 | @Str_Instr1: dc.b "Use D-pad to select song", 0 169 | @Str_Instr2: dc.b "A/C: play, B: stop", 0 170 | even 171 | -------------------------------------------------------------------------------- /src-z80/player/pcm.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; UpdatePCM 3 | ; Updates PCM output upon a timer event 4 | ;**************************************************************************** 5 | 6 | UpdatePCM: 7 | ret ; $C9 = RET = no PCM playback 8 | ; $D0 = RET NC = PCM playback 9 | 10 | exx ; Switch to PCM registers 11 | 12 | UpdatePCM_Again: 13 | ld (ix+0), $27 ; Acknowledge timer 14 | ld (ix+1), $1F 15 | UpdatePCM_Tick: 16 | ;push hl 17 | ;ld hl, $1F27 18 | ;ld ($4000), hl 19 | ;pop hl 20 | 21 | ld a, (hl) ; Fetch next sample 22 | inc a ; Check if it's the end of the waveform 23 | jr z, .stop 24 | ld (ix+0), $2A ; Nope, send sample to YM2612 25 | ld (ix+1), a 26 | 27 | inc l ; Update buffer position 28 | jr z, .reload ; Need to buffer more? 29 | 30 | .nopcm: 31 | exx ; Switch to normal registers 32 | ret ; End of subroutine 33 | 34 | .stop: 35 | ; ld b, $00 ; Stop playback 36 | ld a, $C9 ; Stop playback 37 | ld (UpdatePCM), a 38 | ld (ix+0), $2A ; Turn off DAC 39 | ld (ix+1), $80 40 | ld (ix+0), $2B 41 | ld (ix+1), $00 42 | exx ; Switch to normal registers 43 | ret ; End of subroutine 44 | 45 | .reload: 46 | ld a, (RAM_LastBank) ; Bank switch if needed 47 | cp c 48 | jp z, .noswitchu 49 | ld a, c 50 | ld (RAM_LastBank), a 51 | ld hl, $6000 52 | BankSwitch 53 | .noswitchu: 54 | 55 | ld hl, RAM_PCMBuffer ; Load samples into the buffer 56 | 57 | ld a, (de) ; Samples 1~4 58 | ld (hl), a 59 | inc l 60 | inc e 61 | ld a, (de) 62 | ld (hl), a 63 | inc l 64 | inc e 65 | ld a, (de) 66 | ld (hl), a 67 | inc l 68 | inc e 69 | ld a, (de) 70 | ld (hl), a 71 | inc l 72 | inc e 73 | 74 | ld a, (de) ; Samples 5~8 75 | ld (hl), a 76 | inc l 77 | inc e 78 | ld a, (de) 79 | ld (hl), a 80 | inc l 81 | inc e 82 | ld a, (de) 83 | ld (hl), a 84 | inc l 85 | inc e 86 | ld a, (de) 87 | ld (hl), a 88 | inc l 89 | inc e 90 | 91 | ld a, (de) ; Samples 9~12 92 | ld (hl), a 93 | inc l 94 | inc e 95 | ld a, (de) 96 | ld (hl), a 97 | inc l 98 | inc e 99 | ld a, (de) 100 | ld (hl), a 101 | inc l 102 | inc e 103 | ld a, (de) 104 | ld (hl), a 105 | inc l 106 | inc e 107 | 108 | ld a, (de) ; Samples 13~16 109 | ld (hl), a 110 | inc l 111 | inc e 112 | ld a, (de) 113 | ld (hl), a 114 | inc l 115 | inc e 116 | ld a, (de) 117 | ld (hl), a 118 | inc l 119 | inc e 120 | ld a, (de) 121 | ld (hl), a 122 | inc l 123 | inc e 124 | 125 | jp nz, .nobankchg ; Update high bytes of address if needed 126 | inc d 127 | jp nz, .nobankchg 128 | ld d, $80 129 | inc c 130 | .nobankchg: 131 | 132 | ld l, RAM_PCMBuffer&$FF ; Go back to the beginning of the buffer 133 | jp UpdatePCM_Again ; We took so long we should play the next 134 | ; sample already ._.' 135 | 136 | ;**************************************************************************** 137 | ; PlayPCM* [event $0C] 138 | ; Plays a PCM sample 139 | ;---------------------------------------------------------------------------- 140 | ; input c .... current bank 141 | ; input hl ... current address 142 | ;---------------------------------------------------------------------------- 143 | ; breaks: af, b 144 | ;**************************************************************************** 145 | 146 | PlayPCMSFX: 147 | call PlayPCM ; We're just a wrapper 148 | jp ProcessSFXRun ; End of subroutine 149 | 150 | PlayPCMBGM: 151 | PollPCM 152 | 153 | ld a, (RAM_Locked+6) ; Check if channel is free 154 | or a 155 | jp nz, ProcessBGMSkip1 ; Don't play sample if locked 156 | 157 | call PlayPCM ; We're just a wrapper 158 | jp ProcessBGMRun ; End of subroutine 159 | 160 | PlayPCM: 161 | ld a, (RAM_GlobalVol+$0C) ; Are we allowed to play PCM? 162 | or a 163 | ret z 164 | 165 | call GetParam ; Get sample ID 166 | 167 | ld a, b 168 | exx ; We'll modify PCM data now 169 | 170 | ld h, RAM_PointerList>>8 ; Get offset in pointer list 171 | ld l, a 172 | 173 | ld d, (hl) ; Get PCM address 174 | inc h 175 | ld e, (hl) 176 | inc h 177 | ld c, (hl) 178 | 179 | ld hl, $6000 ; Initial bank switch 180 | ld a, c 181 | ld (RAM_LastBank), a 182 | BankSwitch 183 | 184 | ld h, RAM_PCMBuffer>>8 ; Set buffer where the sample starts 185 | ld a, e 186 | or $F0 187 | ld l, a 188 | 189 | ld b, l 190 | .load1st: ; Copy initial samples into the buffer 191 | ld a, (de) 192 | ld (hl), a 193 | inc e 194 | inc l 195 | jp nz, .load1st 196 | ld l, b 197 | 198 | ld a, e ; Check if the sample should skip ahead 199 | or a ; already 200 | jp nz, .noskip1st 201 | inc d 202 | jp nz, .noskip1st 203 | ld d, $80 204 | inc c 205 | .noskip1st: 206 | 207 | exx ; Back to standard registers 208 | ld a, $D0 ; Enable PCM playback 209 | ld (UpdatePCM), a 210 | 211 | ld (ix+0), $2B ; Turn on DAC 212 | ld (ix+1), $80 213 | ld (ix+0), $2A 214 | ld (ix+1), $80 215 | 216 | ret ; End of subroutine 217 | 218 | ;**************************************************************************** 219 | ; StopPCM* 220 | ; Stops a PCM sample 221 | ;**************************************************************************** 222 | 223 | StopPCMSFX: 224 | call StopPCM ; We're just a wrapper 225 | jp ProcessSFXRun ; End of subroutine 226 | 227 | StopPCMBGM: 228 | PollPCM 229 | 230 | ld a, (RAM_Locked+6) ; Check if channel is free 231 | or a 232 | jp nz, ProcessBGMRun ; Don't stop sample if locked 233 | 234 | call StopPCM ; We're just a wrapper 235 | jp ProcessBGMRun ; End of subroutine 236 | 237 | StopPCM: 238 | ld a, $C9 ; Stop PCM playback 239 | ld (UpdatePCM), a 240 | 241 | ld (ix+0), $2B ; Disable DAC 242 | ld (ix+1), $00 243 | 244 | ret ; End of subroutine 245 | 246 | ;**************************************************************************** 247 | ; LockChannelPCM [event $EC] 248 | ; Locks the PCM channel 249 | ;**************************************************************************** 250 | 251 | LockChannelPCM: 252 | ld a, $01 ; Lock PCM channel 253 | ld (RAM_Locked+6), a 254 | 255 | call StopPCM ; Stop PCM playback 256 | jp ProcessSFXRun ; End of subroutine 257 | 258 | ;**************************************************************************** 259 | ; SetPCMRate [command $07] 260 | ; Changes the sample rate of PCM 261 | ;**************************************************************************** 262 | 263 | SetPCMRate: 264 | ld a, (RAM_ComBank) ; Get new rate 265 | cpl 266 | 267 | ld b, a ; Set high bits of timer 268 | ld hl, $4000 269 | ld (hl), $24 270 | rrca 271 | rrca 272 | or $C0 273 | inc l 274 | ld (hl), a 275 | 276 | ld a, b ; Set low bits of timer 277 | dec l 278 | ld (hl), $25 279 | and $03 280 | inc l 281 | ld (hl), a 282 | 283 | jp EndOfCommand ; End of subroutine 284 | -------------------------------------------------------------------------------- /tester/sound/list.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; PointerList 3 | ; Pointer list used by Echo 4 | ;**************************************************************************** 5 | 6 | PointerList: 7 | Echo_ListEntry Instr_PSGFlat ; $00 [PSG] Flat PSG instrument 8 | Echo_ListEntry Instr_DGuitar ; $01 [FM] Distortion guitar 9 | Echo_ListEntry Instr_Snare ; $02 [PCM] Snare drum 10 | Echo_ListEntry Instr_Kick ; $03 [PCM] Bass drum (kick) 11 | Echo_ListEntry Instr_Strings ; $04 [FM] String ensemble 12 | Echo_ListEntry Instr_Bass ; $05 [FM] Standard bass 13 | Echo_ListEntry Instr_SoftPSG ; $06 [PSG] Soft PSG envelope 14 | Echo_ListEntry Instr_PianoPSG ; $07 [PSG] Piano PSG instrument 15 | Echo_ListEntry Instr_MidiPSG ; $08 [PSG] MIDI square lead 16 | Echo_ListEntry Instr_MidiPiano ; $09 [FM] MIDI piano 17 | Echo_ListEntry Instr_MidiLead1 ; $0A [FM] MIDI square lead 18 | Echo_ListEntry Instr_MidiLead2 ; $0B [FM] MIDI sawtooth lead 19 | Echo_ListEntry Instr_MidiFlute ; $0C [FM] MIDI flute 20 | Echo_ListEntry Instr_NepelPSG ; $0D [PSG] Nepel Four PSG instr. 21 | Echo_ListEntry Instr_MidiSynthBass ; $0E [FM] MIDI synth bass 22 | Echo_ListEntry Instr_MidiLead1F ; $0F [FM] MIDI square (filtered) 23 | Echo_ListEntry Instr_MidiLead2F ; $10 [FM] MIDI sawtooth (filtered) 24 | Echo_ListEntry Instr_Seashore ; $11 [PSG] Seashore 25 | Echo_ListEntry Instr_HitHat ; $12 [PSG] Hit-hat 26 | Echo_ListEntry Instr_PSGString ; $13 [PSG] PSG string 27 | Echo_ListEntry Instr_EGuitar ; $14 [FM] Electric guitar 28 | Echo_ListEnd 29 | 30 | ;**************************************************************************** 31 | ; Instrument $00 [PSG] 32 | ; Flat PSG instrument (no envelope) 33 | ;**************************************************************************** 34 | 35 | Instr_PSGFlat: 36 | dc.b $FE,$00,$FF 37 | 38 | ;**************************************************************************** 39 | ; Instrument $01 [FM] 40 | ; Distortion guitar 41 | ;**************************************************************************** 42 | 43 | Instr_DGuitar: 44 | incbin "data/fm/dguitar.eif" 45 | 46 | ;**************************************************************************** 47 | ; Instrument $02 [PCM] 48 | ; Snare drum 49 | ;**************************************************************************** 50 | 51 | Instr_Snare: 52 | incbin "data/pcm/snare.ewf" 53 | 54 | ;**************************************************************************** 55 | ; Instrument $03 [PCM] 56 | ; Bass drum 57 | ;**************************************************************************** 58 | 59 | Instr_Kick: 60 | incbin "data/pcm/kick.ewf" 61 | 62 | ;**************************************************************************** 63 | ; Instrument $04 [FM] 64 | ; String ensemble 65 | ;**************************************************************************** 66 | 67 | Instr_Strings: 68 | incbin "data/fm/string.eif" 69 | 70 | ;**************************************************************************** 71 | ; Instrument $05 [FM] 72 | ; Standard bass 73 | ;**************************************************************************** 74 | 75 | Instr_Bass: 76 | incbin "data/fm/bass.eif" 77 | 78 | ;**************************************************************************** 79 | ; Instrument $06 [PSG] 80 | ; "Soft" PSG envelope 81 | ;**************************************************************************** 82 | 83 | Instr_SoftPSG: 84 | dc.b $00,$01,$01,$02,$02,$02,$03,$03,$03,$03,$FE,$04,$FF 85 | 86 | ;**************************************************************************** 87 | ; Instrument $07 [PSG] 88 | ; Piano-like PSG instrument 89 | ;**************************************************************************** 90 | 91 | Instr_PianoPSG: 92 | dc.b $00,$01,$02,$03,$04,$04,$05,$05 93 | dc.b $06,$06,$07,$07,$08,$08,$08,$08 94 | dc.b $09,$09,$09,$09,$0A,$0A,$0A,$0A 95 | dc.b $0B,$0B,$0B,$0B,$0C,$0C,$0C,$0C 96 | dc.b $0C,$0C,$0C,$0C,$0D,$0D,$0D,$0D 97 | dc.b $0D,$0D,$0D,$0D,$0E,$0E,$0E,$0E 98 | dc.b $0E,$0E,$0E,$0E,$FE,$0F,$FF 99 | 100 | ;**************************************************************************** 101 | ; Instrument $08 [PSG] 102 | ; MIDI square wave instrument (GM81) 103 | ;**************************************************************************** 104 | 105 | Instr_MidiPSG: 106 | dc.b $00,$01,$02,$FE,$03,$FF 107 | 108 | ;**************************************************************************** 109 | ; Instrument $09 [FM] 110 | ; MIDI acoustic piano (GM01) 111 | ;**************************************************************************** 112 | 113 | Instr_MidiPiano: 114 | incbin "data/fm/piano.eif" 115 | 116 | ;**************************************************************************** 117 | ; Instrument $0A [FM] 118 | ; MIDI square wave instrument (GM81) 119 | ;**************************************************************************** 120 | 121 | Instr_MidiLead1: 122 | incbin "data/fm/square.eif" 123 | 124 | ;**************************************************************************** 125 | ; Instrument $0B [FM] 126 | ; MIDI sawtooth wave instrument (GM82) 127 | ;**************************************************************************** 128 | 129 | Instr_MidiLead2: 130 | incbin "data/fm/saw.eif" 131 | 132 | ;**************************************************************************** 133 | ; Instrument $0C [FM] 134 | ; MIDI flute instrument (GM74) 135 | ;**************************************************************************** 136 | 137 | Instr_MidiFlute: 138 | incbin "data/fm/flute.eif" 139 | 140 | ;**************************************************************************** 141 | ; Instrument $0D [PSG] 142 | ; Nepel Four PSG instrument 143 | ;**************************************************************************** 144 | 145 | Instr_NepelPSG: 146 | dc.b $05,$06,$FE,$07,$FF 147 | 148 | ;**************************************************************************** 149 | ; Instrument $0E [FM] 150 | ; MIDI synth bass (GM39) 151 | ;**************************************************************************** 152 | 153 | Instr_MidiSynthBass: 154 | incbin "data/fm/ebass.eif" 155 | 156 | ;**************************************************************************** 157 | ; Instrument $0F [FM] 158 | ; MIDI square wave instrument (GM81) (filtered) 159 | ;**************************************************************************** 160 | 161 | Instr_MidiLead1F: 162 | incbin "data/fm/squaref.eif" 163 | 164 | ;**************************************************************************** 165 | ; Instrument $10 [FM] 166 | ; MIDI sawtooth wave instrument (GM82) (filtered) 167 | ;**************************************************************************** 168 | 169 | Instr_MidiLead2F: 170 | incbin "data/fm/sawf.eif" 171 | 172 | ;**************************************************************************** 173 | ; Instrument $11 [PSG] 174 | ; Seashore 175 | ;**************************************************************************** 176 | 177 | Instr_Seashore: 178 | dcb.b 4, $0E 179 | dcb.b 4, $0D 180 | dcb.b 4, $0C 181 | dcb.b 4, $0B 182 | dcb.b 4, $0A 183 | dcb.b 4, $09 184 | dcb.b 4, $08 185 | dcb.b 4, $07 186 | dcb.b 4, $06 187 | dcb.b 60, $05 188 | dcb.b 4, $06 189 | dcb.b 4, $07 190 | dcb.b 4, $08 191 | dcb.b 4, $09 192 | dcb.b 4, $0A 193 | dcb.b 4, $0B 194 | dcb.b 4, $0C 195 | dcb.b 4, $0D 196 | dcb.b 4, $0E 197 | dc.b $FE, $0F, $FF 198 | 199 | ;**************************************************************************** 200 | ; Instrument $12 [PSG] 201 | ; Hit-hat 202 | ;**************************************************************************** 203 | 204 | Instr_HitHat: 205 | dc.b $00, $01, $02, $04, $06, $08, $0C 206 | dc.b $FE, $0F, $FF 207 | 208 | ;**************************************************************************** 209 | ; Instrument $13 [PSG] 210 | ; PSG string 211 | ;**************************************************************************** 212 | 213 | Instr_PSGString: 214 | dcb.b 4, $0E 215 | dcb.b 4, $0D 216 | dcb.b 4, $0C 217 | dcb.b 4, $0B 218 | dcb.b 4, $0A 219 | dcb.b 4, $09 220 | dcb.b 4, $08 221 | dc.b $FE, $07, $FF 222 | 223 | ;**************************************************************************** 224 | ; Instrument $14 [FM] 225 | ; Electric guitar 226 | ;**************************************************************************** 227 | 228 | Instr_EGuitar: 229 | incbin "data/fm/eguitar.eif" 230 | 231 | -------------------------------------------------------------------------------- /doc/api-asm.68k: -------------------------------------------------------------------------------- 1 | ============================================================================= 2 | 3 | *** How to use *** 4 | 5 | You need to take "src-68k/echo.68k" and include it in your program. Then you 6 | need to take "built/prog-z80.bin" (or if you built Echo from source, the 7 | generated binary). Finally, go to the echo.68k file, look for @Z80Program 8 | (should be near the end of the file) and change the filename to point where 9 | the prog-z80.bin file is. 10 | 11 | Echo should now be inside your program. Now call Echo_Init (see below) to 12 | initialize Echo and load the instrument list, and then you can proceed to use 13 | Echo as needed (e.g. call Echo_PlayBGM to start playing music). 14 | 15 | Unless stated otherwise, calling the API subroutines will *not* modify the 16 | 68000 registers. 17 | 18 | ============================================================================= 19 | 20 | *** Initialization *** 21 | 22 | Echo_Init 23 | in a0 = pointer to instrument list 24 | 25 | Initializes Echo. Loads the instrument list, loads the Z80 engine and gets 26 | it running. You need to call this before you can use Echo (usually when 27 | the program is just starting). 28 | 29 | The address of the instrument list is given in register a0. The instrument 30 | list can be built using the Echo_List* macros. An example of a short 31 | instrument list is as follows: 32 | 33 | Echo_ListEntry instrument1 34 | Echo_ListEntry instrument2 35 | Echo_ListEntry instrument3 36 | Echo_ListEnd 37 | 38 | Where the parameter for Echo_ListEntry is the address (e.g. a label) to 39 | the EIF/EEF/EWF data of the instrument. 40 | 41 | ============================================================================= 42 | 43 | *** Background music *** 44 | 45 | Echo_PlayBGM 46 | in a0 = pointer to ESF data to play 47 | 48 | Starts playback of the specified background music. The register a0 points 49 | to the ESF data for the background music. 50 | 51 | Echo_StopBGM 52 | 53 | Stops playback of background music. Used both to stop and to pause music. 54 | 55 | Echo_PauseBGM 56 | 57 | Pauses BGM playback (SFXs should be unaffected). 58 | 59 | Echo_ResumeBGM 60 | 61 | Resumes BGM playback after it has been paused with Echo_PauseBGM. 62 | 63 | ============================================================================= 64 | 65 | *** Sound effects *** 66 | 67 | Echo_PlaySFX 68 | in a0 = pointer to ESF data to play 69 | 70 | Starts playback of the specified sound effect. The register a0 points to 71 | the ESF data for the sound effect. 72 | 73 | Echo_StopSFX 74 | 75 | Stops playback of sound effects. 76 | 77 | ============================================================================= 78 | 79 | *** Direct events *** 80 | 81 | Echo_PlayDirect 82 | in a0 = pointer to ESF data to inject 83 | 84 | Injects events to be played as part of the BGM the next tick. The register 85 | a0 points to the ESF data to be injected. 86 | 87 | The injected events are a small stream on their own. The last event must 88 | be $FF (this will return back to the BGM). Do *not* issue $FC, $FD or $FE 89 | events, as you'll just break everything instead. 90 | 91 | The buffer is small, so don't go overboard. There's room for up to 128 92 | bytes (though again, each event is just 2-3 bytes). If there were direct 93 | events pending to play, the new events will be appended at the end, so 94 | take this into account when it comes to the buffer usage. You can check 95 | if there are pending events with Echo_GetStatus (see bit 14) if you're 96 | worried about running out of space. 97 | 98 | The buffer is only checked every tick. 99 | 100 | ============================================================================= 101 | 102 | *** Control *** 103 | 104 | Echo_GetStatus 105 | out d0 = status (see below) 106 | 107 | Gets the current status of Echo. The status is returned as a word in d0, 108 | with the following bits set as relevant: 109 | 110 | Bit 0 .... Sound effect is playing 111 | Bit 1 .... Background music is playing 112 | Bit 14 ... Echo isn't done parsing direct events 113 | Bit 15 ... Echo is busy (can't take commands) 114 | 115 | The API will automatically wait if you try to send a command while Echo is 116 | busy, so the only reason to check for that is if you don't want to halt 117 | the 68000 until Echo is ready to take more commands. 118 | 119 | Echo_SetVolume 120 | in d0 = new volume ($00 = quietest, $FF = loudest) 121 | 122 | Sets the global volume. Register d0 is a byte value ranging from 0 123 | (quietest) to 255 (loudest), and every channel is affected immediately. 124 | The scale of the volume in this case is *linear*. 125 | 126 | Note that since PCM doesn't have volume, it gets toggled on/off depending 127 | on the volume value (the cut off point is at 25%). 128 | 129 | Echo_SetVolumeEx 130 | in a0 = pointer to volume data (see below) 131 | 132 | Sets the global volume for each channel separately. Register a0 points to 133 | a list of 16 bytes (one for each Echo channel). Values for FM and PSG 134 | channels are given in the same way as in events, that is: logarithmic 135 | scale, 0..127 for FM, 0..15 for PSG, lower = louder. You can use the 136 | look-up tables described below (Echo_FMVolTable and Echo_PSGVolTable) to 137 | convert from linear to . 138 | 139 | The last byte (the one belonging to the PCM channel) is used to toggle 140 | whether PCM plays, either 0 (disabled) or 1 (enabled). 141 | 142 | NOTE: the Echo 1.4 docs requested for 13 bytes instead of 16. This has 143 | been changed for the sake of expansion. Currently the extra bytes are 144 | ignored, but consider adapting your code (just set them to zero). 145 | 146 | Echo_FMVolTable 147 | Echo_PSGVolTable 148 | These two are not subroutines but rather look-up tables. They have 64 149 | byte-sized entries and they're used to convert a linear volume value into 150 | a hardware volume value (e.g. for Echo_SetVolumeEx). 151 | 152 | To give an idea of how to use these: take what you'd pass in d0 to 153 | Echo_SetVolume divided by 4 (shift right by 2), then use it as an offset 154 | to these tables. The byte will be the volume as the hardware (or 155 | Echo_SetVolumeEx) wants it. 156 | 157 | ============================================================================= 158 | 159 | *** Settings *** 160 | 161 | Echo_SetPCMRate 162 | in d0 = new PCM rate (see below) 163 | 164 | Changes the sample rate of PCM. Note this is a global parameter as it 165 | affects both BGM and SFX. The value is what one would write in timer A of 166 | the YM2612 register. Here are the approximate frequencies for some values 167 | (default is $04): 168 | 169 | NTSC PAL | NTSC PAL 170 | ----------------------------|-------------------------- 171 | $01 ... 26632Hz ... 26389Hz | $07 ... 6658Hz ... 6597Hz 172 | $02 ... 17755Hz ... 17593Hz | $08 ... 5918Hz ... 5864Hz 173 | $03 ... 13316Hz ... 13194Hz | $09 ... 5326Hz ... 5278Hz 174 | $04 ... 10653Hz ... 10556Hz | $0A ... 4842Hz ... 4798Hz 175 | $05 .... 8877Hz .... 8796Hz | $0B ... 4439Hz ... 4398Hz 176 | $06 .... 7609Hz .... 7539Hz | $0C ... 4097Hz ... 4060Hz 177 | 178 | The higher the sample rate, the better quality, but also takes up more 179 | space and, more importantly, reduces CPU time available for other things 180 | (which can hamper Echo's ability to process complex streams). Be careful 181 | if you increase the sample rate. 182 | 183 | Echo_SetStereo 184 | in d0 = $00 to use mono, otherwise to use stereo 185 | 186 | Toggles whether sound is forced to mono (d0 == $00) or if stereo panning 187 | works (d0 != $00). Will take effect for all following panning events. Can 188 | be used to implement a mono/stereo toggle in games. 189 | 190 | By default Echo is in stereo mode. 191 | 192 | ============================================================================= 193 | 194 | *** Raw access *** 195 | 196 | Echo_SendCommand 197 | in d0 = command 198 | 199 | Sends an argument-less command to Echo. The command ID is given as a byte 200 | in register d0. 201 | 202 | Echo_SendCommandAddr 203 | in d0 = command 204 | in a0 = address 205 | 206 | Sends a command to Echo that takes an address as its argument. The command 207 | ID is given as a byte in register d0, while the address argument is given 208 | in register a0. 209 | 210 | Echo_SendCommandByte 211 | in d0 = command 212 | in d1 = argument 213 | 214 | Sends a command to Echo that takes a byte as its argument. The command ID 215 | is given as a byte in register d0, while the byte argument is given in 216 | register d1. 217 | 218 | ============================================================================= 219 | -------------------------------------------------------------------------------- /src-68k/esf.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; Channel ID constants 3 | ;**************************************************************************** 4 | 5 | ESF_FM1 equ $00 ; FM channel #1 6 | ESF_FM2 equ $01 ; FM channel #2 7 | ESF_FM3 equ $02 ; FM channel #3 8 | ESF_FM4 equ $04 ; FM channel #4 9 | ESF_FM5 equ $05 ; FM channel #5 10 | ESF_FM6 equ $06 ; FM channel #6 11 | ESF_PSG1 equ $08 ; PSG square channel #1 12 | ESF_PSG2 equ $09 ; PSG square channel #2 13 | ESF_PSG3 equ $0A ; PSG square channel #3 14 | ESF_PSG4 equ $0B ; PSG noise channel 15 | ESF_PCM equ $0C ; PCM channel 16 | 17 | ;**************************************************************************** 18 | ; FM note frequency constants 19 | ;**************************************************************************** 20 | 21 | ESF_FMFREQ_C equ 644 22 | ESF_FMFREQ_CS equ 681 23 | ESF_FMFREQ_D equ 722 24 | ESF_FMFREQ_DS equ 765 25 | ESF_FMFREQ_E equ 810 26 | ESF_FMFREQ_F equ 858 27 | ESF_FMFREQ_FS equ 910 28 | ESF_FMFREQ_G equ 964 29 | ESF_FMFREQ_GS equ 1021 30 | ESF_FMFREQ_A equ 1081 31 | ESF_FMFREQ_AS equ 1146 32 | ESF_FMFREQ_B equ 1214 33 | 34 | ESF_FMFREQ_0 equ ESF_FMFREQ_C 35 | ESF_FMFREQ_1 equ ESF_FMFREQ_CS 36 | ESF_FMFREQ_2 equ ESF_FMFREQ_D 37 | ESF_FMFREQ_3 equ ESF_FMFREQ_DS 38 | ESF_FMFREQ_4 equ ESF_FMFREQ_E 39 | ESF_FMFREQ_5 equ ESF_FMFREQ_F 40 | ESF_FMFREQ_6 equ ESF_FMFREQ_FS 41 | ESF_FMFREQ_7 equ ESF_FMFREQ_G 42 | ESF_FMFREQ_8 equ ESF_FMFREQ_GS 43 | ESF_FMFREQ_9 equ ESF_FMFREQ_A 44 | ESF_FMFREQ_10 equ ESF_FMFREQ_AS 45 | ESF_FMFREQ_11 equ ESF_FMFREQ_B 46 | 47 | ;**************************************************************************** 48 | ; Panning values 49 | ;**************************************************************************** 50 | 51 | ESF_PAN_OFF: equ $00 ; Mute 52 | ESF_PAN_L: equ $80 ; Left speaker only 53 | ESF_PAN_R: equ $40 ; Right speaker only 54 | ESF_PAN_LR: equ $C0 ; Both speakers 55 | 56 | ;**************************************************************************** 57 | ; ESF_NoteOn 58 | ; Start playing a note. 59 | ;---------------------------------------------------------------------------- 60 | ; For FM channels: 61 | ; ESF_NoteOn channel, octave, semitone 62 | ; For square PSG channels: 63 | ; ESF_NoteOn channel, octave, semitone 64 | ; For noise PSG channel: 65 | ; ESF_NoteOn channel, type 66 | ; For PCM channel: 67 | ; ESF_NoteOn channel, instrument 68 | ;---------------------------------------------------------------------------- 69 | ; param channel ...... channel to play on 70 | ; param octave ....... octave (0 to 7 for FM, 0 to 5 for PSG) 71 | ; param semitone ..... semitone (0 to 11) 72 | ; param type ......... noise type ($00 to $07) 73 | ; param instrument ... drum instrument ID ($00 to $FF) 74 | ;**************************************************************************** 75 | 76 | ESF_NoteOn macro 77 | dc.b $00+(\1) 78 | if (\1)>8) 138 | dc.b (\3)&$FF 139 | elseif (\1)>6 142 | else 143 | dc.b (\2) 144 | endc 145 | endm 146 | 147 | ;**************************************************************************** 148 | ; ESF_SetInstr 149 | ; Set the instrument of a channel. 150 | ;---------------------------------------------------------------------------- 151 | ; Format: 152 | ; ESF_SetInstr channel, instrument 153 | ;---------------------------------------------------------------------------- 154 | ; param channel ...... Channel to lock 155 | ; param instrument ... Instrument ID ($00 to $FF) 156 | ;**************************************************************************** 157 | 158 | ESF_SetInstr macro 159 | dc.b $40+(\1) 160 | dc.b (\2) 161 | endm 162 | 163 | ;**************************************************************************** 164 | ; ESF_Lock 165 | ; Lock SFX channel. 166 | ;---------------------------------------------------------------------------- 167 | ; Format: 168 | ; ESF_Lock channel 169 | ;---------------------------------------------------------------------------- 170 | ; param channel ... Channel to lock 171 | ;**************************************************************************** 172 | 173 | ESF_Lock macro 174 | dc.b $E0+(\1) 175 | endm 176 | 177 | ;**************************************************************************** 178 | ; ESF_SetPan 179 | ; Set the panning of a FM channel. 180 | ;---------------------------------------------------------------------------- 181 | ; Format: 182 | ; ESF_SetPan channel, panning 183 | ;---------------------------------------------------------------------------- 184 | ; param channel ... channel to modify 185 | ; param panning ... panning (see ESF_PAN_*) 186 | ;**************************************************************************** 187 | 188 | ESF_SetPan macro 189 | dc.b $F0+(\1) 190 | dc.b (\2) 191 | endm 192 | 193 | ;**************************************************************************** 194 | ; ESF_SetFMReg 195 | ; Set a FM register directly. 196 | ;---------------------------------------------------------------------------- 197 | ; Format: 198 | ; ESF_SetFMReg bank, register, value 199 | ;---------------------------------------------------------------------------- 200 | ; param bank ....... YM2612 bank (0 or 1) 201 | ; param register ... register to modify 202 | ; param value ...... value to write 203 | ;**************************************************************************** 204 | 205 | ESF_SetFMReg macro 206 | dc.b $F8+(\1) 207 | dc.b (\2) 208 | dc.b (\3) 209 | endm 210 | 211 | ;**************************************************************************** 212 | ; ESF_Delay 213 | ; Stop event. 214 | ;---------------------------------------------------------------------------- 215 | ; Format: 216 | ; ESF_Delay ticks 217 | ;---------------------------------------------------------------------------- 218 | ; param ticks ... Ticks to wait (60 = 1 second) 219 | ;**************************************************************************** 220 | 221 | ESF_Delay macro 222 | if (\1)>0 223 | if (\1)>=$100 224 | rept (\1)/$100 225 | dc.b $FE, $00 226 | endr 227 | endc 228 | if (\1)%$100 229 | if ((\1)%$100)<=$10 230 | dc.b $D0-1+((\1)%$100) 231 | else 232 | dc.b $FE 233 | dc.b (\1)%$100 234 | endc 235 | endc 236 | endc 237 | endm 238 | 239 | ;**************************************************************************** 240 | ; ESF_Stop 241 | ; Stop event. 242 | ;**************************************************************************** 243 | 244 | ESF_Stop macro 245 | dc.b $FF 246 | endm 247 | -------------------------------------------------------------------------------- /src-z80/core/sfx.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; PlaySFX [command $02] 3 | ; Plays a SFX 4 | ;---------------------------------------------------------------------------- 5 | ; breaks: all 6 | ;**************************************************************************** 7 | 8 | PlaySFX: 9 | PollPCM 10 | call ClearSFX ; Clear SFX resources 11 | PollPCM 12 | 13 | ld a, (RAM_Status) ; Show SFX playback in Echo's status 14 | or $01 15 | ld (RAM_Status), a 16 | 17 | PollPCM 18 | 19 | ld hl, RAM_ComBank ; Get command parameters 20 | ld c, (hl) 21 | inc l 22 | ld e, (hl) 23 | inc l 24 | ld d, (hl) 25 | 26 | PollPCM 27 | 28 | ld hl, RAM_SFXData ; Set SFX as playing 29 | ld (hl), $01 30 | inc l ; No delays! 31 | ld (hl), $01 32 | inc l ; Store SFX start bank 33 | ld (hl), c 34 | inc l ; Store SFX start address (low) 35 | ld (hl), e 36 | inc l ; Store SFX start address (high) 37 | ld (hl), d 38 | 39 | PollPCM 40 | 41 | ld hl, ProcessSFX ; Tell Echo to process SFX 42 | ld (DoTick_SFX+1), hl 43 | 44 | PollPCM 45 | jp EndOfCommand ; End of subroutine 46 | 47 | ;**************************************************************************** 48 | ; ProcessSFX 49 | ; Processes a tick for a SFX 50 | ;---------------------------------------------------------------------------- 51 | ; breaks: all 52 | ;**************************************************************************** 53 | 54 | ProcessSFX: 55 | PollPCM 56 | 57 | ld hl, RAM_SFXData+1 ; SFX data address 58 | 59 | ld a, (hl) ; Delaying? 60 | dec a 61 | jp z, .nodelay 62 | ld (hl), a 63 | 64 | jp DoTick_SFXSkip ; End of subroutine 65 | 66 | .nodelay: 67 | PollPCM 68 | 69 | inc l ; Get current address 70 | ld c, (hl) 71 | inc l 72 | ld e, (hl) 73 | inc l 74 | ld d, (hl) 75 | ex de, hl 76 | 77 | ProcessSFXRun: 78 | PollPCM ; Fetch next event 79 | call GetParam 80 | PollPCM 81 | 82 | ld a, b ; Parse byte 83 | 84 | cp $08 85 | jp c, NoteOnFMSFX ; Events $00-$07: note on FM 86 | cp $0B 87 | jp c, NoteOnPSGSFX ; Events $08-$0A: note on PSG (square) 88 | jp z, NoteOnNoiseSFX ; Event $0B: note on PSG (noise) 89 | cp $0C 90 | jp z, PlayPCMSFX ; Event $0C: note on PCM 91 | 92 | PollPCM 93 | ld a, b 94 | 95 | cp $18 96 | jp c, NoteOffFMSFX ; Events $10-$17: note off FM 97 | cp $1C 98 | jp c, NoteOffPSGSFX ; Events $18-$1B: note off PSG 99 | jp z, StopPCMSFX ; Event $1C: note off PCM 100 | 101 | PollPCM 102 | ld a, b 103 | 104 | cp $FE 105 | jp z, SetDelaySFX ; Event $FE: set delay 106 | cp $FF 107 | jp z, StopSFXEvent ; Event $FF: stop SFX 108 | 109 | PollPCM 110 | ld a, b 111 | 112 | cp $28 113 | jp c, SetFMVolSFX ; Events $28-$2B: set FM volume 114 | cp $2C 115 | jp c, SetPSGVolSFX ; Events $28-$2B: set PSG volume 116 | 117 | PollPCM 118 | ld a, b 119 | 120 | cp $38 121 | jp c, SetNoteFMSFX ; Events $30-$37: set FM note 122 | cp $3B 123 | jp c, SetNotePSGSFX ; Events $38-$3A: set PSG note (square) 124 | jp z, SetNoteNoiseSFX ; Event $3B: set PSG note (noise) 125 | 126 | PollPCM 127 | ld a, b 128 | 129 | cp $48 130 | jp c, LoadFMSFX ; Events $40-$47: load FM instrument 131 | cp $4C 132 | jp c, LoadPSGSFX ; Events $48-$4B: load PSG instrument 133 | 134 | PollPCM 135 | ld a, b 136 | 137 | cp $E0 ; Events $D0-$DF: short set delay 138 | jp c, SetDelaySFXShort 139 | 140 | cp $E8 141 | jp c, LockChannelFM ; Events $E0-$E7: lock FM channel 142 | cp $EC 143 | jp c, LockChannelPSG ; Events $E8-$EB: lock PSG channel 144 | jp z, LockChannelPCM ; Event $EC: lock PCM channel 145 | 146 | PollPCM 147 | ld a, b 148 | 149 | cp $F8 ; Events $F0-$F7: set FM parameters 150 | jp c, SetFMParamSFX 151 | cp $FA ; Events $F8-$F9: set FM register 152 | jp c, SetFMRegSFX 153 | jp z, SetFlagsSFX ; Events $FA-$FB: set/clear flags 154 | cp $FB 155 | jp z, ClearFlagsSFX 156 | 157 | cp $FC 158 | jp z, LoopSFX ; Event $FC: loop SFX 159 | cp $FD 160 | jp z, SetLoopSFX ; Event $FD: set loop point 161 | 162 | ;**************************************************************************** 163 | ; StopSFX* [command $03, event $FF] 164 | ; Stops SFX playback 165 | ;---------------------------------------------------------------------------- 166 | ; breaks: all 167 | ;**************************************************************************** 168 | 169 | StopSFXEvent: 170 | call StopSFX ; We're just a wrapper 171 | jp DoTick_SFXSkip ; End of subroutine 172 | 173 | StopSFXCmd: 174 | call StopSFX ; We're just a wrapper 175 | jp EndOfCommand ; End of subroutine 176 | 177 | StopSFX: 178 | PollPCM 179 | 180 | ld a, (RAM_Status) ; Hide SFX playback in Echo's status 181 | and $FE 182 | ld (RAM_Status), a 183 | 184 | PollPCM 185 | 186 | xor a ; Stop playback 187 | ld (RAM_SFXPlaying), a 188 | ld hl, DoTick_SFXSkip 189 | ld (DoTick_SFX+1), hl 190 | 191 | PollPCM 192 | call ClearSFX ; Clear SFX resources 193 | 194 | PollPCM 195 | ret ; End of subroutine 196 | 197 | ;**************************************************************************** 198 | ; ClearSFX 199 | ; Clears SFX resources 200 | ;---------------------------------------------------------------------------- 201 | ; breaks: all 202 | ;**************************************************************************** 203 | 204 | ClearSFX: 205 | ld a, (RAM_Locked+6) ; Stop PCM playback if needed 206 | or a 207 | call nz, StopPCM 208 | 209 | ;---------------------------------------------------------------------------- 210 | 211 | ld b, 4 ; Look for locked PSG channels 212 | ld de, RAM_Locked+11 213 | .unlockpsg: 214 | 215 | PollPCM 216 | 217 | ld a, (de) ; Check if this channel needs unlocking 218 | or a 219 | jr z, .psgfree 220 | xor a 221 | ld (de), a 222 | 223 | PollPCM 224 | 225 | ld a, b ; Restore BGM volume 226 | rrca 227 | rrca 228 | rrca 229 | rrca 230 | dec a 231 | ld h, RAM_PSGData>>8 232 | ld l, a 233 | ld c, (hl) 234 | sub 15 235 | ld l, a 236 | ld (hl), c 237 | 238 | PollPCM 239 | push de 240 | 241 | ld a, l ; Restore BGM envelope 242 | add 8 243 | ld l, a 244 | add 12-8 245 | ld e, a 246 | ld d, h 247 | 248 | PollPCM 249 | 250 | ld a, (de) 251 | ld (hl), a 252 | inc l 253 | inc e 254 | ld a, (de) 255 | ld (hl), a 256 | inc l 257 | inc e 258 | ld a, (de) 259 | ld (hl), a 260 | 261 | pop de 262 | PollPCM 263 | 264 | .psgfree: 265 | dec e ; Go for next PSG channel to unlock 266 | djnz .unlockpsg 267 | 268 | ;---------------------------------------------------------------------------- 269 | 270 | ld b, 8 ; Look for locked FM channels 271 | .unlockfm: 272 | 273 | PollPCM 274 | 275 | ld a, (de) ; Check if this channel needs unlocking 276 | or a 277 | jp z, .fmfree 278 | xor a 279 | ld (de), a 280 | 281 | PollPCM 282 | 283 | dec b ; Restore FM channel 284 | ld a, b 285 | call KillFM 286 | call RestoreFM 287 | inc b 288 | 289 | .fmfree: 290 | dec e ; Go for next FM channel to unlock 291 | djnz .unlockfm 292 | 293 | ;---------------------------------------------------------------------------- 294 | 295 | ret ; End of subroutine 296 | 297 | ;**************************************************************************** 298 | ; LoopSFX [event $FC] 299 | ; Makes a SFX loop 300 | ;**************************************************************************** 301 | 302 | LoopSFX: 303 | PollPCM 304 | 305 | ld hl, (RAM_SFXLoopPoint+1) ; Get looping address 306 | ld a, (RAM_SFXLoopPoint) 307 | ld c, a 308 | 309 | jp ProcessSFXRun ; End of subroutine 310 | 311 | ;**************************************************************************** 312 | ; SetLoopSFX [event $FD] 313 | ; Sets the SFX loop point 314 | ;**************************************************************************** 315 | 316 | SetLoopSFX: 317 | PollPCM 318 | 319 | ld a, c ; Store loop point address 320 | ld (RAM_SFXLoopPoint), a 321 | ld (RAM_SFXLoopPoint+1), hl 322 | 323 | jp ProcessSFXRun ; End of subroutine 324 | -------------------------------------------------------------------------------- /src-z80/core/main.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; EntryPoint 3 | ; Where the program starts 4 | ;**************************************************************************** 5 | 6 | EntryPoint: 7 | xor a ; Reset Echo status (we don't clear 8 | ld (RAM_Status), a ; RAM_Command since Echo_Init fills in values 9 | ld (RAM_Command2), a ; before Echo gets to run!) 10 | 11 | ld sp, RAM_Stack ; Init stack 12 | 13 | ld hl, $7F11 ; Mute PSG 14 | ld (hl), $9F 15 | ld (hl), $BF 16 | ld (hl), $DF 17 | ld (hl), $FF 18 | xor a 19 | ld (RAM_PSGData), a 20 | ld (RAM_PSGData+16), a 21 | ld (RAM_PSGData+32), a 22 | ld (RAM_PSGData+48), a 23 | dec a 24 | ld (RAM_PSGData+11), a 25 | ld (RAM_PSGData+11+16), a 26 | ld (RAM_PSGData+11+32), a 27 | ld (RAM_PSGData+11+48), a 28 | 29 | ld hl, $6000 ; Set default bank 30 | ld (hl), l 31 | ld (hl), l 32 | ld (hl), l 33 | ld (hl), l 34 | ld (hl), l 35 | ld (hl), l 36 | ld (hl), l 37 | ld (hl), l 38 | ld (hl), l 39 | 40 | ld ix, $4000 ; YM2612 I/O ports base address 41 | ld iy, $4000 42 | 43 | exx ; Init PCM playback status 44 | ld b, $00 ; Not playing 45 | exx 46 | 47 | ld (ix+0), $2B ; Disable DAC by default 48 | ld (ix+1), $00 49 | 50 | ld e, $7F ; Mute all FM channels 51 | ld a, $40 52 | ld b, 4 53 | .mutefm: 54 | ld (ix+0), a 55 | ld (ix+1), e 56 | ld (ix+2), a 57 | ld (ix+3), e 58 | inc a 59 | ld (ix+0), a 60 | ld (ix+1), e 61 | ld (ix+2), a 62 | ld (ix+3), e 63 | inc a 64 | ld (ix+0), a 65 | ld (ix+1), e 66 | ld (ix+2), a 67 | ld (ix+3), e 68 | inc a 69 | inc a 70 | djnz .mutefm 71 | 72 | ld (ix+0), $B4 ; Ensure all channels can be heard from both 73 | ld (ix+1), $C0 ; speakers (by default they're mute!) 74 | ld (ix+0), $B5 75 | ld (ix+1), $C0 76 | ld (ix+0), $B6 77 | ld (ix+1), $C0 78 | ld (ix+2), $B4 79 | ld (ix+3), $C0 80 | ld (ix+2), $B5 81 | ld (ix+3), $C0 82 | ld (ix+2), $B6 83 | ld (ix+3), $C0 84 | 85 | ld (ix+0), $24 ; Init timers 86 | ld (ix+1), $FE 87 | ld (ix+0), $25 88 | ld (ix+1), $03 89 | ld (ix+0), $26 90 | ld (ix+1), $C9 91 | ld (ix+0), $27 92 | ld (ix+1), $3F 93 | 94 | jp IdleLoop ; Go into idle loop 95 | 96 | ;**************************************************************************** 97 | ; RunCommand 98 | ; Checks which command to run 99 | ;---------------------------------------------------------------------------- 100 | ; notes: doesn't return 101 | ;---------------------------------------------------------------------------- 102 | ; To-do: replace with pointer list? 103 | ;**************************************************************************** 104 | 105 | RunCommand: 106 | dec a ; Command $01: load list 107 | jp z, LoadList 108 | dec a ; Command $02: play SFX 109 | jp z, PlaySFX 110 | dec a ; Command $03: stop SFX 111 | jp z, StopSFXCmd 112 | dec a ; Command $04: play BGM 113 | jp z, PlayBGM 114 | dec a ; Command $05: stop BGM 115 | jp z, StopBGMCmd 116 | dec a ; Command $06: resume BGM 117 | jp z, ResumeBGM 118 | dec a ; Command $07: set PCM rate 119 | jp z, SetPCMRate 120 | dec a ; Command $08: pause BGM 121 | jp z, PauseBGM 122 | dec a ; Command $09: set stereo 123 | jp z, SetStereo 124 | 125 | PollPCM 126 | 127 | ; Bad command, ignore >:( 128 | 129 | ;**************************************************************************** 130 | ; EndOfCommand 131 | ; Cleans up when a command finishes 132 | ;---------------------------------------------------------------------------- 133 | ; notes: doesn't return 134 | ;**************************************************************************** 135 | 136 | EndOfCommand: 137 | ld hl, ($1FF8) ; Copy second slot into first 138 | ld ($1FFC), hl 139 | ld hl, ($1FFA) 140 | ld ($1FFE), hl 141 | 142 | xor a ; Free up second slot 143 | ld (RAM_Command2), a 144 | 145 | PollPCM 146 | 147 | ;**************************************************************************** 148 | ; IdleLoop 149 | ; Loop that runs when not processing SFX or BGM 150 | ;---------------------------------------------------------------------------- 151 | ; notes: doesn't return (d'oh) 152 | ;**************************************************************************** 153 | 154 | IdleLoop: 155 | ld a, (RAM_Command) ; Look for commands 156 | or a 157 | jr nz, RunCommand 158 | 159 | PollPCM ; Poll PCM 160 | 161 | ld a, ($4000) ; Tick? 162 | bit 1, a 163 | jr nz, DoTick 164 | bit 0, a ; Poll PCM again 165 | call nz, UpdatePCM ; Not using macro for optimization purposes 166 | 167 | jp IdleLoop ; Keep idling 168 | 169 | ;**************************************************************************** 170 | ; DoTick 171 | ; Called whenever a new tick triggers 172 | ;---------------------------------------------------------------------------- 173 | ; notes: doesn't return 174 | ;**************************************************************************** 175 | 176 | DoTick: 177 | PollPCM 178 | 179 | ld (ix+0), $27 ; Retrigger the timer 180 | ld (ix+1), $2F 181 | DoTick_Tick: 182 | 183 | PollPCM 184 | 185 | ld a, ($1FF1) ; Refresh volume if needed 186 | or a 187 | call nz, RefreshVolume 188 | 189 | DoTick_SFX: ; Process SFXs 190 | jp DoTick_SFXSkip 191 | DoTick_SFXSkip: 192 | 193 | PollPCM 194 | 195 | call ProcessDirect ; Process direct events 196 | 197 | PollPCM 198 | 199 | ld a, (RAM_Paused) ; BGMs are paused? 200 | or a 201 | jr nz, DoTick_BGMSkip 202 | DoTick_BGM: ; Process BGMs 203 | jp DoTick_BGMSkip 204 | DoTick_BGMSkip: 205 | 206 | PollPCM 207 | 208 | jp UpdatePSG ; Update PSG envelopes 209 | DoTick_PSGSkip: 210 | 211 | PollPCM 212 | 213 | jp IdleLoop ; End of subroutine 214 | 215 | ;**************************************************************************** 216 | ; LoadList [command $01] 217 | ; Loads the pointer list 218 | ;---------------------------------------------------------------------------- 219 | ; notes: doesn't return 220 | ;**************************************************************************** 221 | 222 | LoadList: 223 | ld hl, (RAM_ComAddr) ; Get command parameters 224 | ld a, (RAM_ComBank) 225 | ld c, a 226 | 227 | ld de, RAM_PointerList ; Where the pointer list starts 228 | 229 | .loadloop: 230 | call GetParam ; Get high byte address 231 | ld a, b ; Is it the end of the list? 232 | or a 233 | jp z, .loadend 234 | ld (de), a ; Store high byte address 235 | inc d 236 | 237 | call GetParam ; Get low address byte 238 | ld a, b 239 | ld (de), a 240 | inc d 241 | 242 | call GetParam ; Get bank byte 243 | ld a, b 244 | ld (de), a 245 | 246 | dec d ; Go for next byte 247 | dec d 248 | inc e 249 | jp .loadloop 250 | 251 | .loadend: 252 | jp EndOfCommand ; End of subroutine 253 | 254 | ;**************************************************************************** 255 | ; GetParam 256 | ; Subroutine for getting the parameter byte 257 | ;---------------------------------------------------------------------------- 258 | ; input c .... current bank 259 | ; input hl ... current address 260 | ;---------------------------------------------------------------------------- 261 | ; output b .... value 262 | ; output c .... new bank 263 | ; output hl ... new address 264 | ;---------------------------------------------------------------------------- 265 | ; breaks: af 266 | ;---------------------------------------------------------------------------- 267 | ; note: the C value gets incremented *only* when HL hits $0000 (this is 268 | ; relevant if you consider using it to fetch from Z80 RAM, which should 269 | ; never result in HL becoming $0000). 270 | ;**************************************************************************** 271 | 272 | GetParam: 273 | ld a, (RAM_LastBank) ; Bank switch? 274 | cp c 275 | jp z, .noswitchp 276 | ld a, c 277 | ld (RAM_LastBank), a 278 | push hl 279 | ld hl, $6000 280 | BankSwitch 281 | pop hl 282 | .noswitchp: 283 | ld b, (hl) ; Get value 284 | 285 | inc l ; Get next address 286 | jp nz, .nonewbankp 287 | inc h 288 | jp nz, .nonewbankp 289 | ld h, $80 290 | inc c 291 | .nonewbankp: 292 | 293 | ret ; End of subroutine 294 | -------------------------------------------------------------------------------- /doc/api-c.txt: -------------------------------------------------------------------------------- 1 | ============================================================================= 2 | 3 | *** How to use *** 4 | 5 | Add "echo.c" and "echoblob.h" in your program files. Then include the header 6 | "echo.h" in whatever source files you need to access the Echo API, i.e. 7 | 8 | #include "echo.h" 9 | 10 | (these files are present in the "c" directory) 11 | 12 | Then use echo_init to initialize Echo and load the instrument list (see 13 | below). After that you can use Echo as needed (e.g. call echo_play_bgm to 14 | start playing music, etc.). 15 | 16 | The file "echoblob.h" is the Z80 binary turned into a C array. If you want to 17 | change the blob, just use the included blob2c tool. It's invoked as follows: 18 | 19 | blob2c «input.bin» «output.h» 20 | 21 | ============================================================================= 22 | 23 | *** Initialization *** 24 | 25 | void echo_init(const void* const* list) 26 | 27 | Initializes Echo. Loads the instrument list, loads the Z80 engine and gets 28 | it running. You need to call this before you can use Echo (usually when 29 | the program is just starting). 30 | 31 | The parameter 'list' is a pointer to an array of pointers, where each 32 | entry points to the EIF/EEF/EWF data of each instrument. The list ends 33 | with a NULL pointer. For example: 34 | 35 | const void* const list[] = { 36 | instrument1, 37 | instrument2, 38 | instrument3, 39 | NULL 40 | }; 41 | 42 | (if NULL isn't defined for whatever reason just use 0 instead) 43 | 44 | ============================================================================= 45 | 46 | *** Background music *** 47 | 48 | void echo_play_bgm(const void *esf) 49 | 50 | Starts playback of the specified background music. The parameter 'esf' 51 | points to the ESF data for the background music. 52 | 53 | void echo_stop_bgm() 54 | 55 | Stops playback of background music. Used both to stop and to pause music. 56 | 57 | void echo_pause_bgm() 58 | 59 | Pauses BGM playback (SFXs should be unaffected). 60 | 61 | void echo_resume_bgm() 62 | 63 | Resumes BGM playback after it has been paused with echo_pause_bgm. 64 | 65 | ============================================================================= 66 | 67 | *** Sound effects *** 68 | 69 | void echo_play_sfx(const void *esf) 70 | 71 | Starts playback of the specified sound effect. The parameter 'esf' points 72 | to the ESF data for the sound effect. 73 | 74 | void echo_stop_sfx() 75 | 76 | Stops playback of sound effects. 77 | 78 | ============================================================================= 79 | 80 | *** Direct events *** 81 | 82 | void echo_play_direct(const void *esf) 83 | 84 | Injects events to be played as part of the BGM the next tick. The 85 | parameter 'esf' points to the ESF data to be injected. 86 | 87 | The injected events are a small stream on their own. The last event must 88 | be $FF (this will return back to the BGM). Do *not* issue $FC, $FD or $FE 89 | events, as you'll just break everything instead. 90 | 91 | The buffer is small, so don't go overboard. There's room for up to 128 92 | bytes (though again, each event is just 2-3 bytes). If there were direct 93 | events pending to play, the new events will be appended at the end, so 94 | take this into account when it comes to the buffer usage. You can check 95 | if there are pending events with Echo_GetStatus (see ECHO_STAT_DIRBUSY) 96 | if you're worried about running out of space. 97 | 98 | The buffer is only checked every tick. 99 | 100 | ============================================================================= 101 | 102 | *** Control *** 103 | 104 | uint16_t echo_get_status() 105 | 106 | Gets the current status of Echo. Returns an OR of the following flags, 107 | as relevant: 108 | 109 | ECHO_STAT_BGM ....... Background music is playing 110 | ECHO_STAT_SFX ....... Sound effect is playing 111 | ECHO_STAT_DIRBUSY ... Echo isn't done parsing direct events 112 | ECHO_STAT_BUSY ...... Echo is busy (can't take commands) 113 | 114 | The API will automatically wait if you try to send a command while Echo is 115 | busy, so the only reason to check for that is if you don't want to halt 116 | the 68000 until Echo is ready to take more commands. 117 | 118 | void echo_set_volume(uint8_t vol) 119 | 120 | Sets the global volume. The value 'vol' ranges from 0 (quietest) to 255 121 | (loudest), and every channel is affected immediately. The scale of the 122 | volume in this case is *linear*. 123 | 124 | Note that since PCM doesn't have volume, it gets toggled on/off depending 125 | on the volume value (the cut off point is at 25%). 126 | 127 | void echo_set_volume_ex(const uint8_t *ptr) 128 | 129 | Sets the global volume for each channel separately. The parameter 'ptr' 130 | points to a list of 16 bytes (one for each Echo channel). Values for FM 131 | and PSG channels are given in the same way as in events, that is: 132 | logarithmic scale, 0..127 for FM, 0..15 for PSG, lower = louder. 133 | 134 | The last byte (the one belonging to the PCM channel) is used to toggle 135 | whether PCM plays, either 0 (disabled) or 1 (enabled). 136 | 137 | NOTE: the Echo 1.4 docs requested for 13 bytes instead of 16. This has 138 | been changed for the sake of expansion. Currently the extra bytes are 139 | ignored, but consider adapting your code (just set them to zero). 140 | 141 | const uint8_t echo_fm_vol_table[] 142 | const uint8_t echo_psg_vol_table[] 143 | These two are not subroutines but rather look-up tables. They have 64 144 | byte-sized entries and they're used to convert a linear volume value into 145 | a hardware volume value (e.g. for echo_set_volume_ex). 146 | 147 | To give an idea of how to use these: take what you'd pass as argument to 148 | echo_set_volume divided by 4 (shift right by 2), then use it as an index 149 | to these arrays. The byte will be the volume as the hardware (or 150 | echo_set_volume_ex) wants it. 151 | 152 | ============================================================================= 153 | 154 | *** Settings *** 155 | 156 | void echo_set_pcm_rate(uint8_t rate) 157 | 158 | Changes the sample rate of PCM. Note this is a global parameter as it 159 | affects both BGM and SFX. The value is what one would write in timer A of 160 | the YM2612 register. Here are the approximate frequencies for some values 161 | (default is 0x04): 162 | 163 | NTSC PAL | NTSC PAL 164 | -----------------------------|--------------------------- 165 | 0x01 ... 26632Hz ... 26389Hz | 0x07 ... 6658Hz ... 6597Hz 166 | 0x02 ... 17755Hz ... 17593Hz | 0x08 ... 5918Hz ... 5864Hz 167 | 0x03 ... 13316Hz ... 13194Hz | 0x09 ... 5326Hz ... 5278Hz 168 | 0x04 ... 10653Hz ... 10556Hz | 0x0A ... 4842Hz ... 4798Hz 169 | 0x05 .... 8877Hz .... 8796Hz | 0x0B ... 4439Hz ... 4398Hz 170 | 0x06 .... 7609Hz .... 7539Hz | 0x0C ... 4097Hz ... 4060Hz 171 | 172 | The higher the sample rate, the better quality, but also takes up more 173 | space and, more importantly, reduces CPU time available for other things 174 | (which can hamper Echo's ability to process complex streams). Be careful 175 | if you increase the sample rate. 176 | 177 | void echo_set_stereo(int enable) 178 | 179 | Toggles whether sound is forced to mono (enable == 0) or if stereo panning 180 | works (enable != 0). Will take effect for all following panning events. 181 | Can be used to implement a mono/stereo toggle in games. 182 | 183 | By default Echo is in stereo mode. 184 | 185 | ============================================================================= 186 | 187 | *** Raw access *** 188 | 189 | void echo_send_command(uint8_t command) 190 | 191 | Sends an argument-less command to Echo. The parameter 'command' is the 192 | command to send, and may be one of the following: 193 | 194 | ECHO_CMD_STOPBGM ..... Stop background music playback 195 | ECHO_CMD_PAUSEBGM .... Pause background music playback 196 | ECHO_CMD_RESUMEBGM ... Resume background music playback 197 | ECHO_CMD_STOPSFX ..... Stop sound effect playback 198 | 199 | void echo_send_command_addr(uint8_t command, const void *address) 200 | 201 | Sends a command to Echo that takes an address as its argument. The 202 | parameter 'command' is the command to send, while the parameter 'address' 203 | is the address to use as argument. The command may be one of these: 204 | 205 | ECHO_CMD_PLAYBGM .... Start background music playback 206 | ECHO_CMD_PLAYSFX .... Start sound effect playback 207 | ECHO_CMD_LOADLIST ... Load instrument list (warning: see below) 208 | 209 | Do *NOT* use ECHO_CMD_LOADLIST unless you *REALLY* know you're doing, this 210 | makes Echo load the instrument list by itself and it expects a different 211 | format from the one used by the C API. 212 | 213 | void echo_send_command_byte(uint8_t command, uint8_t byte) 214 | 215 | Sends a command to Echo that takes a byte as its argument. The parameter 216 | 'command' is the command to send, while the parameter 'byte' is the byte 217 | to use as argument. The command may be... just this for now: 218 | 219 | ECHO_CMD_SETPCMRATE ... Change PCM sample rate 220 | 221 | ============================================================================= 222 | -------------------------------------------------------------------------------- /tester/sound/echo.68k: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; Echo_Z80Request 3 | ; Requests the Z80 bus 4 | ;**************************************************************************** 5 | 6 | Echo_Z80Request macro 7 | move.w #$100, ($A11100) ; Request Z80 bus 8 | @Echo_WaitZ80\@: 9 | btst.b #0, ($A11100) ; Did we get it yet? 10 | bne.s @Echo_WaitZ80\@ ; Keep waiting 11 | endm ; End of macro 12 | 13 | ;**************************************************************************** 14 | ; Echo_Z80Release 15 | ; Releases the Z80 bus 16 | ;**************************************************************************** 17 | 18 | Echo_Z80Release macro 19 | move.w #$000, ($A11100) ; Release Z80 bus 20 | endm ; End of macro 21 | 22 | ;**************************************************************************** 23 | ; Echo_Z80Reset 24 | ; Resets the Z80 and YM2612 25 | ;**************************************************************************** 26 | 27 | Echo_Z80Reset macro 28 | move.w #$000, ($A11200) ; Assert reset line 29 | rept $10 ; Wait until hardware resets 30 | nop ; ... 31 | endr ; ... 32 | move.w #$100, ($A11200) ; Release reset line 33 | endm ; End of macro 34 | 35 | ;**************************************************************************** 36 | ; Echo_Init 37 | ; Initializes Echo 38 | ; 39 | ; input a0.l ... Address of pointer list 40 | ;**************************************************************************** 41 | 42 | Echo_Init: 43 | movem.l d0/a0-a1, -(sp) ; Save registers 44 | 45 | Echo_Z80Reset ; May not work without this... 46 | Echo_Z80Request ; We need the Z80 bus 47 | 48 | move.b #$01, ($A01FFF) ; Command: load pointer list 49 | 50 | move.l a0, d0 ; Easier to manipulate here 51 | move.b d0, ($A01FFD) ; Store low address byte 52 | lsr.l #7, d0 ; Get high address byte 53 | lsr.b #1, d0 ; We skip one bit 54 | bset.l #7, d0 ; Point into bank window 55 | move.b d0, ($A01FFE) ; Store high address byte 56 | lsr.w #8, d0 ; Get bank byte 57 | move.w d0, d1 ; Parse 32X bit separately 58 | lsr.w #1, d1 ; Put 32X bit in place 59 | and.b #$7F, d0 ; Filter out unused bit from addresses 60 | and.b #$80, d1 ; Filter out all but 32X bit 61 | or.b d1, d0 ; Put everything together 62 | move.b d0, ($A01FFC) ; Store bank byte 63 | 64 | lea @Z80Program(pc), a0 ; Where Z80 program starts 65 | lea ($A00000), a1 ; Where Z80 RAM starts 66 | move.w #@Z80ProgSize-1, d0 ; Size of Z80 program (DBF adjusted) 67 | @LoadLoop: ; Go through all the program 68 | move.b (a0)+, (a1)+ ; Copy byte into Z80 RAM 69 | dbf d0, @LoadLoop ; Go for next byte 70 | 71 | Echo_Z80Reset ; Now reset for real 72 | Echo_Z80Release ; Let the Z80 go! 73 | 74 | movem.l (sp)+, d0/a0-a1 ; Restore registers 75 | rts ; End of subroutine 76 | 77 | ;**************************************************************************** 78 | ; Echo Z80 program 79 | ; It should be located wherever Echo_ProgFile was defined 80 | ;**************************************************************************** 81 | 82 | @Z80Program: incbin "\Echo_ProgFile" 83 | @Z80ProgSize equ *-@Z80Program 84 | even 85 | 86 | ;**************************************************************************** 87 | ; Echo_SendCommand 88 | ; Sends an Echo command (no address parameter) 89 | ; 90 | ; input d0.b ... Echo command 91 | ;**************************************************************************** 92 | 93 | Echo_SendCommand: 94 | move.w d1, -(sp) ; Save register 95 | 96 | Echo_Z80Request ; We need the Z80 bus 97 | 98 | @Try: 99 | tst.b ($A01FFF) ; Check if Echo is ready 100 | beq.s @Ready ; Too busy? 101 | Echo_Z80Release ; Let Echo continue 102 | move.w #$FF, d1 ; Give it some time 103 | dbf d1, * ; ... 104 | Echo_Z80Request ; Get Z80 bus back 105 | bra.s @Try ; Try again 106 | 107 | @Ready: 108 | move.b d0, ($A01FFF) ; Write command ID 109 | Echo_Z80Release ; We're done with the Z80 bus 110 | 111 | move.w (sp)+, d1 ; Restore register 112 | rts ; End of subroutine 113 | 114 | ;**************************************************************************** 115 | ; Echo_SendCommandEx 116 | ; Sends an Echo command (with address parameter) 117 | ; 118 | ; input d0.b ... Echo command 119 | ; input a0.l ... Address parameter 120 | ;**************************************************************************** 121 | 122 | Echo_SendCommandEx: 123 | movem.l d0-d1, -(sp) ; Save register 124 | 125 | Echo_Z80Request ; We need the Z80 bus 126 | 127 | @Try: 128 | tst.b ($A01FFF) ; Check if Echo is ready 129 | beq.s @Ready ; Too busy? 130 | Echo_Z80Release ; Let Echo continue 131 | move.w #$FF, d1 ; Give it some time 132 | dbf d1, * ; ... 133 | Echo_Z80Request ; Get Z80 bus back 134 | bra.s @Try ; Try again 135 | 136 | @Ready: 137 | move.b d0, ($A01FFF) ; Write command ID 138 | 139 | move.l a0, d0 ; Easier to manipulate here 140 | move.b d0, ($A01FFD) ; Store low address byte 141 | lsr.l #7, d0 ; Get high address byte 142 | lsr.b #1, d0 ; We skip one bit 143 | bset.l #7, d0 ; Point into bank window 144 | move.b d0, ($A01FFE) ; Store high address byte 145 | 146 | lsr.w #8, d0 ; Get bank byte 147 | move.w d0, d1 ; Parse 32X bit separately 148 | lsr.w #1, d1 ; Put 32X bit in place 149 | and.b #$7F, d0 ; Filter out unused bit from addresses 150 | and.b #$80, d1 ; Filter out all but 32X bit 151 | or.b d1, d0 ; Put everything together 152 | move.b d0, ($A01FFC) ; Store bank byte 153 | 154 | Echo_Z80Release ; We're done with the Z80 bus 155 | 156 | movem.l (sp)+, d0-d1 ; Restore register 157 | rts ; End of subroutine 158 | 159 | ;**************************************************************************** 160 | ; Echo_PlaySFX 161 | ; Plays a SFX 162 | ; 163 | ; input a0.l ... Pointer to SFX data 164 | ;**************************************************************************** 165 | 166 | Echo_PlaySFX: 167 | move.w d0, -(sp) ; Save register 168 | move.b #$02, d0 ; Command $02 = play SFX 169 | bsr Echo_SendCommandEx ; Send command to Echo 170 | move.w (sp)+, d0 ; Restore register 171 | rts ; End of subroutine 172 | 173 | ;**************************************************************************** 174 | ; Echo_StopSFX 175 | ; Stops SFX playback 176 | ;**************************************************************************** 177 | 178 | Echo_StopSFX: 179 | move.w d0, -(sp) ; Save register 180 | move.b #$03, d0 ; Command $03 = stop SFX 181 | bsr Echo_SendCommand ; Send command to Echo 182 | move.w (sp)+, d0 ; Restore register 183 | rts ; End of subroutine 184 | 185 | ;**************************************************************************** 186 | ; Echo_PlayBGM 187 | ; Plays a BGM 188 | ; 189 | ; input a0.l ... Pointer to BGM data 190 | ;**************************************************************************** 191 | 192 | Echo_PlayBGM: 193 | move.w d0, -(sp) ; Save register 194 | move.b #$04, d0 ; Command $04 = play BGM 195 | bsr Echo_SendCommandEx ; Send command to Echo 196 | move.w (sp)+, d0 ; Restore register 197 | rts ; End of subroutine 198 | 199 | ;**************************************************************************** 200 | ; Echo_StopBGM 201 | ; Stops BGM playback 202 | ;**************************************************************************** 203 | 204 | Echo_StopBGM: 205 | move.w d0, -(sp) ; Save register 206 | move.b #$05, d0 ; Command $05 = stop BGM 207 | bsr Echo_SendCommand ; Send command to Echo 208 | move.w (sp)+, d0 ; Restore register 209 | rts ; End of subroutine 210 | 211 | ;**************************************************************************** 212 | ; Echo_ResumeBGM 213 | ; Resumes BGM playback 214 | ;**************************************************************************** 215 | 216 | Echo_ResumeBGM: 217 | move.w d0, -(sp) ; Save register 218 | move.b #$06, d0 ; Command $06 = resume BGM 219 | bsr Echo_SendCommand ; Send command to Echo 220 | move.w (sp)+, d0 ; Restore register 221 | rts ; End of subroutine 222 | 223 | ;**************************************************************************** 224 | ; Echo_GetStatus 225 | ; Gets the current status of Echo 226 | ; 227 | ; output d0.w ... Echo status 228 | ; Bit #0: SFX is playing 229 | ; Bit #1: BGM is playing 230 | ; Bit #15: command still not parsed 231 | ;**************************************************************************** 232 | 233 | Echo_GetStatus: 234 | moveq #0, d0 235 | Echo_Z80Request ; We need the Z80 bus 236 | move.b ($A01FF0), d0 ; Get the status flags 237 | tst.b ($A01FFF) ; Check if command still has to be parsed 238 | beq.s @NotBusy ; Any commands left to be parsed? 239 | bset.l #15, d0 ; If so, set the relevant flag 240 | @NotBusy: 241 | Echo_Z80Release ; Let the Z80 go! 242 | rts ; End of subroutine 243 | 244 | ;**************************************************************************** 245 | ; Echo_ListEntry 246 | ; Defines an entry in a pointer list 247 | ;**************************************************************************** 248 | 249 | Echo_ListEntry macro addr 250 | dc.b $80|((addr)>>8&$7F) ; High byte of address 251 | dc.b (addr)&$FF ; Low byte of address 252 | dc.b ((addr)>>15&$7F)|((addr)>>16&$80) ; Bank number 253 | endm 254 | 255 | ;**************************************************************************** 256 | ; Echo_ListEnd 257 | ; Ends a pointer list 258 | ;**************************************************************************** 259 | 260 | Echo_ListEnd macro 261 | dc.b $00 ; End of list mark 262 | even ; Just in case... 263 | endm 264 | -------------------------------------------------------------------------------- /src-z80/core/bgm.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; PlayBGM [command $04] 3 | ; Plays a BGM 4 | ;---------------------------------------------------------------------------- 5 | ; breaks: all 6 | ;**************************************************************************** 7 | 8 | PlayBGM: 9 | PollPCM 10 | call ClearBGM ; Clear BGM resources 11 | PollPCM 12 | 13 | ld a, (RAM_Status) ; Show BGM playback in Echo's status 14 | or $02 15 | ld (RAM_Status), a 16 | 17 | PollPCM 18 | 19 | ld hl, RAM_ComBank ; Get command parameters 20 | ld c, (hl) 21 | inc l 22 | ld e, (hl) 23 | inc l 24 | ld d, (hl) 25 | 26 | PollPCM 27 | 28 | xor a ; Playing a BGM immediately unpauses playback 29 | ld (RAM_Paused), a 30 | 31 | inc a 32 | ld hl, RAM_BGMData ; Set BGM as playing 33 | ld (hl), a 34 | inc l ; No delays! 35 | ld (hl), a 36 | inc l ; Store BGM start bank 37 | ld (hl), c 38 | inc l ; Store BGM start address (low) 39 | ld (hl), e 40 | inc l ; Store BGM start address (high) 41 | ld (hl), d 42 | 43 | PollPCM 44 | 45 | ld hl, ProcessBGM ; Tell Echo to process BGM 46 | ld (DoTick_BGM+1), hl 47 | 48 | ld b, 8 ; Force BGM volume of all FM channels to $7F 49 | ld c, $7F ; (so trying to unlock an unused channel 50 | ld hl, RAM_BGMFMVol ; doesn't result in audible garbage) 51 | .playunmute: 52 | ld (hl), c 53 | inc l 54 | djnz .playunmute 55 | 56 | jp EndOfCommand ; End of subroutine 57 | 58 | ;**************************************************************************** 59 | ; PauseBGM [command $08] 60 | ; Pauses a playing BGM 61 | ;---------------------------------------------------------------------------- 62 | ; breaks: all 63 | ;**************************************************************************** 64 | 65 | PauseBGM: 66 | ld a, (RAM_BGMPlaying) ; Is BGM even playing? 67 | or a 68 | jp z, EndOfCommand 69 | 70 | ld a, 1 ; Halt BGM playback 71 | ld (RAM_Paused), a 72 | 73 | ;---------------------------------------------------------------------------- 74 | 75 | ld b, $7F ; Mute all FM channels 76 | ld c, 7 77 | ld hl, RAM_Locked+7 78 | .mutefm: 79 | PollPCM 80 | ld a, (hl) 81 | or a 82 | jr nz, .nofmmute 83 | ld a, c 84 | call SetFMVolTempLoad 85 | .nofmmute: 86 | PollPCM 87 | dec l 88 | dec c 89 | jp p, .mutefm 90 | 91 | ld b, 4 ; Mute all PSG channels 92 | ld c, $0F 93 | ld de, RAM_Locked+8 94 | ld hl, RAM_PSGData 95 | .mutepsg: 96 | PollPCM 97 | ld a, (de) 98 | or a 99 | jr nz, .nopsgmute 100 | ld a, (hl) 101 | and $80 102 | or c, 103 | ld (hl), a 104 | .nopsgmute: 105 | PollPCM 106 | ld a, l 107 | add 16 108 | ld l, a 109 | inc e 110 | djnz .mutepsg 111 | 112 | ;---------------------------------------------------------------------------- 113 | 114 | ld a, (RAM_Locked+6) ; Mute PCM channel 115 | or a 116 | call z, StopPCM 117 | 118 | ;---------------------------------------------------------------------------- 119 | 120 | jp EndOfCommand ; End of subroutine 121 | 122 | ;**************************************************************************** 123 | ; ResumeBGM [command $06] 124 | ; Resumes a stopped BGM 125 | ;---------------------------------------------------------------------------- 126 | ; breaks: all 127 | ;**************************************************************************** 128 | 129 | ResumeBGM: 130 | ld a, (RAM_BGMPlaying) ; Was BGM even playing? 131 | or a 132 | jp z, EndOfCommand 133 | 134 | xor a 135 | ld (RAM_Paused), a ; Resume BGM playback 136 | 137 | ld b, 4 ; Restore PSG channels 138 | ld de, RAM_Locked+11 139 | ld hl, RAM_PSGData+63 140 | .resumepsg: 141 | PollPCM 142 | ld c, (hl) 143 | ld a, l 144 | sub 15 145 | ld l, a 146 | ld a, (de) 147 | or a 148 | jr nz, .nopsgresume 149 | ld a, (hl) 150 | and $80 151 | or c 152 | ld (hl), a 153 | .nopsgresume: 154 | PollPCM 155 | dec l 156 | dec e 157 | djnz .resumepsg 158 | 159 | call RefreshVolume ; Restore remaining channels 160 | jp EndOfCommand ; End of subroutine 161 | 162 | ;**************************************************************************** 163 | ; ProcessBGM 164 | ; Processes a tick for a BGM 165 | ;---------------------------------------------------------------------------- 166 | ; breaks: all 167 | ;**************************************************************************** 168 | 169 | ProcessBGM: 170 | PollPCM 171 | 172 | ld hl, RAM_BGMData+1 ; BGM data address 173 | 174 | ld a, (hl) ; Delaying? 175 | dec a 176 | jp z, .nodelay 177 | ld (hl), a 178 | 179 | jp DoTick_BGMSkip ; End of subroutine 180 | 181 | .nodelay: 182 | PollPCM 183 | 184 | inc l 185 | ld c, (hl) ; Get current address 186 | inc l 187 | ld e, (hl) 188 | inc l 189 | ld d, (hl) 190 | ex de, hl 191 | 192 | ProcessBGMRun: 193 | ProcessBGMSkip: 194 | PollPCM ; Fetch next event 195 | call GetParam 196 | PollPCM 197 | 198 | ld a, b ; Parse byte 199 | 200 | cp $08 201 | jp c, NoteOnFMBGM ; Events $00-$07: note on FM 202 | cp $0B 203 | jp c, NoteOnPSGBGM ; Events $08-$0A: note on PSG (square) 204 | jp z, NoteOnNoiseBGM ; Event $0B: note on PSG (noise) 205 | cp $0C 206 | jp z, PlayPCMBGM ; Event $0C: play PCM 207 | 208 | PollPCM 209 | ld a, b 210 | 211 | cp $18 212 | jp c, NoteOffFMBGM ; Events $10-$17: note off FM 213 | cp $1C 214 | jp c, NoteOffPSGBGM ; Events $18-$1B: note off PSG 215 | jp z, StopPCMBGM ; Event $1C: note off PCM 216 | 217 | PollPCM 218 | ld a, b 219 | 220 | cp $FE 221 | jp z, SetDelayBGM ; Event $FE: set delay 222 | cp $FF 223 | ProcessBGMEventFF: 224 | jp z, StopBGMEvent ; Event $FF: stop BGM 225 | cp $FC 226 | jp z, LoopBGM ; Event $FC: loop BGM 227 | cp $FD 228 | jp z, SetLoopBGM ; Event $FD: set loop point 229 | 230 | PollPCM 231 | ld a, b 232 | 233 | cp $28 234 | jp c, SetFMVolBGM ; Events $28-$2B: set FM volume 235 | cp $2C 236 | jp c, SetPSGVolBGM ; Events $28-$2B: set PSG volume 237 | 238 | PollPCM 239 | ld a, b 240 | 241 | cp $38 242 | jp c, SetNoteFMBGM ; Events $30-$37: set FM note 243 | cp $3B 244 | jp c, SetNotePSGBGM ; Events $38-$3A: set PSG note (square) 245 | jp z, SetNoteNoiseBGM ; Event $3B: set PSG note (noise) 246 | 247 | PollPCM 248 | ld a, b 249 | 250 | cp $48 251 | jp c, LoadFMBGM ; Events $40-$47: load FM instrument 252 | cp $4C 253 | jp c, LoadPSGBGM ; Events $48-$4B: load PSG instrument 254 | 255 | PollPCM 256 | ld a, b 257 | 258 | cp $E0 ; Events $D0-$DF: short set delay 259 | jp c, SetDelayBGMShort 260 | 261 | cp $F8 ; Events $F0-$F7: set FM parameters 262 | jp c, SetFMParamBGM 263 | cp $FA ; Events $F8-$F9: set FM register 264 | jp c, SetFMRegBGM 265 | jp z, SetFlagsBGM ; Events $FA-$FB: set/clear flags 266 | cp $FB 267 | jp z, ClearFlagsBGM 268 | 269 | PollPCM ; FFFFFFFFF bad event >:( 270 | ProcessBGMEnd: 271 | jp StopBGMEvent ; End of subroutine 272 | 273 | ProcessBGMSkip2: ; This is where we land after a locked event 274 | PollPCM ; that had two bytes for the parameter 275 | inc l ; Skip first byte 276 | jp nz, .nobankskip2 277 | inc h 278 | jp nz, .nobankskip2 279 | ld h, $80 280 | inc c 281 | .nobankskip2: 282 | 283 | ProcessBGMSkip1: ; This is where we land after a locked event 284 | PollPCM ; that had one byte for the parameter 285 | inc l ; Skip byte 286 | jp nz, .nobankskip1 287 | inc h 288 | jp nz, .nobankskip1 289 | ld h, $80 290 | inc c 291 | .nobankskip1: 292 | 293 | jp ProcessBGMRun ; Keep processing 294 | 295 | ;**************************************************************************** 296 | ; StopBGM* [command $05, event $FF] 297 | ; Stops BGM playback 298 | ;**************************************************************************** 299 | 300 | StopBGMEvent: 301 | call StopBGM ; We're just a wrapper 302 | jp DoTick_BGMSkip ; End of subroutine 303 | 304 | StopBGMCmd: 305 | call StopBGM ; We're just a wrapper 306 | jp EndOfCommand ; End of subroutine 307 | 308 | StopBGM: 309 | ld a, (RAM_Status) ; Hide BGM playback in Echo's status 310 | and $FD 311 | ld (RAM_Status), a 312 | 313 | PollPCM 314 | 315 | call ClearBGM ; Clear BGM resources 316 | PollPCM 317 | 318 | xor a ; Stop playback 319 | ld (RAM_BGMPlaying), a 320 | ld hl, DoTick_BGMSkip 321 | ld (DoTick_BGM+1), hl 322 | 323 | ld b, 8 ; Force BGM volume of all FM channels to $7F 324 | ld c, $7F 325 | ld hl, RAM_BGMFMVol 326 | .stopmute: 327 | ld (hl), c 328 | inc l 329 | djnz .stopmute 330 | 331 | ret ; End of subroutine 332 | 333 | ;**************************************************************************** 334 | ; ClearBGM 335 | ; Clears BGM resources 336 | ;**************************************************************************** 337 | 338 | ClearBGM: 339 | ld a, (RAM_Locked+6) ; Stop PCM playback if needed 340 | or a 341 | call z, StopPCM 342 | 343 | ;---------------------------------------------------------------------------- 344 | 345 | ld b, 4 ; Reset all PSG channels 346 | ld de, RAM_PSGData+48+15 347 | ld hl, RAM_Locked+11 348 | .killpsg: 349 | PollPCM 350 | 351 | ld a, $0F 352 | ld (de), a ; Reset BGM volume 353 | ;ld (hl), 0 354 | 355 | ld a, e 356 | sub 15 357 | ld e, a 358 | 359 | ld a, (hl) ; Mute PSG channel if it isn't locked 360 | or a 361 | jr nz, .nopsgkill 362 | xor a 363 | ld (de), a 364 | 365 | .nopsgkill: 366 | PollPCM 367 | 368 | dec e 369 | dec l 370 | djnz .killpsg 371 | 372 | ;---------------------------------------------------------------------------- 373 | 374 | ld b, 8 ; Reset all FM channels 375 | ld de, RAM_BGMFMVol+7 376 | .killfm: 377 | PollPCM 378 | 379 | ld a, $7F ; Reset BGM volume 380 | ld (de), a 381 | dec e 382 | 383 | PollPCM 384 | 385 | ld a, (hl) ; Kill FM channel if not locked 386 | or a 387 | jp nz, .nofmkill 388 | dec b 389 | 390 | ld a, b 391 | call KillFM 392 | 393 | PollPCM 394 | 395 | ld a, b ; Reset panning 396 | and $04 397 | rrca 398 | ld iyl, a 399 | ld a, b 400 | and $03 401 | add a, $B4 402 | ld (iy+0), a 403 | ld (iy+1), $C0 404 | 405 | inc b 406 | .nofmkill: 407 | dec l 408 | djnz .killfm 409 | 410 | ;---------------------------------------------------------------------------- 411 | 412 | ld hl, RAM_BGMFMPan ; Reset panning status (for restoring) 413 | ld a, $C0 414 | ld b, 8 415 | .initpanstat: 416 | ld (hl), a 417 | inc l 418 | djnz .initpanstat 419 | 420 | xor a ; Reset flags 421 | ld (RAM_Flags), a 422 | 423 | ret ; End of subroutine 424 | 425 | ;**************************************************************************** 426 | ; LoopBGM [event $FC] 427 | ; Makes a BGM loop 428 | ;**************************************************************************** 429 | 430 | LoopBGM: 431 | PollPCM 432 | 433 | ld hl, (RAM_BGMLoopPoint+1) ; Get looping address 434 | ld a, (RAM_BGMLoopPoint) 435 | ld c, a 436 | 437 | jp ProcessBGMRun ; End of subroutine 438 | 439 | ;**************************************************************************** 440 | ; SetLoopBGM [event $FD] 441 | ; Sets the BGM loop point 442 | ;**************************************************************************** 443 | 444 | SetLoopBGM: 445 | PollPCM 446 | 447 | ld a, c ; Store loop point address 448 | ld (RAM_BGMLoopPoint), a 449 | ld (RAM_BGMLoopPoint+1), hl 450 | 451 | jp ProcessBGMRun ; End of subroutine 452 | -------------------------------------------------------------------------------- /src-z80/player/psg.z80: -------------------------------------------------------------------------------- 1 | ;**************************************************************************** 2 | ; UpdatePSG 3 | ; Updates PSG output 4 | ;**************************************************************************** 5 | 6 | UpdatePSG: 7 | ld hl, RAM_PSGData+48 ; PSG envelope data of *last* channel 8 | ld b, 3 ; Go through all channels 9 | .loop: 10 | push bc 11 | xor a 12 | ld iyl, a 13 | 14 | ld a, (hl) ; Get channel volume 15 | or a 16 | jp m, .noskip 17 | ld b, $0F 18 | inc l 19 | jp .skip 20 | .noskip: 21 | and $7F 22 | ld b, a 23 | 24 | inc l ; Add global volume 25 | ld a, (hl) 26 | add b 27 | ld b, a 28 | 29 | PollPCM 30 | push bc 31 | 32 | inc l ; Get current address of envelope 33 | ld c, (hl) 34 | inc l 35 | ld e, (hl) 36 | inc l 37 | ld d, (hl) 38 | ex de, hl 39 | 40 | .readenv: 41 | PollPCM 42 | call GetParam ; Get next byte 43 | PollPCM 44 | 45 | ld a, b 46 | cp $FE ; Set loop point? 47 | jp z, .envsetloop 48 | cp $FF ; Loop envelope? 49 | jp z, .envloop 50 | 51 | ld iyl, b ; Keep byte safe somewhere... 52 | PollPCM 53 | 54 | ex de, hl ; Store new address 55 | ld (hl), d 56 | dec l 57 | ld (hl), e 58 | dec l 59 | ld (hl), c 60 | dec l 61 | 62 | pop bc 63 | PollPCM 64 | 65 | db $FD,$7D ; ld a, iyl ; Mix envelope with volume 66 | and $0F 67 | add b 68 | ld b, a 69 | 70 | cp $10 ; Check for overflow 71 | jr c, .notmute 72 | ld b, $0F 73 | .notmute: 74 | 75 | .skip: 76 | PollPCM 77 | 78 | ld a, b ; Set PSG channel volume 79 | rlca 80 | rlca 81 | rlca 82 | pop bc 83 | or b 84 | rrca 85 | rrca 86 | rrca 87 | or $90 88 | ld ($7F11), a 89 | 90 | PollPCM 91 | push bc 92 | 93 | ld a, l ; Get current semitone 94 | add 11-1 95 | ld l, a 96 | ld b, (hl) 97 | sub 11-1 98 | ld l, a 99 | 100 | ld a, b ; Oh, don't tocuh it? 101 | inc a 102 | jr z, .notone 103 | 104 | PollPCM 105 | 106 | db $FD,$7D ; ld a, iyl ; Get semitone shift 107 | and $F0 108 | rrca 109 | rrca 110 | rrca 111 | rrca 112 | ex de, hl 113 | ld h, PSGShiftTable>>8 114 | add PSGShiftTable&$FF 115 | ld l, a 116 | ld c, (hl) 117 | ex de, hl 118 | 119 | PollPCM 120 | 121 | ld a, b ; Compute resulting semitone 122 | add c 123 | 124 | ex de, hl ; Get corresponding frequency 125 | ld h, PSGFreqTable>>8 126 | ld l, a 127 | ld c, (hl) 128 | inc l 129 | ld b, (hl) 130 | 131 | PollPCM 132 | 133 | ld a, e ; Get PSG channel 134 | and $30 135 | add a 136 | or c 137 | 138 | ld hl, $7F11 ; Set new frequency 139 | ld (hl), a 140 | ld (hl), b 141 | ex de, hl 142 | 143 | PollPCM 144 | .notone: 145 | pop bc 146 | 147 | ld a, l ; Go for next channel 148 | sub 16+1 149 | ld l, a 150 | PollPCM 151 | dec b 152 | jp p, .loop 153 | 154 | jp DoTick_PSGSkip ; End of subroutine 155 | 156 | .envsetloop: 157 | PollPCM 158 | inc e ; Where we store the loop point 159 | 160 | ex de, hl ; Store loop point 161 | ld (hl), c 162 | inc l 163 | ld (hl), e 164 | inc l 165 | ld (hl), d 166 | ex de, hl 167 | 168 | dec e ; Back to where we were... 169 | dec e 170 | dec e 171 | jp .readenv ; Go for next byte 172 | 173 | .envloop: 174 | PollPCM 175 | inc e ; Where we store the loop point 176 | 177 | ex de, hl ; Retrieve loop point 178 | ld c, (hl) 179 | inc l 180 | ld e, (hl) 181 | inc l 182 | ld d, (hl) 183 | ex de, hl 184 | 185 | dec e ; Back to where we were... 186 | dec e 187 | dec e 188 | jp .readenv ; Go for next byte 189 | 190 | ;**************************************************************************** 191 | ; NoteOnPSG* 192 | ; Does a "note on" for a PSG channel 193 | ;**************************************************************************** 194 | 195 | NoteOnPSGSFX: 196 | call NoteOnPSG ; We're just a wrapper 197 | jp ProcessSFXRun ; End of subroutine 198 | 199 | NoteOnPSGBGM: 200 | ld b, a 201 | PollPCM 202 | ld a, b 203 | 204 | push hl 205 | and $03 ; Check if channel is free 206 | ld hl, RAM_Locked+8 207 | add l 208 | ld l, a 209 | ld a, (hl) 210 | pop hl 211 | or a 212 | jp nz, ProcessBGMSkip1 ; Don't play if locked 213 | 214 | ld a, b 215 | call NoteOnPSG ; We're just a wrapper 216 | jp ProcessBGMRun ; End of subroutine 217 | 218 | NoteOnPSG: 219 | and $03 220 | ld b, a 221 | ;push af 222 | ex af, af' 223 | PollPCM 224 | 225 | push hl ; Set channel volume 226 | ld h, RAM_PSGData>>8 227 | ld a, b 228 | rrca 229 | rrca 230 | rrca 231 | rrca 232 | ld l, a 233 | ld a, (hl) 234 | or $80 235 | ld (hl), a 236 | 237 | PollPCM 238 | push de 239 | 240 | inc l ; Now we'll reset the envelope address... 241 | inc l 242 | ld d, h 243 | ld a, l 244 | add 6 245 | ld e, a 246 | 247 | PollPCM 248 | 249 | ld a, (de) ; Reset envelope address 250 | ld (hl), a 251 | inc l 252 | inc e 253 | ld a, (de) 254 | ld (hl), a 255 | inc l 256 | inc e 257 | ld a, (de) 258 | ld (hl), a 259 | 260 | ld a, e 261 | pop de 262 | pop hl 263 | 264 | ex af, af' 265 | PollPCM 266 | call GetParam ; Get note 267 | PollPCM 268 | ex af, af' 269 | 270 | SetSemitonePSG: 271 | push hl ; Store new semitone 272 | inc a 273 | ld h, RAM_PSGData>>8 274 | ld l, a 275 | ld (hl), b 276 | pop hl 277 | 278 | ret ; End of subroutine 279 | 280 | ;**************************************************************************** 281 | ; NoteOnNoise* 282 | ; Does a "note on" for the noise PSG channel 283 | ;**************************************************************************** 284 | 285 | NoteOnNoiseSFX: 286 | call NoteOnNoise ; We're just a wrapper 287 | jp ProcessSFXRun ; End of subroutine 288 | 289 | NoteOnNoiseBGM: 290 | ld a, (RAM_Locked+11) ; Check if channel is free 291 | or a 292 | jp nz, ProcessBGMSkip1 ; Don't play if locked 293 | 294 | call NoteOnNoise ; We're just a wrapper 295 | jp ProcessBGMRun ; End of subroutine 296 | 297 | NoteOnNoise: 298 | PollPCM 299 | push hl 300 | 301 | ld hl, RAM_PSGData+48 ; Set channel volume 302 | ld a, (hl) 303 | or $80 304 | ld (hl), a 305 | 306 | PollPCM 307 | push de 308 | 309 | inc l ; Now we'll reset the envelope address... 310 | inc l 311 | ld d, h 312 | ld a, l 313 | add 6 314 | ld e, a 315 | 316 | PollPCM 317 | 318 | ld a, (de) ; Reset envelope address 319 | ld (hl), a 320 | inc l 321 | inc e 322 | ld a, (de) 323 | ld (hl), a 324 | inc l 325 | inc e 326 | ld a, (de) 327 | ld (hl), a 328 | 329 | pop de 330 | pop hl 331 | 332 | SetNoteNoise: 333 | PollPCM 334 | call GetParam ; Get noise type 335 | PollPCM 336 | 337 | ld a, $E0 ; Set new noise type 338 | or b 339 | ld ($7F11), a 340 | 341 | ret ; End of subroutine 342 | 343 | ;**************************************************************************** 344 | ; NoteOffPSG* 345 | ; Does a "note off" for a PSG channel 346 | ;**************************************************************************** 347 | 348 | NoteOffPSGSFX: 349 | call NoteOffPSG ; We're just a wrapper 350 | jp ProcessSFXRun ; End of subroutine 351 | 352 | NoteOffPSGBGM: 353 | ld b, a 354 | PollPCM 355 | ld a, b 356 | 357 | push hl 358 | and $03 ; Check if channel is free 359 | ld hl, RAM_Locked+8 360 | add l 361 | ld l, a 362 | ld a, (hl) 363 | pop hl 364 | or a 365 | jp nz, ProcessBGMRun ; Don't stop if locked 366 | 367 | ld a, b 368 | call NoteOffPSG ; We're just a wrapper 369 | jp ProcessBGMRun ; End of subroutine 370 | 371 | NoteOffPSG: 372 | and $03 373 | ld b, a 374 | PollPCM 375 | 376 | push hl ; Mark channel as not playing 377 | ld h, RAM_PSGData>>8 378 | ld a, b 379 | rrca 380 | rrca 381 | rrca 382 | rrca 383 | ld l, a 384 | ld a, (hl) 385 | and $7F 386 | ld (hl), a 387 | 388 | PollPCM 389 | 390 | ld a, l ; Don't waste time with semitone shifting 391 | add 11 392 | ld l, a 393 | ld a, $FF 394 | ld (hl), a 395 | pop hl 396 | 397 | ret ; End of subroutine 398 | 399 | ;**************************************************************************** 400 | ; SetPSGVol* 401 | ; Sets the volume of a PSG channel 402 | ;**************************************************************************** 403 | 404 | SetPSGVolSFX: 405 | and $03 ; Get channel ID 406 | 407 | ex af, af' 408 | PollPCM 409 | call GetParam ; Get volume 410 | PollPCM 411 | ex af, af' 412 | 413 | push hl 414 | ld h, RAM_PSGData>>8 ; Set new volume 415 | rrca 416 | rrca 417 | rrca 418 | rrca 419 | ld l, a 420 | ld a, (hl) 421 | and $80 422 | or b 423 | ld (hl), a 424 | pop hl 425 | 426 | jp ProcessSFXRun ; End of subroutine 427 | 428 | SetPSGVolBGM: 429 | and $03 ; Get channel ID 430 | 431 | ex af, af' 432 | PollPCM 433 | call GetParam ; Get volume 434 | PollPCM 435 | ex af, af' 436 | 437 | push de 438 | push hl 439 | 440 | push af 441 | ld de, RAM_Locked+8 ; Check if channel is locked 442 | add e ; Keep results for later 443 | ld e, a 444 | ld a, (de) 445 | ld e, a 446 | 447 | PollPCM 448 | pop af 449 | 450 | ld h, RAM_PSGData>>8 ; Store new volume 451 | rrca 452 | rrca 453 | rrca 454 | rrca 455 | add 15 456 | ld l, a 457 | ld (hl), b 458 | 459 | PollPCM 460 | 461 | ld a, e ; Is channel locked? 462 | or a 463 | jr nz, .nosetvol 464 | 465 | ld a, l ; Set new volume 466 | sub 15 467 | ld l, a 468 | ld a, (hl) 469 | and $80 470 | or b 471 | ld (hl), a 472 | 473 | PollPCM 474 | 475 | .nosetvol: 476 | pop hl 477 | pop de 478 | 479 | jp ProcessBGMRun ; End of subroutine 480 | 481 | ;**************************************************************************** 482 | ; LoadPSG* 483 | ; Loads a PSG instrument 484 | ;**************************************************************************** 485 | 486 | LoadPSGSFX: 487 | and $03 ; Get channel number 488 | 489 | ex af, af' 490 | PollPCM 491 | call GetParam ; Get instrument ID 492 | PollPCM 493 | ex af, af' 494 | 495 | push de 496 | push hl 497 | 498 | ld d, RAM_PointerList>>8 ; Get position in pointer list 499 | ld e, b 500 | 501 | ld h, RAM_PSGData>>8 ; Where to store address 502 | rrca 503 | rrca 504 | rrca 505 | rrca 506 | add 8+2+(RAM_PSGData&$FF) 507 | ld l, a 508 | 509 | PollPCM 510 | 511 | ld a, (de) ; Store PSG envelope start address 512 | ld (hl), a 513 | inc d 514 | dec l 515 | ld a, (de) 516 | ld (hl), a 517 | inc d 518 | dec l 519 | ld a, (de) 520 | ld (hl), a 521 | 522 | PollPCM 523 | 524 | ld a, l ; Reset volume 525 | sub 8 526 | ld l, a 527 | ld (hl), $00 528 | 529 | pop hl 530 | pop de 531 | jp ProcessSFXRun ; End of subroutine 532 | 533 | ;---------------------------------------------------------------------------- 534 | 535 | LoadPSGBGM: 536 | and $03 ; Get channel number 537 | 538 | ex af, af' 539 | PollPCM 540 | call GetParam ; Get instrument ID 541 | PollPCM 542 | ex af, af' 543 | 544 | push de 545 | push hl 546 | 547 | ld d, RAM_PointerList>>8 ; Get position in pointer list 548 | ld e, b 549 | 550 | ld hl, RAM_Locked+8 ; Get if channel is locked 551 | push af 552 | add l 553 | ld l, a 554 | pop af 555 | ld b, (hl) 556 | 557 | ld h, RAM_PSGData>>8 ; Where to store BGM instrument data 558 | rrca 559 | rrca 560 | rrca 561 | rrca 562 | add 15 563 | ld l, a 564 | 565 | PollPCM 566 | 567 | xor a 568 | ld (hl), a ; Reset volume for BGM 569 | dec l 570 | 571 | ld a, (de) ; Store PSG envelope address for BGM 572 | ld (hl), a 573 | inc d 574 | dec l 575 | ld a, (de) 576 | ld (hl), a 577 | inc d 578 | dec l 579 | ld a, (de) 580 | ld (hl), a 581 | 582 | PollPCM 583 | 584 | ld a, b ; Don't set PSG envelope if locked 585 | or a 586 | jp z, .noloadlocked 587 | pop hl 588 | pop de 589 | jp ProcessBGMRun 590 | .noloadlocked: 591 | PollPCM 592 | 593 | ld d, h ; Set PSG envelope 594 | ld a, l 595 | sub 12-8 596 | ld e, a 597 | 598 | ld a, (hl) 599 | ld (de), a 600 | inc l 601 | inc e 602 | ld a, (hl) 603 | ld (de), a 604 | inc l 605 | inc e 606 | ld a, (hl) 607 | ld (de), a 608 | 609 | PollPCM 610 | 611 | ld a, l ; Reset volume 612 | sub 8 613 | ld l, a 614 | ld (hl), $00 615 | 616 | pop hl 617 | pop de 618 | jp ProcessBGMRun ; End of subroutine 619 | 620 | ;**************************************************************************** 621 | ; SetNotePSG* 622 | ; Sets the note of a PSG channel without "note on" 623 | ;**************************************************************************** 624 | 625 | SetNotePSGSFX: 626 | call SetNotePSG ; We're just a wrapper 627 | jp ProcessSFXRun ; End of subroutine 628 | 629 | SetNotePSGBGM: 630 | ld b, a 631 | PollPCM 632 | ld a, b 633 | 634 | push hl 635 | and $0F ; Check if channel is free 636 | ld h, RAM_Locked>>8 637 | add RAM_Locked&$FF 638 | ld l, a 639 | ld a, (hl) 640 | pop hl 641 | or a 642 | jp nz, ProcessBGMSkip2 ; Don't play if locked 643 | 644 | ld a, b 645 | call SetNotePSG ; We're just a wrapper 646 | jp ProcessBGMRun ; End of subroutine 647 | 648 | SetNotePSG: 649 | and $03 ; Get channel number 650 | 651 | ex af, af' 652 | PollPCM 653 | call GetParam ; Get first byte 654 | PollPCM 655 | 656 | ld a, b ; Select by semitone? 657 | add a, a 658 | jp c, .freqtone 659 | ex af, af' 660 | 661 | push hl ; Mark semitone as not valid 662 | ld l, a 663 | ex af, af' 664 | PollPCM 665 | ld a, l 666 | rrca 667 | rrca 668 | rrca 669 | rrca 670 | add a, 11 671 | ld l, a 672 | ld h, RAM_PSGData>>8 673 | ld (hl), $FF 674 | pop hl 675 | PollPCM 676 | ex af, af' 677 | 678 | push de ; PSG port address 679 | ld de, $7F11 680 | 681 | rrca ; Set first frequency byte 682 | rrca 683 | rrca 684 | or b 685 | or $80 686 | ld (de), a 687 | 688 | PollPCM 689 | call GetParam ; Get second byte 690 | PollPCM 691 | 692 | ex de, hl 693 | ld (hl), b ; Set second frequency byte 694 | ex de, hl 695 | pop de 696 | 697 | ret ; End of subroutine 698 | 699 | ;---------------------------------------------------------------------------- 700 | 701 | .freqtone: 702 | ld b, a 703 | PollPCM 704 | ex af, af' 705 | rrca 706 | rrca 707 | rrca 708 | rrca 709 | add 10 710 | jp SetSemitonePSG 711 | 712 | ;**************************************************************************** 713 | ; SetNoteNoise* 714 | ; Sets the note of the noise PSG channel without "note on" 715 | ;**************************************************************************** 716 | 717 | SetNoteNoiseSFX: 718 | call SetNoteNoise ; We're just a wrapper 719 | jp ProcessSFXRun ; End of subroutine 720 | 721 | SetNoteNoiseBGM: 722 | ld a, (RAM_Locked+11) ; Check if channel is free 723 | or a 724 | jp nz, ProcessBGMSkip1 ; Don't play if locked 725 | 726 | call SetNoteNoise ; We're just a wrapper 727 | jp ProcessBGMRun ; End of subroutine 728 | 729 | ;**************************************************************************** 730 | ; LockChannelPSG [events $E8-$EB] 731 | ; Locks a PSG channel 732 | ;**************************************************************************** 733 | 734 | LockChannelPSG: 735 | and $03 736 | ld b, a 737 | PollPCM 738 | push hl 739 | 740 | ld h, RAM_Locked>>8 ; Get address of channel to lock 741 | ld a, b 742 | add (RAM_Locked&$FF)+8 743 | ld l, a 744 | 745 | ld (hl), $01 ; Lock channel 746 | 747 | PollPCM 748 | 749 | ld a, b ; Stop channel 750 | rrca 751 | rrca 752 | rrca 753 | rrca 754 | ld l, a 755 | ld h, RAM_PSGData>>8 756 | ld (hl), $00 757 | 758 | pop hl 759 | jp ProcessSFXRun ; End of subroutine 760 | -------------------------------------------------------------------------------- /c/echo.c: -------------------------------------------------------------------------------- 1 | // Required headers 2 | #include 3 | #include "echoblob.h" 4 | #include "echo.h" 5 | 6 | // Z80 addresses 7 | static volatile uint8_t* const z80_ram = (uint8_t *) 0xA00000; 8 | static volatile uint16_t* const z80_busreq = (uint16_t *) 0xA11100; 9 | static volatile uint16_t* const z80_reset = (uint16_t *) 0xA11200; 10 | 11 | // Macros to control the Z80 12 | #define Z80_REQUEST() \ 13 | { *z80_busreq = 0x100; while (*z80_busreq & 0x100); } 14 | #define Z80_RELEASE() \ 15 | { *z80_busreq = 0; } 16 | #define Z80_RESET() \ 17 | { *z80_reset = 0; \ 18 | volatile int16_t i; for (i = 30; i >= 0; i--); \ 19 | *z80_reset = 0x100; } 20 | 21 | // Macro to add delays 22 | // Using volatile is needlessly ugly but at least portable 23 | // GCC is already awful at optimizing, so this isn't that bad... 24 | #define DELAY() \ 25 | { volatile int16_t i; for (i = 0x1FF; i >= 0; i--); } 26 | 27 | // Look-up tables for echo_set_volume 28 | const uint8_t echo_fm_vol_table[0x40] = { 29 | 0x7F,0x7B,0x77,0x73,0x70,0x6C,0x68,0x65, 30 | 0x61,0x5E,0x5A,0x57,0x54,0x50,0x4D,0x4A, 31 | 0x47,0x44,0x41,0x3F,0x3C,0x39,0x36,0x34, 32 | 0x31,0x2F,0x2D,0x2A,0x28,0x26,0x24,0x22, 33 | 0x20,0x1E,0x1C,0x1A,0x18,0x16,0x15,0x13, 34 | 0x12,0x10,0x0F,0x0D,0x0C,0x0B,0x0A,0x09, 35 | 0x08,0x07,0x06,0x05,0x04,0x04,0x03,0x02, 36 | 0x02,0x01,0x01,0x01,0x00,0x00,0x00,0x00 37 | }; 38 | const uint8_t echo_psg_vol_table[0x40] = { 39 | 0x0F,0x0F,0x0E,0x0E,0x0D,0x0D,0x0C,0x0C, 40 | 0x0B,0x0B,0x0B,0x0A,0x0A,0x0A,0x09,0x09, 41 | 0x08,0x08,0x08,0x07,0x07,0x07,0x06,0x06, 42 | 0x06,0x06,0x05,0x05,0x05,0x04,0x04,0x04, 43 | 0x04,0x03,0x03,0x03,0x03,0x03,0x02,0x02, 44 | 0x02,0x02,0x02,0x02,0x01,0x01,0x01,0x01, 45 | 0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00, 46 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 47 | }; 48 | 49 | // Look-up table used by echo_play_direct to know how many argument bytes 50 | // each event has so it knows where the source stream actually ends 51 | static const uint8_t arg_table[] = { 52 | 1,1,1,0, 1,1,1,0, 1,1,1,1, 1,0,0,0, // 00-0F (key on) 53 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 10-1F (key off) 54 | 1,1,1,0, 1,1,1,0, 1,1,1,1, 0,0,0,0, // 20-2F (set volume) 55 | 2,2,2,0, 2,2,2,0, 2,2,2,1, 0,0,0,0, // 30-3F (set frequency) 56 | 1,1,1,0, 1,1,1,0, 1,1,1,1, 0,0,0,0, // 40-4F (set instrument) 57 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 50-5F 58 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 60-6F 59 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 70-7F 60 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 80-8F 61 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 90-9F 62 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // A0-AF 63 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // B0-BF 64 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // C0-CF 65 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // D0-DF (quick delay) 66 | 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // E0-EF (lock channel) 67 | 1,1,1,0, 1,1,1,0, 2,2,1,1, 0,0,1,0, // F0-FF (miscellaneous) 68 | }; 69 | 70 | //*************************************************************************** 71 | // echo_init 72 | // Initializes Echo and gets it running. 73 | //--------------------------------------------------------------------------- 74 | // param list: pointer to instrument list 75 | //*************************************************************************** 76 | 77 | void echo_init(const void* const* list) { 78 | // Take over the Z80 79 | Z80_RESET(); 80 | Z80_REQUEST(); 81 | 82 | // Tell Echo to not run any commands by default (the assembly counterpart 83 | // would tell it to load the instrument list, but we can't do that here 84 | // due to linker shenanigans) 85 | z80_ram[0x1FFF] = 0x00; 86 | z80_ram[0x1FFB] = 0x00; 87 | 88 | // Direct stream is empty yet 89 | z80_ram[0x1F00] = 0xFF; 90 | z80_ram[0x1F80] = 0x00; 91 | 92 | // Load the instrument list manually, since thanks to linker shenanigans 93 | // we can't implement the list properly in ROM :/ 94 | volatile uint8_t *dest = &z80_ram[0x1C00]; 95 | while (*list) { 96 | // Retrieve pointer to next instrument 97 | // Cast it to an integer since we need to treat it as such 98 | // This should be considered bad C, but since this is hardware-specific 99 | // code this should be fine to do (portability is not expected) 100 | uint32_t ptr = (uint32_t) *list; 101 | 102 | // Turn the pointer into the base+address notation Echo wants and store 103 | // it in Z80 RAM directly (where the list would go) 104 | dest[0x000] = (ptr >> 8 & 0x7F) | 0x80; 105 | dest[0x100] = (ptr & 0xFF); 106 | dest[0x200] = (ptr >> 15 & 0x7F) | (ptr >> 16 & 0x80); 107 | 108 | // Go for next pointer 109 | list++; 110 | dest++; 111 | } 112 | 113 | // Copy the Echo blob into Z80 RAM 114 | // No, memcpy() won't do here since we must ensure accesses are byte-sized 115 | // (memcpy() may not know this and try word or long accesses) 116 | const uint8_t *src = echo_blob; 117 | dest = z80_ram; 118 | int16_t count = sizeof(echo_blob)-1; 119 | while (count-- >= 0) 120 | *dest++ = *src++; 121 | 122 | // Set up global volume 123 | int i; 124 | for (i = 0; i < 16; i++) 125 | z80_ram[0x1FE0+i] = 0; 126 | z80_ram[0x1FEC] = 1; 127 | z80_ram[0x1FF1] = 1; 128 | 129 | // Let Echo start running! 130 | Z80_RESET(); 131 | Z80_RELEASE(); 132 | } 133 | 134 | //*************************************************************************** 135 | // echo_send_command 136 | // Sends a raw command to Echo. No parameters are taken. 137 | //--------------------------------------------------------------------------- 138 | // param cmd: command to send 139 | //*************************************************************************** 140 | 141 | void echo_send_command(uint8_t cmd) { 142 | // We need access to Z80 bus 143 | Z80_REQUEST(); 144 | 145 | // Is Echo busy yet? 146 | volatile uint8_t *ptr = &z80_ram[0x1FFC]; 147 | if (ptr[3] != 0x00) { 148 | ptr -= 4; 149 | while (ptr[3] != 0x00) { 150 | Z80_RELEASE(); 151 | DELAY(); 152 | Z80_REQUEST(); 153 | } 154 | } 155 | 156 | // Write the command 157 | ptr[3] = cmd; 158 | 159 | // Done with the Z80 160 | Z80_RELEASE(); 161 | } 162 | 163 | //*************************************************************************** 164 | // echo_send_command_addr 165 | // Sends a raw command to Echo. An address parameter is taken. 166 | //--------------------------------------------------------------------------- 167 | // param cmd: command to send 168 | // param addr: address parameter 169 | //*************************************************************************** 170 | 171 | void echo_send_command_addr(uint8_t cmd, const void *addr) { 172 | // Since we need to split the address into multiple bytes we put it in an 173 | // integer. This is a bad practice in general, period, but since we don't 174 | // care about portability here we can afford to do it this time. 175 | uint32_t param = (uint32_t) addr; 176 | 177 | // We need access to Z80 bus 178 | Z80_REQUEST(); 179 | 180 | // Is Echo busy yet? 181 | volatile uint8_t *ptr = &z80_ram[0x1FFC]; 182 | if (ptr[3] != 0x00) { 183 | ptr -= 4; 184 | while (ptr[3] != 0x00) { 185 | Z80_RELEASE(); 186 | DELAY(); 187 | Z80_REQUEST(); 188 | } 189 | } 190 | 191 | // Write the command 192 | ptr[3] = cmd; 193 | ptr[1] = param; 194 | param >>= 8; 195 | ptr[2] = param | 0x80; 196 | param >>= 7; 197 | param = (param & 0x7F) | (param >> 1 & 0x80); 198 | ptr[0] = param; 199 | 200 | // Done with the Z80 201 | Z80_RELEASE(); 202 | } 203 | 204 | //*************************************************************************** 205 | // echo_send_command_byte 206 | // Sends a raw command to Echo. A byte parameter is taken. 207 | //--------------------------------------------------------------------------- 208 | // param cmd: command to send 209 | // param byte: parameter 210 | //*************************************************************************** 211 | 212 | void echo_send_command_byte(uint8_t cmd, uint8_t byte) { 213 | // We need access to Z80 bus 214 | Z80_REQUEST(); 215 | 216 | // Is Echo busy yet? 217 | volatile uint8_t *ptr = &z80_ram[0x1FFC]; 218 | if (ptr[3] != 0x00) { 219 | ptr -= 4; 220 | while (ptr[3] != 0x00) { 221 | Z80_RELEASE(); 222 | DELAY(); 223 | Z80_REQUEST(); 224 | } 225 | } 226 | 227 | // Write the command 228 | ptr[3] = cmd; 229 | ptr[0] = byte; 230 | 231 | // Done with the Z80 232 | Z80_RELEASE(); 233 | } 234 | 235 | //*************************************************************************** 236 | // echo_play_bgm 237 | // Starts playing background music. 238 | //--------------------------------------------------------------------------- 239 | // param ptr: pointer to BGM stream 240 | //*************************************************************************** 241 | 242 | void echo_play_bgm(const void *ptr) { 243 | echo_send_command_addr(ECHO_CMD_PLAYBGM, ptr); 244 | } 245 | 246 | //*************************************************************************** 247 | // echo_stop_bgm 248 | // Stops background music playback. 249 | //*************************************************************************** 250 | 251 | void echo_stop_bgm(void) { 252 | echo_send_command(ECHO_CMD_STOPBGM); 253 | } 254 | 255 | //*************************************************************************** 256 | // echo_pause_bgm 257 | // Pauses background music playback. 258 | //*************************************************************************** 259 | 260 | void echo_pause_bgm(void) { 261 | echo_send_command(ECHO_CMD_PAUSEBGM); 262 | } 263 | 264 | //*************************************************************************** 265 | // echo_resume_bgm 266 | // Resumes background music playback. 267 | //*************************************************************************** 268 | 269 | void echo_resume_bgm(void) { 270 | echo_send_command(ECHO_CMD_RESUMEBGM); 271 | } 272 | 273 | //*************************************************************************** 274 | // echo_play_sfx 275 | // Starts playing a sound effect. 276 | //--------------------------------------------------------------------------- 277 | // param ptr: pointer to SFX stream 278 | //*************************************************************************** 279 | 280 | void echo_play_sfx(const void *ptr) { 281 | echo_send_command_addr(ECHO_CMD_PLAYSFX, ptr); 282 | } 283 | 284 | //*************************************************************************** 285 | // echo_stop_sfx 286 | // Stops sound effect playback. 287 | //*************************************************************************** 288 | 289 | void echo_stop_sfx(void) { 290 | echo_send_command(ECHO_CMD_STOPSFX); 291 | } 292 | 293 | //*************************************************************************** 294 | // echo_play_direct 295 | // Injects events into the BGM stream for the next tick. 296 | //--------------------------------------------------------------------------- 297 | // param ptr: pointer to BGM stream 298 | //*************************************************************************** 299 | 300 | void echo_play_direct(const void *ptr) { 301 | // We need access to Z80 bus 302 | Z80_REQUEST(); 303 | 304 | // Check where we can start writing events 305 | // If it's a bogus value we need to wait 306 | uint8_t len = z80_ram[0x1F80]; 307 | while (len >= 0x80) { 308 | Z80_RELEASE(); 309 | DELAY(); 310 | Z80_REQUEST(); 311 | len = z80_ram[0x1F80]; 312 | } 313 | 314 | // Write the events 315 | const uint8_t *src = (uint8_t*)(ptr); 316 | volatile uint8_t *dest = &z80_ram[0x1F00 + len]; 317 | for (;;) { 318 | uint8_t byte = *src++; 319 | *dest++ = byte; len++; 320 | if (byte == 0xFF) break; 321 | for (unsigned i = arg_table[byte]; i > 0; i--) { 322 | *dest++ = *src++; 323 | len++; 324 | } 325 | } 326 | 327 | // Store new length 328 | z80_ram[0x1F80] = len; 329 | 330 | // Done with the Z80 331 | Z80_RELEASE(); 332 | } 333 | 334 | //*************************************************************************** 335 | // echo_set_volume 336 | // Changes the global volume for every channel. 337 | //--------------------------------------------------------------------------- 338 | // param vol: new volume (0 = quietest, 255 = loudest) 339 | //*************************************************************************** 340 | 341 | void echo_set_volume(uint8_t vol) { 342 | // We need access to Z80 bus 343 | Z80_REQUEST(); 344 | 345 | // Set FM volume values 346 | uint8_t fm_vol = echo_fm_vol_table[vol >> 2]; 347 | z80_ram[0x1FE0] = fm_vol; 348 | z80_ram[0x1FE1] = fm_vol; 349 | z80_ram[0x1FE2] = fm_vol; 350 | z80_ram[0x1FE3] = fm_vol; 351 | z80_ram[0x1FE4] = fm_vol; 352 | z80_ram[0x1FE5] = fm_vol; 353 | z80_ram[0x1FE6] = fm_vol; 354 | z80_ram[0x1FE7] = fm_vol; 355 | 356 | // Set PSG volume values 357 | uint8_t psg_vol = echo_psg_vol_table[vol >> 2]; 358 | z80_ram[0x1FE8] = psg_vol; 359 | z80_ram[0x1FE9] = psg_vol; 360 | z80_ram[0x1FEA] = psg_vol; 361 | z80_ram[0x1FEB] = psg_vol; 362 | 363 | // Determine whether to enable PCM 364 | z80_ram[0x1FEC] = (vol >= 0x40) ? 1 : 0; 365 | 366 | // Tell Echo to update all the volumes 367 | z80_ram[0x1FF1] = 1; 368 | 369 | // Done with the Z80 370 | Z80_RELEASE(); 371 | } 372 | 373 | //*************************************************************************** 374 | // echo_set_volume_ex 375 | // Changes the global volume for each individual channel. 376 | //--------------------------------------------------------------------------- 377 | // param ptr: pointer to array with volume values 378 | //*************************************************************************** 379 | 380 | void echo_set_volume_ex(const uint8_t *ptr) { 381 | // We need access to Z80 bus 382 | Z80_REQUEST(); 383 | 384 | // Store the new volume values 385 | int i; 386 | for (i = 0; i < 16; i++) 387 | z80_ram[0x1FE0+i] = ptr[i]; 388 | 389 | // Tell Echo to update all the volumes 390 | z80_ram[0x1FF1] = 1; 391 | 392 | // Done with the Z80 393 | Z80_RELEASE(); 394 | } 395 | 396 | //*************************************************************************** 397 | // echo_set_pcm_rate 398 | // Changes the playback rate of PCM. 399 | //--------------------------------------------------------------------------- 400 | // param rate: new rate (timer A value) 401 | //*************************************************************************** 402 | 403 | void echo_set_pcm_rate(uint8_t rate) { 404 | echo_send_command_byte(ECHO_CMD_SETPCMRATE, rate); 405 | } 406 | 407 | //*************************************************************************** 408 | // echo_set_stereo 409 | // Toggles stereo or mono. 410 | //--------------------------------------------------------------------------- 411 | // param enable: non-zero for stereo, zero for mono 412 | //*************************************************************************** 413 | 414 | void echo_set_stereo(int enable) { 415 | echo_send_command_byte(ECHO_CMD_SETSTEREO, !!enable); 416 | } 417 | 418 | //*************************************************************************** 419 | // echo_get_status 420 | // Retrieves Echo's current status. 421 | //--------------------------------------------------------------------------- 422 | // return: status flags (see ECHO_STAT_*) 423 | //*************************************************************************** 424 | 425 | uint16_t echo_get_status(void) { 426 | // Look-up tables used to work around the fact that playing/stopping 427 | // won't set the status immediately (only when Echo processes the command) 428 | // and this can catch programmers off guard 429 | static const uint16_t and_flags[] = { 430 | 0xFFFF,0xFFFF, 0xFFFF,0xFFFE,0xFFFF,0xFFFD, 0xFFFF,0xFFFF,0xFFFF 431 | }; 432 | static const uint16_t or_flags[] = { 433 | 0x0000,0x0000, 0x0001,0x0000,0x0002,0x0000, 0x0000,0x0000,0x0000 434 | }; 435 | 436 | // We need access to the Z80 437 | Z80_REQUEST(); 438 | 439 | // Retrieve status from Z80 RAM 440 | uint16_t status = 0; 441 | status = z80_ram[0x1FF0]; 442 | if (z80_ram[0x1FFB] != 0) 443 | status |= ECHO_STAT_BUSY; 444 | if (z80_ram[0x1F00] != 0xFF) 445 | status |= ECHO_STAT_DIRBUSY; 446 | if (z80_ram[0x0008] != 0xC9) 447 | status |= ECHO_STAT_PCM; 448 | 449 | // Look ahead in the queue for any pending commands 450 | // Adjust the flags accordingly if needed 451 | uint8_t command = z80_ram[0x1FFF]; 452 | status &= and_flags[command]; 453 | status |= or_flags[command]; 454 | command = z80_ram[0x1FFB]; 455 | status &= and_flags[command]; 456 | status |= or_flags[command]; 457 | 458 | // Done with the Z80 459 | Z80_RELEASE(); 460 | 461 | // Return status 462 | return status; 463 | } 464 | 465 | //*************************************************************************** 466 | // echo_get_flags 467 | // Gets the current values of the flags. 468 | //--------------------------------------------------------------------------- 469 | // return: bitmask with flags 470 | //*************************************************************************** 471 | 472 | uint8_t echo_get_flags(void) 473 | { 474 | Z80_REQUEST(); 475 | uint8_t flags = z80_ram[0x1FF2]; 476 | Z80_RELEASE(); 477 | return flags; 478 | } 479 | 480 | //*************************************************************************** 481 | // echo_set_flags 482 | // Sets flags from the 68000. 483 | //--------------------------------------------------------------------------- 484 | // param flags: bitmask of flags to be set (1 = set, 0 = intact) 485 | //*************************************************************************** 486 | 487 | void echo_set_flags(uint8_t flags) 488 | { 489 | Z80_REQUEST(); 490 | z80_ram[0x1FF2] |= flags; 491 | Z80_RELEASE(); 492 | } 493 | 494 | //*************************************************************************** 495 | // echo_clear_flags 496 | // Clears flags from the 68000. 497 | //--------------------------------------------------------------------------- 498 | // param flags: bitmask of flags to be cleared (1 = clear, 0 = intact) 499 | //*************************************************************************** 500 | 501 | void echo_clear_flags(uint8_t flags) 502 | { 503 | Z80_REQUEST(); 504 | z80_ram[0x1FF2] &= ~flags; 505 | Z80_RELEASE(); 506 | } 507 | -------------------------------------------------------------------------------- /doc/esf.txt: -------------------------------------------------------------------------------- 1 | ============================================================================= 2 | 3 | OVERVIEW 4 | 5 | ESF stands for "Echo Stream Format". This is the format in which BGM and 6 | SFX sequences are stored. Essentially this is the music arrangement data. 7 | The format is mostly the same for both BGM and SFX, with just a few 8 | exceptions. 9 | 10 | FORMAT 11 | 12 | ESF is a streamed format (think MIDI without the useless complexity). 13 | It's stored as a sequence of events, where each event takes up between 14 | one and three bytes. An event could be something like "note on", "set 15 | volume", "stop", etc. 16 | 17 | CHANNEL LOCKING 18 | 19 | When both BGM and SFX are being played, it's likely that some channels 20 | may get used by both. In this case, Echo will give the SFX privilege over 21 | those channels. 22 | 23 | This is achieved by channel "locking". When the SFX starts, it first 24 | locks the channels it needs to use. When the SFX ends, those channels get 25 | unlocked. BGM won't play on the locked channels while the SFX is playing. 26 | 27 | ============================================================================= 28 | 29 | EVENT LIST 30 | 31 | $00nn ..... Note on FM channel #1 32 | $01nn ..... Note on FM channel #2 33 | $02nn ..... Note on FM channel #3 34 | $04nn ..... Note on FM channel #4 35 | $05nn ..... Note on FM channel #5 36 | $06nn ..... Note on FM channel #6 37 | $08nn ..... Note on PSG channel #1 38 | $09nn ..... Note on PSG channel #2 39 | $0Ann ..... Note on PSG channel #3 40 | $0Bnn ..... Note on PSG channel #4 41 | $0Cnn ..... Note on PCM channel 42 | 43 | $10 ....... Note off FM channel #1 44 | $11 ....... Note off FM channel #2 45 | $12 ....... Note off FM channel #3 46 | $14 ....... Note off FM channel #4 47 | $15 ....... Note off FM channel #5 48 | $16 ....... Note off FM channel #6 49 | $18 ....... Note off PSG channel #1 50 | $19 ....... Note off PSG channel #2 51 | $1A ....... Note off PSG channel #3 52 | $1B ....... Note off PSG channel #4 53 | $1C ....... Note off PCM channel 54 | 55 | $20nn ..... Set volume FM channel #1 56 | $21nn ..... Set volume FM channel #2 57 | $22nn ..... Set volume FM channel #3 58 | $24nn ..... Set volume FM channel #4 59 | $25nn ..... Set volume FM channel #5 60 | $26nn ..... Set volume FM channel #6 61 | $28nn ..... Set volume PSG channel #1 62 | $29nn ..... Set volume PSG channel #2 63 | $2Ann ..... Set volume PSG channel #3 64 | $2Bnn ..... Set volume PSG channel #4 65 | 66 | $30nnnn ... Set frequency FM channel #1 67 | $31nnnn ... Set frequency FM channel #2 68 | $32nnnn ... Set frequency FM channel #3 69 | $34nnnn ... Set frequency FM channel #4 70 | $35nnnn ... Set frequency FM channel #5 71 | $36nnnn ... Set frequency FM channel #6 72 | $38nnnn ... Set frequency PSG channel #1 73 | $39nnnn ... Set frequency PSG channel #2 74 | $3Annnn ... Set frequency PSG channel #3 75 | $3Bnn ..... Set noise type PSG channel #4 76 | 77 | $40nn ..... Set instrument FM channel #1 78 | $41nn ..... Set instrument FM channel #2 79 | $42nn ..... Set instrument FM channel #3 80 | $44nn ..... Set instrument FM channel #4 81 | $45nn ..... Set instrument FM channel #5 82 | $46nn ..... Set instrument FM channel #6 83 | $48nn ..... Set instrument PSG channel #1 84 | $49nn ..... Set instrument PSG channel #2 85 | $4Ann ..... Set instrument PSG channel #3 86 | $4Bnn ..... Set instrument PSG channel #4 87 | 88 | $Dn ....... Delay ticks (short) 89 | 90 | $E0 ....... [SFX] Lock FM channel #1 91 | $E1 ....... [SFX] Lock FM channel #2 92 | $E2 ....... [SFX] Lock FM channel #3 93 | $E4 ....... [SFX] Lock FM channel #4 94 | $E5 ....... [SFX] Lock FM channel #5 95 | $E6 ....... [SFX] Lock FM channel #6 96 | $E8 ....... [SFX] Lock PSG channel #1 97 | $E9 ....... [SFX] Lock PSG channel #2 98 | $EA ....... [SFX] Lock PSG channel #3 99 | $EB ....... [SFX] Lock PSG channel #4 100 | 101 | $F0nn ..... Set parameters FM channel #1 102 | $F1nn ..... Set parameters FM channel #2 103 | $F2nn ..... Set parameters FM channel #3 104 | $F4nn ..... Set parameters FM channel #4 105 | $F5nn ..... Set parameters FM channel #5 106 | $F6nn ..... Set parameters FM channel #6 107 | 108 | $F8rrnn ... Set FM register in bank 0 109 | $F9rrnn ... Set FM register in bank 1 110 | $FAnn ..... Set flags 111 | $FBnn ..... Clear flags 112 | 113 | $FC ....... [BGM] Go to loop 114 | $FD ....... [BGM] Set loop point 115 | $FEnn ..... Delay ticks (long) 116 | $FF ....... Stop playback 117 | 118 | ============================================================================= 119 | 120 | $00nn: Note on FM channel #1 121 | $01nn: Note on FM channel #2 122 | $02nn: Note on FM channel #3 123 | $04nn: Note on FM channel #4 124 | $05nn: Note on FM channel #5 125 | $06nn: Note on FM channel #6 126 | 127 | These events do a "note on" at the specified FM channel. The event is 128 | followed by a byte, which indicates which note to play. The value is as 129 | follows, where "octave" ranges from 0 to 7 and "semitone" ranges from 0 130 | to 11: 131 | 132 | 32 * octave + 2 * semitone + 1 133 | 134 | $08nn: Note on PSG channel #1 135 | $09nn: Note on PSG channel #2 136 | $0Ann: Note on PSG channel #3 137 | 138 | These events do a "note on" at the specified square wave PSG channel. 139 | The event is followed by a byte, which indicates which note to play. The 140 | value is as follows, where "octave" ranges from 0 to 5 and "semitone" 141 | ranges from 0 to 11: 142 | 143 | 24 * octave + 2 * semitone 144 | 145 | $0Bnn: Note on PSG channel #4 146 | 147 | This event does a "note on" at the noise PSG channel. The event is 148 | followed by a byte, which indicates what kind of noise to play. The 149 | following values are valid: 150 | 151 | $00 ... Periodic noise, high pitch 152 | $01 ... Periodic noise, medium pitch 153 | $02 ... Periodic noise, low pitch 154 | $03 ... Periodic noise, PSG3 frequency 155 | $04 ... White noise, high pitch 156 | $05 ... White noise, medium pitch 157 | $06 ... White noise, low pitch 158 | $07 ... White noise, PSG3 frequency 159 | 160 | When using values $03 and $07, the third square wave PSG channel controls 161 | the noise frequency. You can change this frequency using the events to 162 | change the frequency of that channel (usually you'd use event type $3A). 163 | 164 | $0Cnn: Note on PCM channel 165 | 166 | This event does a "note on" at the PCM channel. More specifically, it 167 | starts playback of a PCM sample. This event is followed by a byte, that 168 | specifies an index in the pointer list indicating where's the sample 169 | data. Samples are stored as EWF (Echo Waveform Format). 170 | 171 | NOTE: FM channel #6 will be disabled. That channel will be re-enabled 172 | when PCM playback is over (either because the waveform is over or because 173 | the channel is stopped explicitly). 174 | 175 | ============================================================================= 176 | 177 | $10: Note off FM channel #1 178 | $11: Note off FM channel #2 179 | $12: Note off FM channel #3 180 | $14: Note off FM channel #4 181 | $15: Note off FM channel #5 182 | $16: Note off FM channel #6 183 | $18: Note off PSG channel #1 184 | $19: Note off PSG channel #2 185 | $1A: Note off PSG channel #3 186 | $1B: Note off PSG channel #4 187 | 188 | These events do a "note off" at the specified channel. 189 | 190 | $1C: Note off PCM channel 191 | 192 | This event does a "note off" at the PCM channel. This means that any PCM 193 | playback is immediately stopped. FM channel #6 is immediately enabled as 194 | well. 195 | 196 | ============================================================================= 197 | 198 | $20nn: Set volume FM channel #1 199 | $21nn: Set volume FM channel #2 200 | $22nn: Set volume FM channel #3 201 | $24nn: Set volume FM channel #4 202 | $25nn: Set volume FM channel #5 203 | $26nn: Set volume FM channel #6 204 | 205 | These events set the volume of a specific FM channel. The event is 206 | followed by a byte, which indicates the new volume. A value of $00 is the 207 | loudest, a value of $7F is the quietest. 208 | 209 | $28nn: Set volume PSG channel #1 210 | $29nn: Set volume PSG channel #2 211 | $2Ann: Set volume PSG channel #3 212 | $2Bnn: Set volume PSG channel #4 213 | 214 | These events set the volume of a specific PSG channel. The event is 215 | followed by a byte, which indicates the new volume. A value of $00 is the 216 | loudest, a value of $0F is the quietest. 217 | 218 | ============================================================================= 219 | 220 | $30nn/$30nnnn: Set frequency FM channel #1 221 | $31nn/$31nnnn: Set frequency FM channel #2 222 | $32nn/$32nnnn: Set frequency FM channel #3 223 | $34nn/$34nnnn: Set frequency FM channel #4 224 | $35nn/$35nnnn: Set frequency FM channel #5 225 | $36nn/$36nnnn: Set frequency FM channel #6 226 | 227 | These events set the raw frequency of a specific FM channel, without 228 | triggering a new note. Meant for note slides. The format of this event 229 | depends on whether the second byte has its MSB set or not. 230 | 231 | If the 2nd byte's MSB is set, the event is two bytes long. The 2nd byte 232 | is the semitone in this format (where "octave" is from 0 to 7 and 233 | "semitone" is from 0 to 11): 234 | 235 | octave * 16 + semitone + 128 236 | 237 | If the 2nd byte's MSB is clear, the event is three bytes long. The 2nd 238 | and 3rd bytes specify the new frequency in the same format as the YM2612 239 | expects. The 2nd byte is register $A4+, the 3rd byte is register $A0+. 240 | 241 | Echo uses the following frequency values for each semitone: 242 | 243 | C - 644 | E - 810 | G# - 1021 244 | C# - 681 | F - 858 | A - 1081 245 | D - 722 | F# - 910 | A# - 1146 246 | D# - 765 | G - 964 | B - 1214 247 | 248 | $38nn/$38nnnn: Set frequency PSG channel #1 249 | $39nn/$39nnnn: Set frequency PSG channel #2 250 | $3Ann/$3Annnn: Set frequency PSG channel #3 251 | 252 | These events set the raw frequency of a specific square wave PSG channel, 253 | without triggering a new note. Meant for note slides. The format of this 254 | event depends on whether the second byte has its MSB set or not. 255 | 256 | If the 2nd byte's MSB is set, the event is two bytes long. The 2nd byte 257 | is the semitone in this format (where "octave" is from 0 to 5 and 258 | "semitone" is from 0 to 11): 259 | 260 | octave * 12 + semitone + 128 261 | 262 | If the 2nd byte's MSB is clear, the event is three bytes long. The 2nd 263 | and 3rd bytes specify the new frequency, the 2nd byte containing the 264 | four least significant bits (LSB aligned), and the 3rd byte containing 265 | the six most significant bits (LSB aligned too). 266 | 267 | IMPORTANT: using the 3-byte event prevents semitone shifting in PSG 268 | instruments from working. It will start working again whenever a new note 269 | or the 2-byte version of this event is used. 270 | 271 | Echo uses the following frequency values for each semitone: 272 | 273 | |Oct.0|Oct.1|Oct.2|Oct.3|Oct.4|Oct.5 274 | ---|-----|-----|-----|-----|-----|----- 275 | C | 851 | 425 | 212 | 106 | 53 | 26 276 | C# | 803 | 401 | 200 | 100 | 50 | 25 277 | D | 758 | 379 | 189 | 94 | 47 | 23 278 | D# | 715 | 357 | 178 | 89 | 44 | 22 279 | E | 675 | 337 | 168 | 84 | 42 | 21 280 | F | 637 | 318 | 159 | 79 | 39 | 19 281 | F# | 601 | 300 | 150 | 75 | 37 | 18 282 | G | 568 | 284 | 142 | 71 | 35 | 17 283 | G# | 536 | 268 | 134 | 67 | 33 | 16 284 | A | 506 | 253 | 126 | 63 | 31 | 15 285 | A# | 477 | 238 | 119 | 59 | 29 | 14 286 | B | 450 | 225 | 112 | 56 | 28 | 14 287 | 288 | $3Bnn: Set noise type PSG channel #4 289 | 290 | This event works like event $0Bnn, but it doesn't trigger a note attack 291 | (i.e. instrument envelope won't be reset, note won't start playing if it 292 | wasn't already). 293 | 294 | ============================================================================= 295 | 296 | $40nn: Set instrument FM channel #1 297 | $41nn: Set instrument FM channel #2 298 | $42nn: Set instrument FM channel #3 299 | $44nn: Set instrument FM channel #4 300 | $45nn: Set instrument FM channel #5 301 | $46nn: Set instrument FM channel #6 302 | 303 | These events are used to set the instrument to be used by a specific FM 304 | channel. The event is followed by a byte, which has an index in the 305 | pointer list that indicates where's the FM instrument data. Instruments 306 | are stored as EIF (Echo Instrument Format). 307 | 308 | $48nn: Set instrument PSG channel #1 309 | $49nn: Set instrument PSG channel #2 310 | $4Ann: Set instrument PSG channel #3 311 | $4Bnn: Set instrument PSG channel #4 312 | 313 | These events are used to set the instrument to be used by a specific PSG 314 | channel. The event is followed by a byte, which has an index in the 315 | pointer list that indicates where's the PSG instrument data. Instruments 316 | are stored as EEF (Echo Envelope Format). 317 | 318 | ============================================================================= 319 | 320 | $D0~$DF: Delay ticks (short) 321 | 322 | Same as $FE01 to $FE10 ($D0 = $FE01, $D1 = $FE02, etc.). Just a shorter 323 | variant of the event to reduce space usage for shorter delays (which are 324 | the most common). 325 | 326 | ============================================================================= 327 | 328 | $E0: Lock FM channel #1 [SFX ONLY] 329 | $E1: Lock FM channel #2 [SFX ONLY] 330 | $E2: Lock FM channel #3 [SFX ONLY] 331 | $E4: Lock FM channel #4 [SFX ONLY] 332 | $E5: Lock FM channel #5 [SFX ONLY] 333 | $E6: Lock FM channel #6 [SFX ONLY] 334 | $E8: Lock PSG channel #1 [SFX ONLY] 335 | $E9: Lock PSG channel #1 [SFX ONLY] 336 | $EA: Lock PSG channel #1 [SFX ONLY] 337 | $EB: Lock PSG channel #1 [SFX ONLY] 338 | 339 | These events are used to "lock" specific channels. When a channel is 340 | locked, BGM can't mess up with it. All channels used by a SFX must be 341 | locked before they're used. Channels get unlocked when the SFX stream is 342 | over. 343 | 344 | To lock the PCM channel, you need to lock FM channel #6 (event $E6). Echo 345 | will behave accordingly. 346 | 347 | ============================================================================= 348 | 349 | $F0: Set parameters FM channel #1 350 | $F1: Set parameters FM channel #2 351 | $F2: Set parameters FM channel #3 352 | $F4: Set parameters FM channel #4 353 | $F5: Set parameters FM channel #5 354 | $F6: Set parameters FM channel #6 355 | 356 | These events set up some miscellaneous parameters for the FM channels. 357 | The event is followed by a byte, which speficies the new parameters to be 358 | used. The format of this byte is as follows: 359 | 360 | Bit 7 ..... 1 to enable left speaker, 0 to mute it 361 | Bit 6 ..... 1 to enable right speaker, 0 to mute it 362 | Bit 5-0 ... Must be 0 363 | 364 | ============================================================================= 365 | 366 | $F8rrnn: Set FM register in bank 0 367 | $F9rrnn: Set FM register in bank 1 368 | 369 | These events are used to change the value of a YM2612 register directly. 370 | The rr byte indicates the register, the nn byte is the value to be 371 | written into it ($F8 writes to $4000/1, $F9 writes to $4002/3). 372 | 373 | HERE BE DRAGONS, USE AT YOUR OWN RISK. The channel locking mechanism will 374 | *not* take care of this event, and if a SFX locks the channel then the 375 | register will *not* be restored properly once the SFX is over. 376 | 377 | More specifically: 378 | 379 | * If you use it on a BGM, don't let it touch any channels that may 380 | get locked by SFXs. 381 | 382 | * If you use it on a SFX for an effect on the SFX itself, use it only 383 | for channels locked by that SFX. 384 | 385 | * If you use it on a SFX to affect the music, use it only on channels 386 | that are *not* locked by any SFX (same as first rule, actually). 387 | 388 | * It's possible to touch the global registers ($22~$2B in bank 0). 389 | Do it at your own risk. You may mess up with Echo's functionality, 390 | so be careful about it and make sure you know what you're doing. 391 | 392 | Since version 1.64, writes to register $27 are handled specially: only 393 | bits 7-6 are passed as-is while bits 5-0 are left to Echo. This is 394 | because this register selects ch3 mode but also has the bits that trigger 395 | the timers. This way, streams can change ch3 mode without risking Echo 396 | undoing the changes. 397 | 398 | ============================================================================= 399 | 400 | $FAnn: Set flags 401 | 402 | This event will set some of the flags. The value nn is a bitmask that is 403 | OR'd with the current flags. 404 | 405 | $FBnn: Clear flags 406 | 407 | This event will clear some of the flags. The value nn is a bitmask that 408 | is AND'd with the current flags. 409 | 410 | ============================================================================= 411 | 412 | $FC: Go to loop 413 | $FD: Set loop point 414 | 415 | This event are used in streams to loop music. Put event $FD where the 416 | loop starts, and then end the stream using event $FC (don't use event 417 | $FF). This will tell Echo to loop the song instead of stopping playback. 418 | 419 | SFX support for this event was added in 1.5, if you loop a sound effect 420 | you'll have to either stop it manually or play another SFX. 421 | 422 | $FEnn: Delay ticks 423 | 424 | This event is used for timing. It tells Echo to wait a specific amount of 425 | ticks before continuing with the stream. A byte follows this event, it's 426 | the amount of ticks to wait. If it's $00, then Echo waits 256 ticks 427 | instead. 428 | 429 | If you need to wait more than 256 ticks, you can trigger this event 430 | several times in a row. For example, to wait 320 ticks, use this event 431 | twice: $FE00FE40 --> 256 ticks + 64 ticks = 320 ticks. 432 | 433 | One tick lasts 1/60th of a second. 434 | 435 | $FF: Stop playback 436 | 437 | This event indicates the end of the stream and tells Echo to stop 438 | playback from this stream. If the stream was a SFX, then any locked 439 | channels will be unlocked as well. 440 | 441 | ============================================================================= 442 | --------------------------------------------------------------------------------