aboutsummaryrefslogtreecommitdiff
path: root/src-z80/core
diff options
context:
space:
mode:
authorJavier Degirolmo2011-10-07 12:17:54 -0300
committerJavier Degirolmo2011-10-07 12:17:54 -0300
commit178968cc2fc4c32fb918ebb4ccd24d78b64cd73f (patch)
treed8cb1d39bd1991258ffe45b4bcc498b0443e4b35 /src-z80/core
Initial upload, Echo 0.8 UNSTABLE VERSION
Diffstat (limited to 'src-z80/core')
-rw-r--r--src-z80/core/bgm.z80290
-rw-r--r--src-z80/core/main.z80330
-rw-r--r--src-z80/core/sfx.z80316
-rw-r--r--src-z80/core/vars.z8070
4 files changed, 1006 insertions, 0 deletions
diff --git a/src-z80/core/bgm.z80 b/src-z80/core/bgm.z80
new file mode 100644
index 0000000..f69235d
--- /dev/null
+++ b/src-z80/core/bgm.z80
@@ -0,0 +1,290 @@
+;****************************************************************************
+; PlayBGM [command $04]
+; Plays a BGM
+;****************************************************************************
+
+PlayBGM:
+ PollPCM
+ call ClearBGM ; Clear BGM resources
+ PollPCM
+
+ ld a, (RAM_Status) ; Show BGM playback in Echo's status
+ or $02
+ ld (RAM_Status), a
+
+ PollPCM
+
+ ld hl, RAM_ComBank ; Get command parameters
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+
+ PollPCM
+
+ xor a ; Command parsed
+ ld (RAM_Command), a
+
+ ld hl, RAM_BGMData ; Set BGM as playing
+ ld (hl), $01
+ inc l ; No delays!
+ ld (hl), $01
+ inc l ; Store BGM start bank
+ ld (hl), c
+ inc l ; Store BGM start address (low)
+ ld (hl), e
+ inc l ; Store BGM start address (high)
+ ld (hl), d
+
+ PollPCM
+
+ ld hl, ProcessBGM ; Tell Echo to process BGM
+ ld (DoTick_BGM+1), hl
+
+ PollPCM
+ jp IdleLoop ; End of subroutine
+
+;****************************************************************************
+; ProcessBGM
+; Processes a tick for a BGM
+;****************************************************************************
+
+ProcessBGM:
+ PollPCM
+
+ ld hl, RAM_BGMData+1 ; BGM data address
+
+ ld a, (hl) ; Delaying?
+ dec a
+ jp z, .nodelay
+ ld (hl), a
+
+ jp DoTick_BGMSkip ; End of subroutine
+
+.nodelay:
+ PollPCM
+
+ inc l
+ ld c, (hl) ; Get current address
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+ProcessBGMRun:
+ PollPCM ; Fetch next event
+ call GetParam
+ PollPCM
+
+ ld a, b ; Parse byte
+
+ cp $08
+ jp c, NoteOnFMBGM ; Events $00-$07: note on FM
+ cp $0B
+ jp c, NoteOnPSGBGM ; Events $08-$0A: note on PSG (square)
+ jp z, NoteOnNoiseBGM ; Event $0B: note on PSG (noise)
+ cp $0C
+ jp z, PlayPCMBGM ; Event $0C: play PCM
+
+ PollPCM
+ ld a, b
+
+ cp $18
+ jp c, NoteOffFMBGM ; Events $10-$17: note off FM
+ cp $1C
+ jp c, NoteOffPSGBGM ; Events $18-$1B: note off PSG
+ jp z, StopPCMBGM ; Event $1C: note off PCM
+
+ PollPCM
+ ld a, b
+
+ cp $FE
+ jp z, SetDelayBGM ; Event $FE: set delay
+ cp $FF
+ jp z, StopBGMEvent ; Event $FF: stop BGM
+ cp $FC
+ jp z, LoopBGM ; Event $FC: loop BGM
+ cp $FD
+ jp z, SetLoopBGM ; Event $FD: set loop point
+
+ PollPCM
+ ld a, b
+
+ cp $28
+ jp c, SetFMVolBGM ; Events $28-$2B: set FM volume
+ cp $2C
+ jp c, SetPSGVolBGM ; Events $28-$2B: set PSG volume
+
+ PollPCM
+ ld a, b
+
+ cp $38
+ jp c, SetNoteFMBGM ; Events $30-$37: set FM note
+ cp $3B
+ jp c, SetNotePSGBGM ; Events $38-$3A: set PSG note (square)
+ jp z, SetNoteNoiseBGM ; Event $3B: set PSG note (noise)
+
+ PollPCM
+ ld a, b
+
+ cp $48
+ jp c, LoadFMBGM ; Events $40-$47: load FM instrument
+ cp $4C
+ jp c, LoadPSGBGM ; Events $48-$4B: load PSG instrument
+
+ PollPCM
+ ld a, b
+
+ cp $F8 ; Events $F0-$F7: set FM parameters
+ jp c, SetFMParamBGM
+
+ PollPCM ; FFFFFFFFF bad event >:(
+ jp StopBGMEvent ; End of subroutine
+
+ProcessBGMSkip2: ; This is where we land after a locked event
+ PollPCM ; that had two bytes for the parameter
+ inc l ; Skip first byte
+ jp nz, .nobankskip2
+ inc h
+ jp nz, .nobankskip2
+ ld h, $80
+ inc c
+.nobankskip2:
+
+ProcessBGMSkip1: ; This is where we land after a locked event
+ PollPCM ; that had one byte for the parameter
+ inc l ; Skip byte
+ jp nz, .nobankskip1
+ inc h
+ jp nz, .nobankskip1
+ ld h, $80
+ inc c
+.nobankskip1:
+
+ProcessBGMSkip: ; This is where we land after a locked event
+ PollPCM ; without parameters
+
+ jp ProcessBGMRun ; Keep processing
+
+;****************************************************************************
+; StopBGM* [command $05, event $FF]
+; Stops BGM playback
+;****************************************************************************
+
+StopBGMEvent:
+ call StopBGM ; We're just a wrapper
+ jp DoTick_BGMSkip ; End of subroutine
+
+StopBGMCmd:
+ xor a ; Command parsed
+ ld (RAM_Command), a
+ call StopBGM ; We're just a wrapper
+ jp IdleLoop ; End of subroutine
+
+StopBGM:
+ ld a, (RAM_Status) ; Hide BGM playback in Echo's status
+ and $FD
+ ld (RAM_Status), a
+
+ PollPCM
+
+ call ClearBGM ; Clear BGM resources
+ PollPCM
+
+ xor a ; Stop playback
+ ld (RAM_BGMPlaying), a
+ ld hl, DoTick_BGMSkip
+ ld (DoTick_BGM+1), hl
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; ClearBGM
+; Clears BGM resources
+;****************************************************************************
+
+ClearBGM:
+ ld a, (RAM_Locked+6) ; Stop PCM playback if needed
+ or a
+ call z, StopPCM
+
+ ld b, 4 ; Mute all non-locked PSG channels
+ ld de, RAM_PSGData+48
+ ld hl, RAM_Locked+11
+.mutepsg:
+ PollPCM
+ ld a, (hl)
+ or a
+ jr nz, .nopsgmute
+ xor a
+ ld (de), a
+.nopsgmute:
+ PollPCM
+ ld a, e
+ sub 16
+ ld e, a
+ dec l
+ djnz .mutepsg
+
+ ld b, 8 ; Mute all non-locked FM channels
+.mutefm:
+ PollPCM
+ ld a, (hl)
+ or a
+ jr nz, .nofmmute
+ PollPCM
+ dec b
+ ld (ix+0), $28
+ ld (ix+1), b
+ inc b
+.nofmmute:
+ dec l
+ djnz .mutefm
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; LoopBGM [event $FC]
+; Makes a BGM loop
+;****************************************************************************
+
+LoopBGM:
+ PollPCM
+
+ ex de, hl ; Get looping address
+ inc l
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ dec l
+ dec l
+ dec l
+ ex de, hl
+
+ jp ProcessBGMRun ; End of subroutine
+
+;****************************************************************************
+; SetLoopBGM [event $FD]
+; Sets the BGM loop point
+;****************************************************************************
+
+SetLoopBGM:
+ PollPCM
+
+ ex de, hl
+ inc l ; Store loop point address
+ ld (hl), c
+ inc l
+ ld (hl), e
+ inc l
+ ld (hl), d
+ dec l
+ dec l
+ dec l
+ ex de, hl
+
+ jp ProcessBGMRun ; End of subroutine
diff --git a/src-z80/core/main.z80 b/src-z80/core/main.z80
new file mode 100644
index 0000000..4018a1b
--- /dev/null
+++ b/src-z80/core/main.z80
@@ -0,0 +1,330 @@
+;****************************************************************************
+; EntryPoint
+; Where the program starts
+;****************************************************************************
+
+EntryPoint:
+ ld sp, RAM_Stack ; Init stack
+
+ xor a ; Reset Echo status
+ ld (RAM_Status), a
+
+ ld hl, $7F11 ; Mute PSG
+ ld (hl), $9F
+ ld (hl), $BF
+ ld (hl), $DF
+ ld (hl), $FF
+ xor a
+ ld (RAM_PSGData), a
+ ld (RAM_PSGData+16), a
+ ld (RAM_PSGData+32), a
+ ld (RAM_PSGData+48), a
+
+ ld hl, $6000 ; Set default bank
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+ ld (hl), l
+
+ ;ex af, af' ; Set 9th bank bit to 0 by default
+ ;xor a ; (0 = $000000-$7FFFFF range aka MD mode)
+ ;ex af, af' ; (1 = $800000-$FFFFFF range aka 32X mode)
+
+ ld ix, $4000 ; YM2612 I/O ports base address
+ ld iyh, $40
+
+ exx ; Init PCM playback status
+ ld bc, $0000 ; B = playing, C = bank
+ ld de, $0000 ; DE = address
+ ld hl, $6000 ; HL = always $6000
+ exx
+
+ ld (ix+0), $2B ; Disable DAC by default
+ ld (ix+1), $00
+
+ ld e, $7F ; Mute all FM channels
+ ld a, $40
+ ld b, 4
+.mutefm:
+ ld (ix+0), a
+ ld (ix+1), e
+ ld (ix+2), a
+ ld (ix+3), e
+ inc a
+ ld (ix+0), a
+ ld (ix+1), e
+ ld (ix+2), a
+ ld (ix+3), e
+ inc a
+ ld (ix+0), a
+ ld (ix+1), e
+ ld (ix+2), a
+ ld (ix+3), e
+ inc a
+ inc a
+ djnz .mutefm
+
+ ld (ix+0), $B4 ; Ensure all channels can be heard from both
+ ld (ix+1), $C0 ; speakers (by default they're mute!)
+ ld (ix+0), $B5
+ ld (ix+1), $C0
+ ld (ix+0), $B6
+ ld (ix+1), $C0
+ ld (ix+2), $B4
+ ld (ix+3), $C0
+ ld (ix+2), $B5
+ ld (ix+3), $C0
+ ld (ix+2), $B6
+ ld (ix+3), $C0
+
+ ld (ix+0), $24 ; Init timers
+ ld (ix+1), $FE
+ ld (ix+0), $25
+ ld (ix+1), $03
+ ld (ix+0), $26
+ ld (ix+0), $C9
+ ld (ix+1), $FF
+ ld (ix+0), $27
+ ld (ix+1), $3F
+
+ jp IdleLoop ; Go into idle loop
+
+;****************************************************************************
+; PollPCM
+; Used to update PCM while not idle
+;****************************************************************************
+
+PollPCM: macro
+ ld a, ($4000)
+ bit 0, a
+ call nz, UpdatePCM
+ endm
+
+;****************************************************************************
+; RunCommand
+; Checks which command to run
+;****************************************************************************
+
+RunCommand:
+ dec a ; Command $01: load list
+ jp z, LoadList
+ dec a ; Command $02: play SFX
+ jp z, PlaySFX
+ dec a ; Command $03: stop SFX
+ jp z, StopSFXCmd
+ dec a ; Command $04: play BGM
+ jp z, PlayBGM
+ dec a ; Command $05: stop BGM
+ jp z, StopBGMCmd
+
+ PollPCM
+
+ xor a ; Bad command, ignore >:(
+ ld (RAM_Command), a
+
+ PollPCM
+
+;****************************************************************************
+; IdleLoop
+; Loop that runs when not processing SFX or BGM
+;****************************************************************************
+
+IdleLoop:
+ ld a, (RAM_Command) ; Look for commands
+ or a
+ jr nz, RunCommand
+
+ PollPCM ; Poll PCM
+
+ ld a, ($4000) ; Tick?
+ bit 1, a
+ jr nz, DoTick
+ bit 0, a ; Poll PCM again
+ call nz, UpdatePCM ; Not using macro for optimization purposes
+
+ jp IdleLoop ; Keep idling
+
+;****************************************************************************
+; DoTick
+; Called whenever a new tick triggers
+;****************************************************************************
+
+DoTick:
+ ld a, (ix+0)
+ bit 0, a
+ call nz, UpdatePCM
+
+ ld (ix+0), $26 ; Reset timer
+.timerset:
+ ld (ix+1), $C8
+ ld (ix+0), $27
+ ld (ix+1), $2F
+
+ ld a, (.timerset+3) ; $C8 is too fast, $C9 is too slow
+ xor $01 ; So, we alternate between them to compensate
+ ld (.timerset+3), a
+
+ PollPCM
+
+DoTick_SFX: ; Process SFXs
+ jp DoTick_SFXSkip
+DoTick_SFXSkip:
+
+ PollPCM
+
+DoTick_BGM: ; Process BGMs
+ jp DoTick_BGMSkip
+DoTick_BGMSkip:
+
+ PollPCM
+
+ jp UpdatePSG ; Update PSG envelopes
+DoTick_PSGSkip:
+
+ PollPCM
+
+ jp IdleLoop ; End of subroutine
+
+;****************************************************************************
+; BankSwitch
+; Switches into a new bank (won't update player status!)
+;
+; input A .... New bank to switch into
+; input HL ... Must be $6000
+; breaks ..... AF
+;****************************************************************************
+
+BankSwitch: macro
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ rrca
+ ld (hl), a
+ ld (hl), l
+ rrca
+ ld (hl), a
+ endm
+
+;****************************************************************************
+; LoadList [command $01]
+; Loads the pointer list
+;****************************************************************************
+
+LoadList:
+ ld hl, RAM_ComBank ; Get command parameters
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+ xor a ; Command parsed
+ ld (RAM_Command), a
+
+ ld a, c ; Do initial bank switch
+ ld de, $6000
+ ex de, hl
+ BankSwitch
+ ex de, hl
+
+ ld de, RAM_PointerList ; Where the pointer list starts
+
+.loop:
+ ld a, (hl) ; Get high address byte
+ or a ; Is it the end of the list?
+ jp z, .end ; If so, stop parsing list
+ ld (de), a ; Nope, store it in list
+
+ inc d ; Get address for next byte
+ inc l
+ jr nz, .noswitch1
+ inc h
+ jr nz, .noswitch1
+ inc c
+ ld a, c
+ ld h, $60
+ BankSwitch
+ ld h, $80
+.noswitch1:
+
+ ld a, (hl) ; Get low address byte
+ ld (de), a ; Store it in pointer list
+
+ inc d ; Get address for next byte
+ inc l
+ jr nz, .noswitch2
+ inc h
+ jr nz, .noswitch2
+ inc c
+ ld a, c
+ ld h, $60
+ BankSwitch
+ ld h, $80
+.noswitch2:
+
+ ld a, (hl) ; Get bank byte
+ ld (de), a ; Store it in pointer list
+
+ dec d ; Get address for next byte
+ dec d
+ inc e
+ inc l
+ jr nz, .noswitch3
+ inc h
+ jr nz, .noswitch3
+ inc c
+ ld a, c
+ ld h, $60
+ BankSwitch
+ ld h, $80
+.noswitch3:
+
+ jp .loop ; Go for next byte
+
+.end:
+ ld a, c ; Store current bank
+ ld (RAM_LastBank), a
+ jp IdleLoop ; End of subroutine
+
+;****************************************************************************
+; GetParam
+; Subroutine for getting the parameter byte
+;****************************************************************************
+
+GetParam:
+ ld a, (RAM_LastBank) ; Bank switch?
+ cp c
+ jp z, .noswitchp
+ ld a, c
+ ld (RAM_LastBank), a
+ exx
+ BankSwitch
+ exx
+.noswitchp:
+ ld b, (hl) ; Get volume
+
+ PollPCM
+
+ inc l ; Get next address
+ jp nz, .nonewbankp
+ inc h
+ jp nz, .nonewbankp
+ ld h, $80
+ inc c
+.nonewbankp:
+
+ ret ; End of subroutine
diff --git a/src-z80/core/sfx.z80 b/src-z80/core/sfx.z80
new file mode 100644
index 0000000..b7e26aa
--- /dev/null
+++ b/src-z80/core/sfx.z80
@@ -0,0 +1,316 @@
+;****************************************************************************
+; PlaySFX [command $02]
+; Plays a SFX
+;****************************************************************************
+
+PlaySFX:
+ PollPCM
+ call ClearSFX ; Clear SFX resources
+ PollPCM
+
+ ld a, (RAM_Status) ; Show SFX playback in Echo's status
+ or $01
+ ld (RAM_Status), a
+
+ PollPCM
+
+ ld hl, RAM_ComBank ; Get command parameters
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+
+ PollPCM
+
+ xor a ; Command parsed
+ ld (RAM_Command), a
+
+ ld hl, RAM_SFXData ; Set SFX as playing
+ ld (hl), $01
+ inc l ; No delays!
+ ld (hl), $01
+ inc l ; Store SFX start bank
+ ld (hl), c
+ inc l ; Store SFX start address (low)
+ ld (hl), e
+ inc l ; Store SFX start address (high)
+ ld (hl), d
+
+ PollPCM
+
+ ld hl, ProcessSFX ; Tell Echo to process SFX
+ ld (DoTick_SFX+1), hl
+
+ PollPCM
+ jp IdleLoop ; End of subroutine
+
+;****************************************************************************
+; ProcessSFX
+; Processes a tick for a SFX
+;****************************************************************************
+
+ProcessSFX:
+ PollPCM
+
+ ld hl, RAM_SFXData+1 ; SFX data address
+
+ ld a, (hl) ; Delaying?
+ dec a
+ jp z, .nodelay
+ ld (hl), a
+
+ jp DoTick_SFXSkip ; End of subroutine
+
+.nodelay:
+ PollPCM
+
+ inc l ; Get current address
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+ProcessSFXRun:
+ PollPCM ; Fetch next event
+ call GetParam
+ PollPCM
+
+ ld a, b ; Parse byte
+
+ cp $08
+ jp c, NoteOnFMSFX ; Events $00-$07: note on FM
+ cp $0B
+ jp c, NoteOnPSGSFX ; Events $08-$0A: note on PSG (square)
+ jp z, NoteOnNoiseSFX ; Event $0B: note on PSG (noise)
+ cp $0C
+ jp z, PlayPCMSFX ; Event $0C: note on PCM
+
+ PollPCM
+ ld a, b
+
+ cp $18
+ jp c, NoteOffFMSFX ; Events $10-$17: note off FM
+ cp $1C
+ jp c, NoteOffPSGSFX ; Events $18-$1B: note off PSG
+ jp z, StopPCMSFX ; Event $1C: note off PCM
+
+ PollPCM
+ ld a, b
+
+ cp $FE
+ jp z, SetDelaySFX ; Event $FE: set delay
+ cp $FF
+ jp z, StopSFXEvent ; Event $FF: stop SFX
+
+ PollPCM
+ ld a, b
+
+ cp $28
+ jp c, SetFMVolSFX ; Events $28-$2B: set FM volume
+ cp $2C
+ jp c, SetPSGVolSFX ; Events $28-$2B: set PSG volume
+
+ PollPCM
+ ld a, b
+
+ cp $38
+ jp c, SetNoteFMSFX ; Events $30-$37: set FM note
+ cp $3B
+ jp c, SetNotePSGSFX ; Events $38-$3A: set PSG note (square)
+ jp z, SetNoteNoiseSFX ; Event $3B: set PSG note (noise)
+
+ PollPCM
+ ld a, b
+
+ cp $48
+ jp c, LoadFMSFX ; Events $40-$47: load FM instrument
+ cp $4C
+ jp c, LoadPSGSFX ; Events $48-$4B: load PSG instrument
+
+ PollPCM
+ ld a, b
+
+ cp $E8
+ jp c, LockChannelFM ; Events $E0-$E7: lock FM channel
+ cp $EC
+ jp c, LockChannelPSG ; Events $E8-$EB: lock PSG channel
+ jp z, LockChannelPCM ; Event $EC: lock PCM channel
+
+ PollPCM
+ ld a, b
+
+ cp $F8 ; Events $F0-$F7: set FM parameters
+ jp c, SetFMParamSFX
+
+;****************************************************************************
+; StopSFX* [command $03, event $FF]
+; Stops SFX playback
+;****************************************************************************
+
+StopSFXEvent:
+ call StopSFX ; We're just a wrapper
+ jp DoTick_SFXSkip ; End of subroutine
+
+StopSFXCmd:
+ xor a ; Command parsed
+ ld (RAM_Command), a
+ call StopSFX ; We're just a wrapper
+ jp IdleLoop ; End of subroutine
+
+StopSFX:
+ PollPCM
+
+ ld a, (RAM_Status) ; Hide SFX playback in Echo's status
+ and $FE
+ ld (RAM_Status), a
+
+ PollPCM
+
+ xor a ; Stop playback
+ ld (RAM_SFXPlaying), a
+ ld hl, DoTick_SFXSkip
+ ld (DoTick_SFX+1), hl
+
+ PollPCM
+ call ClearSFX ; Clear SFX resources
+
+ PollPCM
+ ret ; End of subroutine
+
+;****************************************************************************
+; ClearSFX
+; Clears SFX resources
+;****************************************************************************
+
+ClearSFX:
+ ld a, (RAM_Locked+6) ; Stop PCM playback if needed
+ or a
+ call nz, StopPCM
+
+;----------------------------------------------------------------------------
+
+ ld b, 4 ; Look for locked PSG channels
+ ld de, RAM_Locked+11
+.unlockpsg:
+
+ PollPCM
+
+ ld a, (de) ; Check if this channel needs unlocking
+ or a
+ jr z, .psgfree
+ xor a
+ ld (de), a
+
+ PollPCM
+
+ ld a, b ; Restore BGM volume
+ rrca
+ rrca
+ rrca
+ rrca
+ dec a
+ ld h, RAM_PSGData>>8
+ ld l, a
+ ld c, (hl)
+ sub 15
+ ld l, a
+ ld (hl), c
+
+ PollPCM
+ push de
+
+ ld a, l ; Restore BGM envelope
+ add 8
+ ld l, a
+ add 12-8
+ ld e, a
+ ld d, h
+
+ PollPCM
+
+ ld a, (de)
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+
+ pop de
+ PollPCM
+
+.psgfree:
+ dec e ; Go for next PSG channel to unlock
+ djnz .unlockpsg
+
+;----------------------------------------------------------------------------
+
+ ld b, 8 ; Look for locked FM channels
+.unlockfm:
+
+ PollPCM
+
+ ld a, (de) ; Check if this channel needs unlocking
+ or a
+ jp z, .fmfree
+ xor a
+ ld (de), a
+
+ dec b ; Mute FM channel
+ ld (ix+0), $28
+ ld (ix+1), b
+
+ PollPCM
+
+ ld a, b
+ and $04 ; Determine which port to write
+ rrca
+ ld iyl, a
+
+ ld a, b ; Kill ADSR
+ call KillFM
+
+ PollPCM
+
+ ld hl, RAM_BGMFMVol ; Restore BGM FM volume
+ ld a, b
+ add l
+ ld l, a
+ ld c, (hl)
+ ld a, l
+ add 8
+ ld l, a
+ ld (hl), c
+
+ PollPCM
+
+ ld a, l ; Restore BGM FM instrument
+ sub 8*2
+ ld l, a
+ push bc
+ push de
+ push hl
+ ld a, b
+ ld b, (hl)
+ call LoadFMDirect
+ pop hl
+ pop de
+ pop bc
+
+ PollPCM
+ inc b
+.fmfree:
+ PollPCM
+ dec e ; Go for next FM channel to unlock
+ dec b
+ jp nz, .unlockfm
+
+;----------------------------------------------------------------------------
+
+ ret ; End of subroutine
diff --git a/src-z80/core/vars.z80 b/src-z80/core/vars.z80
new file mode 100644
index 0000000..ef9a676
--- /dev/null
+++ b/src-z80/core/vars.z80
@@ -0,0 +1,70 @@
+;****************************************************************************
+; Player variables
+;****************************************************************************
+
+ ds $100-($&$FF), $FF
+RAM_PSGData: ds 4*16 ; PSG envelope data
+ ; ds 1 ... Channel volume
+ ; ds 1 ... Global volume
+ ; ds 3 ... Current address
+ ; ds 3 ... Looping address
+ ; ds 3 ... Start address
+ ; ds 1 ... Padding
+ ; ds 3 ... BGM instrument address
+ ; ds 1 ... BGM channel volume
+
+RAM_BGMFMInstr: ds 8 ; FM instruments used by BGM
+RAM_BGMFMVol: ds 8 ; FM volumes used by BGM
+RAM_FMVolume: ds 8 ; Volume of each FM channel
+RAM_FMData: ds 8*5 ; FM info (for volume handling)
+ ; ds 8*1 ... Register $B0
+ ; ds 8*1 ... Register $40
+ ; ds 8*1 ... Register $44
+ ; ds 8*1 ... Register $48
+ ; ds 8*1 ... Register $4C
+
+RAM_LastBank: ds 1 ; Last accessed bank
+
+RAM_BGMData: ; Where BGM data starts
+RAM_BGMPlaying: ds 1 ; Set if a BGM is playing
+RAM_BGMDelay: ds 1 ; How many ticks to wait
+RAM_BGMBank: ds 1 ; Current BGM bank
+RAM_BGMAddress: ds 2 ; Current BGM address
+RAM_BGMLoopPoint: ds 3 ; BGM loop point
+
+RAM_SFXData: ; Where SFX data starts
+RAM_SFXPlaying: ds 1 ; Set if a SFX is playing
+RAM_SFXDelay: ds 1 ; How many ticks to wait
+RAM_SFXBank: ds 1 ; Current SFX bank
+RAM_SFXAddress: ds 2 ; Current SFX address
+
+RAM_Locked: ds 12 ; Locked channels
+
+RAM_Scratch: ds 32 ; Scratch bytes, may be useful when
+ ; buffering to speed up to avoid bank
+ ; switching conflicts
+
+;****************************************************************************
+; Pointer list starts being stored from here
+; $300 (768) bytes are needed to store the pointer list
+;
+; Format for a pointer list entry is as follows:
+; RAM_PointerList[$000+n] = address high
+; RAM_PointerList[$100+n] = address low
+; RAM_PointerList[$200+n] = bank
+;****************************************************************************
+
+ ds $100-($&$FF), $FF
+
+RAM_PointerList: equ $1C00
+
+;****************************************************************************
+; 68000 communication variables
+;****************************************************************************
+
+RAM_Stack: equ $1FF0 ; Where stack starts
+
+RAM_Status: equ $1FF0 ; Current playback status
+RAM_Command: equ $1FFF ; Command type
+RAM_ComAddr: equ $1FFD ; Command address parameter
+RAM_ComBank: equ $1FFC ; Command bank parameter