aboutsummaryrefslogtreecommitdiff
path: root/src-z80/player
diff options
context:
space:
mode:
Diffstat (limited to 'src-z80/player')
-rw-r--r--src-z80/player/fm.z80784
-rw-r--r--src-z80/player/freq.z80105
-rw-r--r--src-z80/player/misc.z8031
-rw-r--r--src-z80/player/pcm.z80139
-rw-r--r--src-z80/player/psg.z80667
5 files changed, 1726 insertions, 0 deletions
diff --git a/src-z80/player/fm.z80 b/src-z80/player/fm.z80
new file mode 100644
index 0000000..40cf5da
--- /dev/null
+++ b/src-z80/player/fm.z80
@@ -0,0 +1,784 @@
+;****************************************************************************
+; NoteOnFM
+; Does a "note on" for a FM channel
+;****************************************************************************
+
+NoteOnFMSFX:
+ call NoteOnFM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOnFMBGM:
+ ld b, a
+ PollPCM
+
+ push hl
+ ld a, b
+ and $07 ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play if locked
+
+ ld a, b
+ call NoteOnFM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOnFM:
+ and $07 ; Get channel ID
+ ld (ix+0), $28 ; Note off
+ ld (ix+1), a
+
+ ld b, a
+ ex af, af'
+ PollPCM
+
+ ld a, b ; Determine which port to write
+ and $04
+ rrca
+ ld iyl, a
+
+ PollPCM
+ call GetParam ; Get note
+ PollPCM
+
+ ex af, af'
+ push de
+ push hl
+
+ ld d, a
+ and $03 ; Index of first frequency register
+ add $A4
+ ld e, a
+
+ PollPCM
+
+ ld h, FMFreqTable>>8 ; Get address of note
+ ld a, b
+ and $1F
+ add FMFreqTable&$FF
+ ld l, a
+
+ PollPCM
+
+ ld a, b ; Set new frequency
+ and $E0
+ rrca
+ rrca
+ ld b, a
+ ld a, (hl)
+ or b
+ ld (iy+0), e
+ ld (iy+1), a
+
+ PollPCM
+
+ ld a, e
+ sub 4
+ ld e, a
+ dec l
+ ld a, (hl)
+ ld (iy+0), e
+ ld (iy+1), a
+
+ PollPCM
+
+ ld a, d ; Note on!
+ or $F0
+ ld (ix+0), $28
+ ld (ix+1), a
+
+ pop hl
+ pop de
+ ret ; End of subroutine
+
+;****************************************************************************
+; NoteOffFM
+; Does a "note off" for a FM channel
+;****************************************************************************
+
+NoteOffFMSFX:
+ call NoteOffFM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOffFMBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $07 ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip ; Don't stop if locked
+
+ ld a, b
+ call NoteOffFM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOffFM:
+ and $07 ; Get channel ID
+ ld (ix+0), $28 ; Note off
+ ld (ix+1), a
+ ret ; End of subroutine
+
+;****************************************************************************
+; SetFMNote*
+; Sets the note of a FM channel without "note on"
+;****************************************************************************
+
+SetNoteFMSFX:
+ call SetNoteFM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetNoteFMBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $07 ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip2 ; Don't change if locked
+
+ ld a, b
+ call SetNoteFM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+SetNoteFM:
+ push af
+ ld b, a
+ PollPCM
+
+ ld a, b ; Determine which port to write
+ and $04
+ rrca
+ ld iyl, a
+
+ PollPCM
+ call GetParam ; Get high byte
+ PollPCM
+
+ pop af
+ push de
+
+ and $07
+ ld d, a
+ and $03 ; Index of first frequency register
+ add $A4
+ ld e, a
+
+ PollPCM
+
+ ld (iy+0), e ; Load high byte
+ ld (iy+1), b
+
+ PollPCM ; Load low byte
+ call GetParam
+ PollPCM
+
+ ld a, e
+ sub 4
+ ld e, a
+
+ ld (iy+0), e
+ ld (iy+1), b
+
+ PollPCM
+ pop de
+ ret ; End of subroutine
+
+;****************************************************************************
+; LoadFM*
+; Loads a FM instrument
+;****************************************************************************
+
+LoadFMSFX:
+ call LoadFMEvent ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+LoadFMBGM:
+ and $07 ; Get channel ID
+ ld b, a
+ PollPCM
+
+ push de
+ push bc
+ ld a, b
+ ld de, RAM_BGMFMInstr ; Store instrument ID
+ add e
+ ld e, a
+ PollPCM
+ call GetParam
+ PollPCM
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ ld e, c
+ pop bc
+ ld c, e
+ pop de
+
+ PollPCM
+
+ push hl ; Check if channel is free
+ ld hl, RAM_Locked
+ ld a, b
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip ; Don't load if locked
+
+ PollPCM
+
+ ld a, b
+ and $07
+ push hl
+ ld hl, RAM_BGMFMInstr
+ add l
+ ld l, a
+ ld b, (hl)
+ pop hl
+
+ push af
+ PollPCM
+ pop af
+
+ call LoadFMDirect ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+LoadFMEvent:
+ and $07 ; Get channel ID
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get instrument ID
+ PollPCM
+ ex af, af'
+LoadFMDirect:
+
+ push af
+ and $04 ; Determine which port to write
+ rrca
+ ld iyl, a
+ PollPCM
+ pop af
+
+ push bc
+ push de
+ push hl
+
+ ld h, RAM_PointerList>>8 ; Get instrument address
+ ld l, b
+ ld d, (hl)
+ inc h
+ ld e, (hl)
+ inc h
+ ld c, (hl)
+ ex de, hl
+
+ push af
+ PollPCM
+
+ ld b, 28/4 ; Load FM instrument into our scratch buffer,
+ ld de, RAM_Scratch ; so we don't conflict with PCM bank
+.getinstr: ; switch (which would slow down A LOT)
+ ld a, b
+ ex af, af'
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ inc e
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ inc e
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ inc e
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ inc e
+ ex af, af'
+ ld b, a
+ djnz .getinstr
+ call GetParam
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+
+ PollPCM
+
+ pop af ; Kill ADSR
+ ld b, a
+ call KillFM
+ ld a, b
+
+ ld de, RAM_FMData ; Get address of FM data
+ and $07
+ add e
+ ld e, a
+
+ push af
+ and $03 ; Register index for operator
+ add $B0
+
+ ld hl, RAM_Scratch ; Get address of buffer with the instrument
+ ; data
+
+ ex af, af'
+ PollPCM ; Write operator
+ ex af, af'
+ ld (iy+0), a
+ ld b, (hl)
+ inc l
+ ld (iy+1), b
+
+ ex af, af' ; Store $B0 register in FM data buffer
+ ld a, b ; Also store it in the FM channel information
+ ld (de), a ; table since we need it when changing the
+ ld a, e ; FM channel volume
+ add 8
+ ld e, a
+ PollPCM
+ ex af, af'
+
+ sub $B0-$30
+
+ ex af, af' ; Write registers $30-$3C
+ PollPCM
+ ex af, af'
+
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+
+ ld b, 4 ; Write registers $40-$4C
+.reg40: ; Also store the registers in the FM channel
+ ld c, a ; information field since we'll need them to
+ push af ; set the FM channel volume
+ PollPCM
+ ld (iy+0), c
+ ld a, (hl)
+ ld (iy+1), a
+ ld (de), a
+ ld a, e
+ add 8
+ ld e, a
+ pop af
+ add 4
+ inc l
+ djnz .reg40
+
+ ld b, 20/4 ; Write registers $50-$5C
+.reg50:
+ ld c, b
+ ex af, af'
+ PollPCM
+ ex af, af'
+
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+ inc l
+ ld (iy+0), a
+ ld b, (hl)
+ ld (iy+1), b
+ add 4
+
+ inc l
+ ld b, c
+ djnz .reg50
+
+ PollPCM
+
+ ex de, hl
+ ld a, l
+ sub 8*6
+ ld l, a
+ ld b, (hl)
+ add 8
+ ld l, a
+
+ PollPCM
+
+ pop af
+ pop hl
+ pop de
+ pop bc
+ ret
+
+ ;jp SetFMVolLoad ; End of subroutine
+
+;****************************************************************************
+; SetFMVol*
+; Sets the volume of a FM channel
+;****************************************************************************
+
+SetFMVolSFX:
+ call SetFMVolEvent ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetFMVolBGM:
+ and $07
+ ld b, a
+
+ PollPCM
+
+ push de
+ push bc
+ ld a, b
+ ld de, RAM_BGMFMVol ; Store BGM volume
+ add e
+ ld e, a
+ PollPCM
+ call GetParam
+ PollPCM
+ ex de, hl
+ ld (hl), b
+ ex de, hl
+ ld e, c
+ pop bc
+ ld c, e
+ pop de
+
+ PollPCM
+
+ push hl
+ ld a, b ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip ; Don't change if locked
+
+ PollPCM
+
+ ld a, b
+ push hl
+ ld hl, RAM_BGMFMVol
+ add l
+ ld l, a
+ ld b, (hl)
+ pop hl
+
+ push af
+ PollPCM
+ pop af
+
+ call SetFMVolBGMEvent ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+SetFMVolEvent:
+ push af
+ PollPCM
+ call GetParam ; Get new volume
+ PollPCM
+ pop af
+
+SetFMVolBGMEvent:
+ push bc
+ push de
+ push hl
+
+ ld hl, RAM_FMVolume ; Store new volume
+ and $07
+ add l
+ ld l, a
+ ld (hl), b
+
+ ex af, af'
+ PollPCM
+ ld a, l ; Get address of FM data
+ add 8
+ ld l, a
+ ex af, af'
+
+SetFMVolLoad:
+ push af
+ ;ld iyh, $40 ; Determine which port to write
+ and $04
+ rrca
+ ld iyl, a
+ PollPCM
+ pop af
+
+ and $03 ; Index of first volume register
+ add $40
+ ld c, a
+
+ PollPCM
+
+ ld a, (hl) ; Get algorithm
+ and $07
+ ld e, a
+
+ PollPCM
+
+ ld a, l
+ add 8
+ ld l, a
+ ld a, e ; Process operator #1
+ cp $07
+ jr c, .noop1
+ ld a, (hl)
+ add b
+ cp $7F
+ jr c, .notooloud1
+ ld a, $7F
+.notooloud1:
+ ld (iy+0), c
+ ld (iy+1), a
+.noop1:
+ ld a, c
+ add 4
+ ld c, a
+
+ PollPCM
+
+ ld a, l
+ add 8
+ ld l, a
+ ld a, e ; Process operator #2
+ cp $05
+ jr c, .noop2
+ ld a, (hl)
+ add b
+ cp $7F
+ jr c, .notooloud2
+ ld a, $7F
+.notooloud2:
+ ld (iy+0), c
+ ld (iy+1), a
+.noop2:
+ ld a, c
+ add 4
+ ld c, a
+
+ PollPCM
+
+ ld a, l
+ add 8
+ ld l, a
+ ld a, e ; Process operator #3
+ cp $04
+ jr c, .noop3
+ ld a, (hl)
+ add b
+ cp $7F
+ jr c, .notooloud3
+ ld a, $7F
+.notooloud3:
+ ld (iy+0), c
+ ld (iy+1), a
+.noop3:
+ ld a, c
+ add 4
+ ld c, a
+
+ PollPCM
+
+ ld a, l
+ add 8
+ ld l, a
+ ld a, (hl) ; Process operator #4
+ add b
+ cp $7F
+ jr c, .notooloud4
+ ld a, $7F
+.notooloud4:
+ ld (iy+0), c
+ ld (iy+1), a
+
+ PollPCM
+
+ pop hl
+ pop de
+ pop bc
+ ret ; End of subroutine
+
+;****************************************************************************
+; SetFMParam*
+; Sets the different parameters of a FM channel
+;****************************************************************************
+
+SetFMParamSFX:
+ call SetFMParam ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetFMParamBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $07 ; Check if channel is free
+ ld hl, RAM_Locked
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't modify if locked
+
+ ld a, b
+ call SetFMParam ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+SetFMParam:
+ ld b, a
+ PollPCM
+
+ ;ld iyh, $40 ; Determine which port to write
+ ld a, b
+ and $04
+ rrca
+ ld iyl, a
+
+ ld a, b
+ ex af, af'
+ PollPCM
+ call GetParam ; Get parameters
+ PollPCM
+ ex af, af'
+
+ and $03 ; Get channel ID
+ add $B4
+ ld (iy+0), a ; Set new parameters
+ ld (iy+1), b
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; LockChannelFM [events $E0-$E7]
+; Locks a FM channel
+;****************************************************************************
+
+LockChannelFM:
+ and $07
+ ld b, a
+ PollPCM
+
+ push hl
+ ld h, RAM_Locked>>8 ; Get address of channel to lock
+ ld a, b
+ add RAM_Locked&$FF
+ ld l, a
+ ld (hl), $01 ; Lock channel
+ pop hl
+
+ PollPCM
+
+ ld a, b ; Determine which port to write
+ and $04
+ rrca
+ ld iyl, a
+
+ ld a, b ; Reset stereo
+ and $03
+ add a
+ add a
+ add $B4
+ ld (iy+0), a
+ ld (iy+1), $C0
+
+ jp ProcessSFXRun ; End of subroutine
+
+;****************************************************************************
+; KillFM
+; Kills a FM channel
+;****************************************************************************
+
+KillFM:
+ push af
+ push de
+
+ and $03 ; Load dummy FM instrument
+ add $40
+ ld c, 6
+ ld hl, DummyFMInstr
+.loaddummy:
+ ex af, af'
+ PollPCM
+ ex af, af'
+ ld e, (hl)
+
+ ld (iy+0), a
+ ld (iy+1), e
+ add 4
+ ld (iy+0), a
+ ld (iy+1), e
+ add 4
+ ld (iy+0), a
+ ld (iy+1), e
+ add 4
+ ld (iy+0), a
+ ld (iy+1), e
+ add 4
+
+ inc l
+ dec c
+ jp nz, .loaddummy
+
+ pop de
+ PollPCM
+ pop af
+
+ ld c, a ; Reset ADSR
+ or $F0
+ ld (ix+0), $28
+ ld (ix+1), a
+ ld (ix+0), $28
+ ld (ix+1), c
+
+ PollPCM
+ ret ; End of subroutine
diff --git a/src-z80/player/freq.z80 b/src-z80/player/freq.z80
new file mode 100644
index 0000000..a469c0b
--- /dev/null
+++ b/src-z80/player/freq.z80
@@ -0,0 +1,105 @@
+;****************************************************************************
+; PSGFreqTable
+; Frequency table for all PSG notes
+;****************************************************************************
+
+ ds $100-($&$FF), $FF
+PSGFreqTable:
+ db $83, $35 ; C-4 - 851
+ db $83, $32 ; C#4 - 803
+ db $86, $2F ; D-4 - 758
+ db $8B, $2C ; D#4 - 715
+ db $83, $2A ; E-4 - 675
+ db $8D, $27 ; F-4 - 637
+ db $89, $25 ; F#4 - 601
+ db $88, $23 ; G-4 - 568
+ db $88, $21 ; G#4 - 536
+ db $8A, $1F ; A-4 - 506
+ db $8D, $1D ; A#4 - 477
+ db $82, $1C ; B-4 - 450
+ db $89, $1A ; C-5 - 425
+ db $81, $19 ; C#5 - 401
+ db $8B, $17 ; D-5 - 379
+ db $85, $16 ; D#5 - 357
+ db $81, $15 ; E-5 - 337
+ db $8E, $13 ; F-5 - 318
+ db $8C, $12 ; F#5 - 300
+ db $8C, $11 ; G-5 - 284
+ db $8C, $10 ; G#5 - 268
+ db $8D, $0F ; A-5 - 253
+ db $8E, $0E ; A#5 - 238
+ db $81, $0E ; B-5 - 225
+ db $84, $0D ; C-6 - 212
+ db $88, $0C ; C#6 - 200
+ db $8D, $0B ; D-6 - 189
+ db $82, $0B ; D#6 - 178
+ db $88, $0A ; E-6 - 168
+ db $8F, $09 ; F-6 - 159
+ db $86, $09 ; F#6 - 150
+ db $8E, $08 ; G-6 - 142
+ db $86, $08 ; G#6 - 134
+ db $8E, $07 ; A-6 - 126
+ db $87, $07 ; A#6 - 119
+ db $80, $07 ; B-6 - 112
+ db $8A, $06 ; C-7 - 106
+ db $84, $06 ; C#7 - 100
+ db $8E, $05 ; D-7 - 94
+ db $89, $05 ; D#7 - 89
+ db $84, $05 ; E-7 - 84
+ db $8F, $04 ; F-7 - 79
+ db $8B, $04 ; F#7 - 75
+ db $87, $04 ; G-7 - 71
+ db $83, $04 ; G#7 - 67
+ db $8F, $03 ; A-7 - 63
+ db $8B, $03 ; A#7 - 59
+ db $88, $03 ; B-7 - 56
+ db $85, $03 ; C-8 - 53
+ db $82, $03 ; C#8 - 50
+ db $8F, $02 ; D-8 - 47
+ db $8C, $02 ; D#8 - 44
+ db $8A, $02 ; E-8 - 42
+ db $87, $02 ; F-8 - 39
+ db $85, $02 ; F#8 - 37
+ db $83, $02 ; G-8 - 35
+ db $81, $02 ; G#8 - 33
+ db $8F, $01 ; A-8 - 31
+ db $8D, $01 ; A#8 - 29
+ db $8C, $01 ; B-8 - 28
+ db $8A, $01 ; C-9 - 26
+ db $89, $01 ; C#9 - 25
+ db $87, $01 ; D-9 - 23
+ db $86, $01 ; D#9 - 22
+ db $85, $01 ; E-9 - 21
+ db $83, $01 ; F-9 - 19
+ db $82, $01 ; F#9 - 18
+ db $81, $01 ; G-9 - 17
+ db $80, $01 ; G#9 - 16
+ db $8F, $00 ; A-9 - 15
+ db $8E, $00 ; A#9 - 14
+ db $8E, $00 ; B-9 - 14
+
+;****************************************************************************
+; FMFreqTable
+; Frequency table for all FM notes
+;****************************************************************************
+
+FMFreqTable:
+ dw 644, 681, 722, 765
+ dw 810, 858, 910, 964
+ dw 1021, 1081, 1146, 1214
+
+;****************************************************************************
+; DummyFMInstr
+; Dummy FM instrument to mute FM channels...
+;
+; To-do: put this in its own file? Although I'd like for this table to stay
+; in this area in memory
+;****************************************************************************
+
+DummyFMInstr:
+ db $7F ; $40..$4C
+ db $1F ; $50..$5C
+ db $1F ; $60..$6C
+ db $1F ; $70..$7C
+ db $0F ; $80..$8C
+ db $00 ; $90..$9C
diff --git a/src-z80/player/misc.z80 b/src-z80/player/misc.z80
new file mode 100644
index 0000000..6373ab0
--- /dev/null
+++ b/src-z80/player/misc.z80
@@ -0,0 +1,31 @@
+;****************************************************************************
+; SetDelay* [event $FE]
+; Adds a delay in playback
+;****************************************************************************
+
+SetDelaySFX:
+ call SetDelay ; We're just a wrapper
+ jp DoTick_SFXSkip ; End of subroutine
+
+SetDelayBGM:
+ call SetDelay ; We're just a wrapper
+ jp DoTick_BGMSkip ; End of subroutine
+
+SetDelay:
+ PollPCM
+ call GetParam ; Get delay
+ PollPCM
+
+ ex de, hl
+ ld (hl), d ; Store new address
+ dec l
+ ld (hl), e
+ dec l
+ ld (hl), c
+
+ PollPCM
+
+ dec l ; Store new delay
+ ld (hl), b
+
+ ret ; End of subroutine
diff --git a/src-z80/player/pcm.z80 b/src-z80/player/pcm.z80
new file mode 100644
index 0000000..44bc236
--- /dev/null
+++ b/src-z80/player/pcm.z80
@@ -0,0 +1,139 @@
+;****************************************************************************
+; PlayPCM*
+; Plays a PCM sample
+;****************************************************************************
+
+PlayPCMSFX:
+ call PlayPCM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+PlayPCMBGM:
+ PollPCM
+
+ ld a, (RAM_Locked+6) ; Check if channel is free
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play sample if locked
+
+ call PlayPCM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+PlayPCM:
+ call GetParam ; Get sample ID
+
+ ld a, b
+ exx ; We'll modify PCM data now
+ ld b, $01 ; Play PCM!
+
+ ld h, RAM_PointerList>>8 ; Get offset in pointer list
+ ld l, a
+
+ ld d, (hl) ; Get PCM address
+ inc h
+ ld e, (hl)
+ inc h
+ ld c, (hl)
+
+ ld hl, $6000 ; Restore $6000 back to HL
+ exx ; Back to standard variables
+
+ ld (ix+0), $2B ; Turn on DAC
+ ld (ix+1), $80
+ ld (ix+0), $2A
+ ld (ix+1), $80
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; UpdatePCM
+; Updates PCM output upon a timer event
+;****************************************************************************
+
+UpdatePCM:
+ ;ld (ix+0), $24 ; Reset timer
+ ;ld (ix+1), $FE
+ ;ld (ix+0), $25
+ ;ld (ix+1), $03
+ ld (ix+0), $27
+ ld (ix+1), $1F
+
+ exx ; Switch to PCM registers
+
+ ld a, b ; Do any playback?
+ or a
+ jr z, .nopcm
+
+ ld a, (RAM_LastBank) ; Bank switch?
+ cp c
+ jp z, .noswitchu
+ ld a, c
+ ld (RAM_LastBank), a
+ BankSwitch
+.noswitchu:
+
+ ld a, (de) ; Get sample
+ inc a ; Is it end of waveform?
+ jr z, .stop ; If so, stop
+ ld (ix+0), $2A ; Nope, send sample to YM2612
+ ld (ix+1), a
+
+ inc e ; Get address for next sample
+ jr nz, .nopcm
+ inc d
+ jr nz, .nopcm
+ ld d, $80
+ inc c
+
+.nopcm:
+ exx ; Go back to normal registers
+ ret ; End of subroutine
+
+.stop:
+ ld b, $00 ; Stop playback
+ ld (ix+0), $2A ; Turn off DAC
+ ld (ix+1), $80
+ ld (ix+0), $2B
+ ld (ix+1), $00
+ exx ; Go back to normal registers
+ ret ; End of subroutine
+
+;****************************************************************************
+; StopPCM*
+; Stops a PCM sample
+;****************************************************************************
+
+StopPCMSFX:
+ call StopPCM ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+StopPCMBGM:
+ PollPCM
+
+ ld a, (RAM_Locked+6) ; Check if channel is free
+ or a
+ jp nz, ProcessBGMRun ; Don't stop sample if locked
+
+ call StopPCM ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+StopPCM:
+ exx ; Stop PCM playback
+ ld b, $00
+ exx
+
+ ld (ix+0), $2B ; Disable DAC
+ ld (ix+1), $00
+
+ ret ; End of subroutine
+
+
+;****************************************************************************
+; LockChannelPCM [event $EC]
+; Locks the PCM channel
+;****************************************************************************
+
+LockChannelPCM:
+ ld a, $01 ; Lock PCM channel
+ ld (RAM_Locked+6), a
+
+ call StopPCM ; Stop PCM playback
+ jp ProcessSFXRun ; End of subroutine
diff --git a/src-z80/player/psg.z80 b/src-z80/player/psg.z80
new file mode 100644
index 0000000..46df82a
--- /dev/null
+++ b/src-z80/player/psg.z80
@@ -0,0 +1,667 @@
+;****************************************************************************
+; UpdatePSG
+; Updates PSG output
+;****************************************************************************
+
+UpdatePSG:
+ ld hl, RAM_PSGData+48 ; PSG envelope data of *last* channel
+ ld b, 3 ; Go through all channels
+.loop:
+ push bc
+
+ ld a, (hl) ; Get channel volume
+ bit 7, a
+ jr nz, .noskip
+ ld b, $0F
+ inc l
+ jp .skip
+.noskip:
+ and $7F
+ ld b, a
+
+ inc l ; Add global volume
+ ld a, (hl)
+ add b
+ ld b, a
+
+ PollPCM
+ push bc
+
+ inc l ; Get current address of envelope
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+.readenv:
+ PollPCM
+ call GetParam ; Get next byte
+ PollPCM
+
+ ld a, b
+ cp $FE ; Set loop point?
+ jp z, .envsetloop
+ cp $FF ; Loop envelope?
+ jp z, .envloop
+
+ ld iyl, b ; Keep byte safe somewhere...
+ PollPCM
+
+ ex de, hl ; Store new address
+ ld (hl), d
+ dec l
+ ld (hl), e
+ dec l
+ ld (hl), c
+ dec l
+
+ pop bc
+ PollPCM
+
+ db $FD,$7D ; ld a, iyl ; Mix envelope with volume
+ add b
+ ld b, a
+
+ cp $10 ; Check for overflow
+ jr c, .notmute
+ ld b, $0F
+.notmute:
+
+.skip:
+ PollPCM
+
+ ld a, b ; Set PSG channel volume
+ rlca
+ rlca
+ rlca
+ pop bc
+ or b
+ rrca
+ rrca
+ rrca
+ or $90
+ ld ($7F11), a
+
+ ld a, l ; Go for next channel
+ sub 16+1
+ ld l, a
+ PollPCM
+ dec b
+ jp p, .loop
+
+ jp DoTick_PSGSkip ; End of subroutine
+
+.envsetloop:
+ PollPCM
+ inc e ; Where we store the loop point
+
+ ex de, hl ; Store loop point
+ ld (hl), c
+ inc l
+ ld (hl), e
+ inc l
+ ld (hl), d
+ ex de, hl
+
+ dec e ; Back to where we were...
+ dec e
+ dec e
+ jp .readenv ; Go for next byte
+
+.envloop:
+ PollPCM
+ inc e ; Where we store the loop point
+
+ ex de, hl ; Retrieve loop point
+ ld c, (hl)
+ inc l
+ ld e, (hl)
+ inc l
+ ld d, (hl)
+ ex de, hl
+
+ dec e ; Back to where we were...
+ dec e
+ dec e
+ jp .readenv ; Go for next byte
+
+;****************************************************************************
+; NoteOnPSG*
+; Does a "note on" for a PSG channel
+;****************************************************************************
+
+NoteOnPSGSFX:
+ call NoteOnPSG ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOnPSGBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $03 ; Check if channel is free
+ ld hl, RAM_Locked+8
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play if locked
+
+ ld a, b
+ call NoteOnPSG ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOnPSG:
+ and $03
+ ld b, a
+ ;push af
+ ex af, af'
+ PollPCM
+
+ push hl ; Set channel volume
+ ld h, RAM_PSGData>>8
+ ld a, b
+ rrca
+ rrca
+ rrca
+ rrca
+ ld l, a
+ ld a, (hl)
+ or $80
+ ld (hl), a
+
+ PollPCM
+ push de
+
+ inc l ; Now we'll reset the envelope address...
+ inc l
+ ld d, h
+ ld a, l
+ add 6
+ ld e, a
+
+ PollPCM
+
+ ld a, (de) ; Reset envelope address
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+
+ pop de
+ pop hl
+
+ PollPCM
+ call GetParam ; Get note
+ PollPCM
+
+ ;pop af
+ ex af, af'
+ push hl
+ push de
+ ld h, PSGFreqTable>>8 ; Get address of frequency data
+ ld l, b
+ ld de, $7F11
+
+ rrca ; Set new frequency
+ rrca
+ rrca
+ ld b, (hl)
+ or b
+ ld (de), a
+ inc l
+ ld a, (hl)
+ ld (de), a
+ pop de
+ pop hl
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; NoteOnNoise*
+; Does a "note on" for the noise PSG channel
+;****************************************************************************
+
+NoteOnNoiseSFX:
+ call NoteOnNoise ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOnNoiseBGM:
+ ld a, (RAM_Locked+11) ; Check if channel is free
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play if locked
+
+ call NoteOnNoise ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOnNoise:
+ PollPCM
+ push hl
+
+ ld hl, RAM_PSGData+48 ; Set channel volume
+ ld a, (hl)
+ or $80
+ ld (hl), a
+
+ PollPCM
+ push de
+
+ inc l ; Now we'll reset the envelope address...
+ inc l
+ ld d, h
+ ld a, l
+ add 6
+ ld e, a
+
+ PollPCM
+
+ ld a, (de) ; Reset envelope address
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+ inc l
+ inc e
+ ld a, (de)
+ ld (hl), a
+
+ pop de
+ pop hl
+
+SetNoteNoise:
+ PollPCM
+ call GetParam ; Get noise type
+ PollPCM
+
+ ld a, $E0 ; Set new noise type
+ or b
+ ld ($7F11), a
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; NoteOffPSG*
+; Does a "note off" for a PSG channel
+;****************************************************************************
+
+NoteOffPSGSFX:
+ call NoteOffPSG ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+NoteOffPSGBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $03 ; Check if channel is free
+ ld hl, RAM_Locked+8
+ add l
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMRun ; Don't stop if locked
+
+ ld a, b
+ call NoteOffPSG ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+NoteOffPSG:
+ and $03
+ ld b, a
+ PollPCM
+
+ push hl ; Mark channel as not playing
+ ld h, RAM_PSGData>>8
+ ld a, b
+ rrca
+ rrca
+ rrca
+ rrca
+ ld l, a
+ ld a, (hl)
+ and $7F
+ ld (hl), a
+ pop hl
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; SetPSGVol*
+; Sets the volume of a PSG channel
+;****************************************************************************
+
+SetPSGVolSFX:
+ and $03 ; Get channel ID
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get volume
+ PollPCM
+ ex af, af'
+
+ push hl
+ ld h, RAM_PSGData>>8 ; Set new volume
+ rrca
+ rrca
+ rrca
+ rrca
+ ld l, a
+ ld a, (hl)
+ and $80
+ or b
+ ld (hl), a
+ pop hl
+
+ jp ProcessSFXRun ; End of subroutine
+
+SetPSGVolBGM:
+ and $03 ; Get channel ID
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get volume
+ PollPCM
+ ex af, af'
+
+ push de
+ push hl
+
+ push af
+ ld de, RAM_Locked+8 ; Check if channel is locked
+ add e ; Keep results for later
+ ld e, a
+ ld a, (de)
+ ld e, a
+
+ PollPCM
+ pop af
+
+ ld h, RAM_PSGData>>8 ; Store new volume
+ rrca
+ rrca
+ rrca
+ rrca
+ add 15
+ ld l, a
+ ld (hl), b
+
+ PollPCM
+
+ ld a, e ; Is channel locked?
+ or a
+ jr nz, .nosetvol
+
+ ld a, l ; Set new volume
+ sub 15
+ ld l, a
+ ld a, (hl)
+ and $80
+ or b
+ ld (hl), a
+
+ PollPCM
+
+.nosetvol:
+ pop hl
+ pop de
+
+ jp ProcessBGMRun ; End of subroutine
+
+;****************************************************************************
+; LoadPSG*
+; Loads a PSG instrument
+;****************************************************************************
+
+LoadPSGSFX:
+ and $03 ; Get channel number
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get instrument ID
+ PollPCM
+ ex af, af'
+
+ push de
+ push hl
+
+ ld d, RAM_PointerList>>8 ; Get position in pointer list
+ ld e, b
+
+ ld h, RAM_PSGData>>8 ; Where to store address
+ rrca
+ rrca
+ rrca
+ rrca
+ add 8+2+(RAM_PSGData&$FF)
+ ld l, a
+
+ PollPCM
+
+ ld a, (de) ; Store PSG envelope start address
+ ld (hl), a
+ inc d
+ dec l
+ ld a, (de)
+ ld (hl), a
+ inc d
+ dec l
+ ld a, (de)
+ ld (hl), a
+
+ PollPCM
+
+ ld a, l ; Reset volume
+ sub 8
+ ld l, a
+ ld (hl), $00
+
+ pop hl
+ pop de
+ jp ProcessSFXRun ; End of subroutine
+
+;----------------------------------------------------------------------------
+
+LoadPSGBGM:
+ and $03 ; Get channel number
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get instrument ID
+ PollPCM
+ ex af, af'
+
+ push de
+ push hl
+
+ ld d, RAM_PointerList>>8 ; Get position in pointer list
+ ld e, b
+
+ ld hl, RAM_Locked+8 ; Get if channel is locked
+ push af
+ add l
+ ld l, a
+ pop af
+ ld b, (hl)
+
+ ld h, RAM_PSGData>>8 ; Where to store BGM instrument data
+ rrca
+ rrca
+ rrca
+ rrca
+ add 15
+ ld l, a
+
+ PollPCM
+
+ ld (hl), a ; Reset volume for BGM
+ dec l
+
+ ld a, (de) ; Store PSG envelope address for BGM
+ ld (hl), a
+ inc d
+ dec l
+ ld a, (de)
+ ld (hl), a
+ inc d
+ dec l
+ ld a, (de)
+ ld (hl), a
+
+ PollPCM
+
+ ld a, b ; Don't set PSG envelope if locked
+ or a
+ jp z, .noloadlocked
+ pop hl
+ pop de
+ jp ProcessBGMRun
+.noloadlocked:
+ PollPCM
+
+ ld d, h ; Set PSG envelope
+ ld a, l
+ sub 12-8
+ ld e, a
+
+ ld a, (hl)
+ ld (de), a
+ inc l
+ inc e
+ ld a, (hl)
+ ld (de), a
+ inc l
+ inc e
+ ld a, (hl)
+ ld (de), a
+
+ PollPCM
+
+ ld a, l ; Reset volume
+ sub 8
+ ld l, a
+ ld (hl), $00
+
+ pop hl
+ pop de
+ jp ProcessBGMRun ; End of subroutine
+
+;****************************************************************************
+; SetNotePSG*
+; Sets the note of a PSG channel without "note on"
+;****************************************************************************
+
+SetNotePSGSFX:
+ call SetNotePSG ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetNotePSGBGM:
+ ld b, a
+ PollPCM
+ ld a, b
+
+ push hl
+ and $0F ; Check if channel is free
+ ;ld hl, RAM_Locked
+ ;add l
+ ld h, RAM_Locked>>8
+ add RAM_Locked&$FF
+ ld l, a
+ ld a, (hl)
+ pop hl
+ or a
+ jp nz, ProcessBGMSkip2 ; Don't play if locked
+
+ ld a, b
+ call SetNotePSG ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+SetNotePSG:
+ and $03 ; Get channel number
+
+ ex af, af'
+ PollPCM
+ call GetParam ; Get first byte
+ PollPCM
+ ex af, af'
+
+ push de ; PSG port address
+ ld de, $7F11
+
+ rrca ; Set first frequency byte
+ rrca
+ rrca
+ or b
+ or $80
+ ld (de), a
+
+ PollPCM
+ call GetParam ; Get second byte
+ PollPCM
+
+ ex de, hl
+ ld (hl), b ; Set second frequency byte
+ ex de, hl
+ pop de
+
+ ret ; End of subroutine
+
+;****************************************************************************
+; SetNoteNoise*
+; Sets the note of the noise PSG channel without "note on"
+;****************************************************************************
+
+SetNoteNoiseSFX:
+ call SetNoteNoise ; We're just a wrapper
+ jp ProcessSFXRun ; End of subroutine
+
+SetNoteNoiseBGM:
+ ld a, (RAM_Locked+11) ; Check if channel is free
+ or a
+ jp nz, ProcessBGMSkip1 ; Don't play if locked
+
+ call SetNoteNoise ; We're just a wrapper
+ jp ProcessBGMRun ; End of subroutine
+
+;****************************************************************************
+; LockChannelPSG [events $E8-$EB]
+; Locks a PSG channel
+;****************************************************************************
+
+LockChannelPSG:
+ ld b, a
+ PollPCM
+ push hl
+
+ ld h, RAM_Locked>>8 ; Get address of channel to lock
+ ld a, b
+ and $0F
+ add RAM_Locked&$FF
+ ld l, a
+
+ ld (hl), $01 ; Lock channel
+
+ PollPCM
+
+ ld a, b ; Stop channel
+ rrca
+ rrca
+ rrca
+ rrca
+ ld l, a
+ ld h, RAM_PSGData>>8
+ ld (hl), $00
+
+ pop hl
+ jp ProcessSFXRun ; End of subroutine