mirror of https://github.com/zxdos/zxuno.git
661 lines
23 KiB
PHP
661 lines
23 KiB
PHP
; playstc.inc - play Sound Tracker .STC music file on AY-3-891x sound chip.
|
|
;
|
|
; Contributor of a binary file:
|
|
; 2016 Michal B. (a.k.a. Yerzmyey)
|
|
;
|
|
; Reverse-engineer:
|
|
; 2021 Ivan Tatarinov <ivan-tat@ya.ru>
|
|
;
|
|
; This is free and unencumbered software released into the public domain.
|
|
; For more information, please refer to <http://unlicense.org>.
|
|
;
|
|
; SPDX-FileCopyrightText: 2021 Ivan Tatarinov <ivan-tat@ya.ru>
|
|
;
|
|
; SPDX-FileContributor: 2016 Michal B. (a.k.a. Yerzmyey) (binary file)
|
|
;
|
|
; SPDX-License-Identifier: Unlicense
|
|
|
|
; Compatible compilers:
|
|
; SJAsmPlus, <https://github.com/sjasmplus/sjasmplus/>
|
|
|
|
module PlaySTC
|
|
|
|
include filestc.def
|
|
include ay.def
|
|
|
|
if PLAYSTC_USER_DEFINED_FILE != 1
|
|
|
|
; Clear all values for better compression of the final compiled binary file
|
|
|
|
STCDelay: equ 0
|
|
STCDelayCtr: equ 0
|
|
STCSamplesCnt: equ 0
|
|
STCPositionsCnt: equ 0
|
|
STCHeader: equ 0
|
|
STCPositions: equ 0 - ST_Positions.rPositions
|
|
STCOrnaments: equ 0
|
|
STCPatterns: equ 0
|
|
STCSamples: equ 0
|
|
STCChannelA: equ 0
|
|
STCChannelB: equ 0
|
|
STCChannelC: equ 0
|
|
|
|
; internal values
|
|
_STCOrnamentData: equ 0
|
|
_STCRepeatLen: equ 0
|
|
|
|
else ; PLAYSTC_USER_DEFINED_FILE == 1
|
|
|
|
; internal values
|
|
_STCOrnamentData: equ STCOrnaments + ST_Ornament.rData
|
|
_STCRepeatLen: equ -1
|
|
|
|
endif ; PLAYSTC_USER_DEFINED_FILE == 1
|
|
|
|
; Constants
|
|
|
|
NOTE_FREQ = 55 ; A note (440 Hz down 3 octaves)
|
|
|
|
; Here "numerator" is 1000 * 2 ^ (halftone / 12)
|
|
macro _PutPeriod numerator, octave
|
|
dw ((((PLAYSTC_AY_FREQUENCY / 16 * numerator + 1000 / 2) / 1000 + NOTE_FREQ / 2) / NOTE_FREQ) >> octave) - 1
|
|
endm
|
|
|
|
NotePeriods:
|
|
i = 0
|
|
while i < 8
|
|
; Original, but hardcoded and inaccurate:
|
|
; dw 3832>>i, 3600>>i, 3424>>i, 3200>>i, 3032>>i, 2856>>i, 2696>>i, 2544>>i, 2400>>i, 2272>>i, 2136>>i, 2016>>i
|
|
; _PutPeriod 2000, i ; A
|
|
_PutPeriod 1888, i ; A#
|
|
_PutPeriod 1782, i ; B
|
|
_PutPeriod 1682, i ; C
|
|
_PutPeriod 1587, i ; C#
|
|
_PutPeriod 1498, i ; D
|
|
_PutPeriod 1414, i ; D#
|
|
_PutPeriod 1335, i ; E
|
|
_PutPeriod 1260, i ; F
|
|
_PutPeriod 1189, i ; F#
|
|
_PutPeriod 1122, i ; G
|
|
_PutPeriod 1059, i ; G#
|
|
_PutPeriod 1000, i ; A
|
|
; _PutPeriod 944, i ; A#
|
|
; _PutPeriod 891, i ; B
|
|
|
|
i = i + 1
|
|
endw
|
|
|
|
; Variables
|
|
|
|
p_rPositions: dw STCPositions + ST_Positions.rPositions
|
|
p_Ornaments: dw STCOrnaments
|
|
p_Patterns: dw STCPatterns
|
|
p_Samples: dw STCSamples
|
|
b_Delay: db STCDelay
|
|
b_DelayCtr: db 1 ; 1 = last tick
|
|
b_PositionsCnt: db STCPositionsCnt
|
|
p_ChannelA: dw STCChannelA
|
|
p_ChannelB: dw STCChannelB
|
|
p_ChannelC: dw STCChannelC
|
|
b_EmptyChannel: db $FF ; $FF = pattern end mark
|
|
|
|
struct _ST_r0
|
|
bSampleFlag db 0 ; 0=ornament, 1=envelope, 2=?
|
|
bNoteDelay db 0
|
|
ends
|
|
|
|
struct _ST_r1
|
|
bSamplePos db 0
|
|
bNote db 0
|
|
bDelayCtr db 0
|
|
pSampleData dw 0
|
|
pOrnamentData dw _STCOrnamentData
|
|
bRepeatLen db _STCRepeatLen ; =-1 if no loop
|
|
ends
|
|
|
|
struct _ST_Chn
|
|
r0 _ST_r0
|
|
r1 _ST_r1
|
|
ends
|
|
|
|
_PlayState: equ $
|
|
r_ChnA: _ST_Chn
|
|
r_ChnB: _ST_Chn
|
|
r_ChnC: _ST_Chn
|
|
b_CurPos: db 0 ; 0-31
|
|
; AY-3-891x registers
|
|
w_AYPitchA: dw 0 ; R0: Channel A fine pitch (0-255), R1: Channel A coarse pitch (0-15) - 0-4095
|
|
w_AYPitchB: dw 0 ; R2: Channel B fine pitch (0-255), R3: Channel B coarse pitch (0-15) - 0-4095
|
|
w_AYPitchC: dw 0 ; R4: Channel C fine pitch (0-255), R5: Channel C coarse pitch (0-15) - 0-4095
|
|
b_AYNoisePitch: db 0 ; R6: Noise pitch (0-31)
|
|
b_AYMixer: db 0 ; R7: Mixer [ - - N N N T T T ]
|
|
b_AYVolumeA: db 0 ; R8: Channel A volume (0-15) and mode flag (bit 4)
|
|
b_AYVolumeB: db 0 ; R9: Channel B volume (0-15) and mode flag (bit 4)
|
|
b_AYVolumeC: db 0 ; R10: Channel C volume (0-15) and mode flag (bit 4)
|
|
w_AYEnvPeriod: dw 0 ; R11: Envelope fine duration (0-255), R12: Envelope coarse duration (0-255)
|
|
b_AYEnvShape: db 0 ; R13: Envelope shape (0-15)
|
|
_PlayStateLen: equ $ - _PlayState
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: HL = pointer to STC file data
|
|
Init:
|
|
di ; HL = Header
|
|
ld (p_Header), hl
|
|
ld a, (hl) ; A = Header->bDelay
|
|
ld (b_Delay), a
|
|
inc hl ; HL = & Header->wPositionsOff
|
|
call ReadPtr ; HL = & Header->wOrnamentsOff
|
|
; DE = Positions
|
|
ld a, (de) ; A = Positions->bCount
|
|
inc de ; DE = & Positions->rPositions
|
|
inc a ; A = Positions->bCount + 1
|
|
ld (b_PositionsCnt), a
|
|
ld (p_rPositions), de
|
|
call ReadPtr ; HL = & Header->wPatternsOff
|
|
; DE = Ornaments
|
|
ld (p_Ornaments), de
|
|
push de ; push Ornaments
|
|
call ReadPtr ; HL = & Header->sIdentifier
|
|
; DE = Patterns
|
|
ld (p_Patterns), de
|
|
ld hl, ST_Header.rSamples
|
|
call GetDataPtr
|
|
ex de, hl ; HL = & Header->rSamples
|
|
; DE = Patterns
|
|
ld (p_Samples), hl
|
|
ld hl, b_EmptyChannel
|
|
ld (p_ChannelA), hl
|
|
ld hl, _PlayState
|
|
ld de, _PlayState + 1
|
|
ld bc, _PlayStateLen - 1 ; B = 0
|
|
ld (hl), b
|
|
ldir ; clear variables in _PlayState area
|
|
pop hl ; HL = Ornaments
|
|
ld bc, ST_Ornament
|
|
xor a ; A = 0
|
|
call FindItem ; HL = & Ornaments[.bNumber==0].bNumber
|
|
dec a ; A = -1
|
|
ld (r_ChnA.r1.bRepeatLen), a
|
|
ld (r_ChnB.r1.bRepeatLen), a
|
|
ld (r_ChnC.r1.bRepeatLen), a
|
|
ld a, 1 ; 1 = last tick
|
|
ld (b_DelayCtr), a
|
|
inc hl ; HL = & Ornaments[.bNumber==0].rData
|
|
ld (r_ChnA.r1.pOrnamentData), hl
|
|
ld (r_ChnB.r1.pOrnamentData), hl
|
|
ld (r_ChnC.r1.pOrnamentData), hl
|
|
call WriteToAY ; Reset AY
|
|
ei
|
|
ret
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: A = number
|
|
; HL = pointer to a byte
|
|
; BC = pointer's increment
|
|
; Out: HL = found pointer
|
|
FindItem:
|
|
cp (hl)
|
|
ret z
|
|
add hl, bc
|
|
jp FindItem
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: HL = pointer to a 16-bit offset in file
|
|
; Out: HL = initial HL + 2
|
|
; DE = pointer to data
|
|
ReadPtr:
|
|
ld e, (hl)
|
|
inc hl
|
|
ld d, (hl)
|
|
inc hl
|
|
ex de, hl ; HL = (HL)
|
|
; DE = initial HL + 2
|
|
; jp GetDataPtr ; no need, it follows
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: HL = relative offset in ST file
|
|
; Out: HL = initial DE value
|
|
; DE = pointer to data
|
|
GetDataPtr:
|
|
ld bc, STCHeader
|
|
p_Header: equ $ - 2
|
|
add hl, bc
|
|
ex de, hl
|
|
ret
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: A = position in sample (0-31)
|
|
; IX = pointer to sample's data
|
|
; Out: B = $02 * NoToneFlag
|
|
; C = $10 * NoNoiseFlag
|
|
; H = noise value (0-31)
|
|
; L = volume value (0-15)
|
|
; DE = tone shift value (0-$FFF) + $1000 * DoToneShiftDownFlag
|
|
ReadSample:
|
|
ld d, 0 ; D = 0
|
|
ld e, a ; DE = A = a
|
|
add a, a ; A = a*2
|
|
add a, e ; A = a*3 (0-93)
|
|
ld e, a ; DE = a*3
|
|
add ix, de ; IX = & Sample->rData[a]
|
|
; Byte 0: bits 7-4: Tone shift value (bits 11-8)
|
|
; bits 3-0: Volume (0-15)
|
|
; Byte 1: bit 7: Noise mask (0=noise on, 1=noise off)
|
|
; bit 6: Tone mask (0=tone on, 1=tone off)
|
|
; bit 5: Tone shift direction (0=minus to freq., 1=plus to freq.)
|
|
; bits 4-0: Noise value (0-31)
|
|
; Byte 2: Tone shift (LSB)
|
|
ld a, (ix + 1) ; A = Byte_1
|
|
bit 7, a ; Noise mask (0=noise on, 1=noise off)
|
|
ld c, $10 ; C = $10 (no noise flag)
|
|
jp nz, .LNoNoise
|
|
ld c, d ; C = 0 (noise enabled)
|
|
.LNoNoise:
|
|
bit 6, a ; Tone mask (0=tone on, 1=tone off)
|
|
ld b, $02 ; B = $02 (no tone flag)
|
|
jp nz, .LNoTone
|
|
ld b, d ; B = 0 (tone enabled)
|
|
.LNoTone:
|
|
and %00011111 ; Noise value (0-31)
|
|
ld h, a
|
|
ld e, (ix + 2) ; E = Tone shift (LSB)
|
|
ld a, (ix + 0) ; A = Byte_0
|
|
push af
|
|
and %11110000 ; A = Byte_0 & $F0
|
|
.4 rrca ; A = Tone shift value (MSB)
|
|
ld d, a ; DE = Tone shift value
|
|
pop af
|
|
and %00001111 ; A = Volume (0-15)
|
|
ld l, a ; L = Volume (0-15)
|
|
bit 5, (ix + 1) ; Tone shift direction (0=minus to freq., 1=plus to freq.)
|
|
ret z
|
|
set 4, d ; DE |= $1000 (Tone shift direction)
|
|
ret
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
NextPattern:
|
|
ld a, (b_CurPos)
|
|
ld c, a ; C = b_CurPos
|
|
ld hl, b_PositionsCnt
|
|
cp (hl)
|
|
jp c, .LOk ; if (b_CurPos >= b_PositionsCnt) restart
|
|
xor a ; position is not available, restart
|
|
ld c, a ; C = 0
|
|
.LOk: inc a
|
|
ld (b_CurPos), a
|
|
ld l, c
|
|
ld h, 0
|
|
add hl, hl ; HL = bOldPosition * ST_PositionRec
|
|
ld de, (p_rPositions)
|
|
add hl, de ; HL = p_rPositions[bOldPosition]
|
|
ld c, (hl) ; C = p_rPositions[bOldPosition].bPNum
|
|
inc hl
|
|
ld a, (hl) ; A = p_rPositions[bOldPosition].bTransposition
|
|
ld (CalcPitch.bTransposition), a
|
|
ld a, c ; A = p_rPositions[bOldPosition].bPNum
|
|
ld hl, (p_Patterns)
|
|
ld bc, ST_Pattern
|
|
call FindItem
|
|
inc hl ; HL = & p_Patterns[A].wChannelAOff
|
|
call ReadPtr
|
|
ld (p_ChannelA), de
|
|
call ReadPtr
|
|
ld (p_ChannelB), de
|
|
call ReadPtr
|
|
ld (p_ChannelC), de
|
|
ret
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: IX = pointer to _ST_Chn.r1 structure
|
|
; Out: Flag S=1 if counter was restarted
|
|
NextTick:
|
|
dec (ix + _ST_r1.bDelayCtr)
|
|
ret p
|
|
ld a, (ix - _ST_r0 + _ST_r0.bNoteDelay)
|
|
ld (ix + _ST_r1.bDelayCtr), a
|
|
ret
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
Play:
|
|
ld a, (b_DelayCtr)
|
|
dec a
|
|
ld (b_DelayCtr), a
|
|
jp nz, PlayChannels
|
|
ld a, (b_Delay)
|
|
ld (b_DelayCtr), a
|
|
ld ix, r_ChnA.r1
|
|
call NextTick
|
|
jp p, .LContinueB
|
|
ld hl, (p_ChannelA)
|
|
ld a, (hl) ; A = event
|
|
inc a
|
|
call z, NextPattern ; next pattern if pattern end mark $FF found
|
|
ld hl, (p_ChannelA)
|
|
call NextRow
|
|
ld (p_ChannelA), hl
|
|
.LContinueB:
|
|
ld ix, r_ChnB.r1
|
|
call NextTick
|
|
jp p, .LContinueC
|
|
ld hl, (p_ChannelB)
|
|
call NextRow
|
|
ld (p_ChannelB), hl
|
|
.LContinueC:
|
|
ld ix, r_ChnC.r1
|
|
call NextTick
|
|
jp p, PlayChannels
|
|
ld hl, (p_ChannelC)
|
|
call NextRow
|
|
ld (p_ChannelC), hl
|
|
jp PlayChannels
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: HL = pointer to pattern data
|
|
; IX = pointer to _ST_Chn.r1 structure
|
|
NextRow:
|
|
ld a, (hl) ; read event
|
|
cp $60
|
|
jp c, .LNote ; $00-$5F: Note number (0-95), end position
|
|
cp $70
|
|
jp c, .LSample ; $60-$6F: Sample number (0-15)
|
|
cp $80
|
|
jp c, .LOrnament ; $70-$7F: Ornament number (0-15)
|
|
jp z, .LPause ; $80: Pause, end position, channel off
|
|
cp $81
|
|
jp z, .LEmptyNote ; $81: Empty note, end position
|
|
cp $82
|
|
jp z, .LNoEnvAndOrn; $82: Disable envelope and ornament
|
|
cp $8F
|
|
jp c, .LEnvelope ; $83-$8E: Envelope number (3-14), read envelope's period LSB, disable ornament
|
|
; $8F-$A1: Nothing, never used in pattern
|
|
sub $A1 ; $A2-$FE: Delay (1-93)
|
|
ld (ix + _ST_r1.bDelayCtr), a
|
|
ld (ix - _ST_r0 + _ST_r0.bNoteDelay), a
|
|
inc hl
|
|
jp NextRow
|
|
|
|
.LNote:
|
|
; $00-$5F: Note number (0-95), end position
|
|
ld (ix + _ST_r1.bNote), a
|
|
ld (ix + _ST_r1.bSamplePos), 0
|
|
ld (ix + _ST_r1.bRepeatLen), ST_SampleLength
|
|
.LEmptyNote:
|
|
; $81: Empty note, end position
|
|
inc hl
|
|
ret
|
|
|
|
.LSample:
|
|
; $60-$6F: Sample number (0-15)
|
|
sub $60
|
|
push hl
|
|
ld bc, ST_Sample
|
|
ld hl, (p_Samples)
|
|
call FindItem
|
|
; HL = Samples[A]
|
|
inc hl ; HL = & Samples[A].rData
|
|
ld (ix + _ST_r1.pSampleData), l
|
|
ld (ix + _ST_r1.pSampleData + 1), h
|
|
pop hl
|
|
inc hl
|
|
jp NextRow
|
|
|
|
.LPause:
|
|
; $80: Pause, end position, channel off
|
|
inc hl
|
|
.LClrRepLen:
|
|
ld (ix + _ST_r1.bRepeatLen), -1
|
|
ret
|
|
|
|
.LNoEnvAndOrn:
|
|
; $82: Disable envelope and ornament
|
|
xor a
|
|
jr .LSet
|
|
|
|
.LOrnament:
|
|
; $70-$7F: Ornament number (0-15)
|
|
sub $70
|
|
.LSet: push hl
|
|
ld bc, ST_Ornament
|
|
ld hl, (p_Ornaments)
|
|
call FindItem
|
|
inc hl
|
|
ld (ix + _ST_r1.pOrnamentData), l
|
|
ld (ix + _ST_r1.pOrnamentData + 1), h
|
|
ld (ix - _ST_r0 + _ST_r0.bSampleFlag), 0
|
|
pop hl
|
|
inc hl
|
|
jp NextRow
|
|
|
|
.LEnvelope:
|
|
; $83-$8E: Envelope number (3-14), read envelope's period LSB, disable ornament
|
|
sub $80
|
|
ld (b_AYEnvShape), a
|
|
inc hl
|
|
ld a, (hl) ; A = envelope's period LSB
|
|
inc hl
|
|
ld (w_AYEnvPeriod), a
|
|
ld (ix - _ST_r0 + _ST_r0.bSampleFlag), 1
|
|
push hl
|
|
xor a
|
|
ld bc, ST_Ornament
|
|
ld hl, (p_Ornaments)
|
|
call FindItem
|
|
inc hl
|
|
ld (ix + _ST_r1.pOrnamentData), l
|
|
ld (ix + _ST_r1.pOrnamentData + 1), h
|
|
pop hl
|
|
jp NextRow
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: IX = pointer to _ST_Chn.r1 structure
|
|
; Out: C = sample position
|
|
SetupSample:
|
|
ld a, (ix + _ST_r1.bRepeatLen)
|
|
inc a
|
|
ret z ; if (r1->bRepeatLen == -1) return
|
|
.2 dec a ; A = r1->bRepeatLen - 1
|
|
ld (ix + _ST_r1.bRepeatLen), a
|
|
; r1->bRepeatLen = r1->bRepeatLen - 1
|
|
push af
|
|
ld a, (ix + _ST_r1.bSamplePos)
|
|
ld c, a ; C = r1->bSamplePos
|
|
inc a
|
|
and ST_SamplePosMask
|
|
ld (ix + _ST_r1.bSamplePos), a
|
|
; r1->bSamplePos = (r1->bSamplePos + 1) & ST_SamplePosMask
|
|
pop af ; A = r1->bRepeatLen - 1
|
|
ret nz ; if (r1->bRepeatLen != 1) return
|
|
ld e, (ix + _ST_r1.pSampleData)
|
|
ld d, (ix + _ST_r1.pSampleData + 1)
|
|
ld hl, ST_Sample.bRepeatPosition - ST_Sample.rData
|
|
add hl, de ; HL = & Sample->bRepeatPosition
|
|
ld a, (hl)
|
|
dec a ; A = Sample->bRepeatPosition - 1
|
|
jp m, NextRow.LClrRepLen ; if (Sample->bRepeatPosition <= 0) reset
|
|
ld c, a ; C = Sample->bRepeatPosition - 1
|
|
inc a
|
|
and ST_SamplePosMask
|
|
ld (ix + _ST_r1.bSamplePos), a
|
|
; r1->bSamplePos = Sample->bRepeatPosition & ST_SamplePosMask
|
|
inc hl ; HL = & Sample->bRepeatLength
|
|
ld a, (hl)
|
|
inc a
|
|
ld (ix + _ST_r1.bRepeatLen), a
|
|
; r1->bRepeatLen = Sample->bRepeatLength + 1
|
|
ret
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: C = 0 for noise (no noise otherwise)
|
|
; H = noise pitch (0-31)
|
|
SetupNoise:
|
|
ld a, c
|
|
or a
|
|
ret nz
|
|
ld a, h
|
|
ld (b_AYNoisePitch), a
|
|
ret
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: IX = pointer to _ST_Chn.r1 structure
|
|
; HL = pointer to AY channel volume register's data (R8/R9/R10)
|
|
SetupEnvShape:
|
|
ld a, (ix + _ST_r1.bRepeatLen)
|
|
inc a
|
|
ret z ; if (r1->bRepeatLen == -1) return
|
|
ld a, (ix - _ST_r0 + _ST_r0.bSampleFlag)
|
|
or a
|
|
ret z ; if (r0->bSampleFlag == 0) return
|
|
cp 2
|
|
jp z, .LReset ; if (r0->bSampleFlag == 2) b_AYEnvShape = 0
|
|
ld (ix - _ST_r0 + _ST_r0.bSampleFlag), 2
|
|
jp .L2 ; if (r0->bSampleFlag == 1) r0->bSampleFlag = 2
|
|
.LReset:xor a
|
|
ld (b_AYEnvShape), a
|
|
.L2: set 4, (hl) ; volume |= $10 (do not use volume: use envelope)
|
|
ret
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
; In: IX = pointer to _ST_Chn.r1 structure
|
|
; L = volume value (0-15)
|
|
; DE = tone shift value (0-$FFF) + $1000 * DoToneShiftDownFlag
|
|
; Out: A = volume (0-15)
|
|
; HL = pitch
|
|
CalcPitch:
|
|
ld a, l
|
|
push af
|
|
push de
|
|
ld l, (ix + _ST_r1.pOrnamentData)
|
|
ld h, (ix + _ST_r1.pOrnamentData + 1)
|
|
ld de, 0
|
|
.wCurPos: equ $ - 2
|
|
add hl, de ; HL = & OrnamentData[wCurPos]
|
|
ld a, (ix + _ST_r1.bNote)
|
|
add a, (hl)
|
|
add a, 0 ; A = r1->bNote + OrnamentData[wCurPos] + bTransposition
|
|
.bTransposition: equ $ - 1
|
|
add a, a ; A = (r1->bNote + OrnamentData[wCurPos] + bTransposition) * 2
|
|
ld e, a
|
|
ld d, 0
|
|
ld hl, NotePeriods
|
|
add hl, de ; HL = & NotePeriods[r1->bNote + OrnamentData[wCurPos] + bTransposition]
|
|
ld e, (hl)
|
|
inc hl
|
|
ld d, (hl)
|
|
ex de, hl ; HL = NotePeriods[r1->bNote + OrnamentData[wCurPos] + bTransposition]
|
|
pop de ; DE = tone shift value (0-$FFF) + $1000 * DoToneShiftDownFlag
|
|
pop af ; A = volume value (0-15)
|
|
bit 4, d
|
|
jr z, .LShiftUp
|
|
res 4, d
|
|
add hl, de
|
|
ret
|
|
.LShiftUp:
|
|
and a
|
|
sbc hl, de
|
|
ret
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
PlayChannels:
|
|
; Play channel A
|
|
ld ix, r_ChnA.r1
|
|
call SetupSample
|
|
ld a, c
|
|
ld (CalcPitch.wCurPos), a
|
|
ld ix, (r_ChnA.r1.pSampleData)
|
|
call ReadSample
|
|
ld a, c
|
|
or b
|
|
rrca ; A = $08 * NoNoiseFlag | $01 * NoToneFlag
|
|
ld (b_AYMixer), a
|
|
ld ix, r_ChnA.r1
|
|
ld a, (ix + _ST_r1.bRepeatLen)
|
|
inc a
|
|
jp z, .L1 ; if (r1->bRepeatLen == -1) skip
|
|
call SetupNoise
|
|
call CalcPitch
|
|
ld (w_AYPitchA), hl
|
|
.L1: ld hl, b_AYVolumeA
|
|
ld (hl), a
|
|
call SetupEnvShape
|
|
; Play channel B
|
|
ld ix, r_ChnB.r1
|
|
call SetupSample
|
|
ld a, (ix + _ST_r1.bRepeatLen)
|
|
inc a
|
|
jp z, .L2 ; if (r1->bRepeatLen == -1) skip
|
|
ld a, c
|
|
ld (CalcPitch.wCurPos), a
|
|
ld ix, (r_ChnB.r1.pSampleData)
|
|
call ReadSample
|
|
ld a, (b_AYMixer)
|
|
or c
|
|
or b ; A |= $10 * NoNoiseFlag | $02 * NoToneFlag
|
|
ld (b_AYMixer), a
|
|
call SetupNoise
|
|
ld ix, r_ChnB.r1
|
|
call CalcPitch
|
|
ld (w_AYPitchB), hl
|
|
.L2: ld hl, b_AYVolumeB
|
|
ld (hl), a
|
|
call SetupEnvShape
|
|
; Play channel C
|
|
ld ix, r_ChnC.r1
|
|
call SetupSample
|
|
ld a, (ix + _ST_r1.bRepeatLen)
|
|
inc a
|
|
jp z, .L3 ; if (r1->bRepeatLen == -1) skip
|
|
ld a, c
|
|
ld (CalcPitch.wCurPos), a
|
|
ld ix, (r_ChnC.r1.pSampleData)
|
|
call ReadSample
|
|
ld a, (b_AYMixer)
|
|
rlc c
|
|
rlc b
|
|
or b
|
|
or c ; A |= $20 * NoNoiseFlag | $04 * NoToneFlag
|
|
ld (b_AYMixer), a
|
|
call SetupNoise
|
|
ld ix, r_ChnC.r1
|
|
call CalcPitch
|
|
ld (w_AYPitchC), hl
|
|
.L3: ld hl, b_AYVolumeC
|
|
ld (hl), a
|
|
call SetupEnvShape
|
|
; Output sound
|
|
; jp WriteToAY ; no need, it follows
|
|
|
|
;-----------------------------------------------------------------------------
|
|
; Subroutine
|
|
WriteToAY:
|
|
ld hl, b_AYEnvShape; start from register 13
|
|
xor a
|
|
or (hl)
|
|
ld a, 13 ; register number
|
|
jr nz, .LNot0 ; value == 0 ?
|
|
sub 3 ; skip values and start from register 10
|
|
.3 dec hl
|
|
.LNot0: ld c, ay_ctrl_port & $FF
|
|
.LLoop: ld b, ay_ctrl_port >> 8
|
|
out (c), a
|
|
ld b, ay_data_port >> 8 ; LSB address is the same
|
|
outd
|
|
dec a
|
|
jp p, .LLoop
|
|
ret
|
|
|
|
endmodule
|