diff --git a/sdk/include/ay.def b/sdk/include/ay.def new file mode 100644 index 0000000..70f078f --- /dev/null +++ b/sdk/include/ay.def @@ -0,0 +1,20 @@ +; ay.def - definitions for AY-3-8910/8912/8913 and compatible PSG. +; +; SPDX-FileCopyrightText: 2021 Ivan Tatarinov +; +; SPDX-License-Identifier: Unlicense + +; Compatible compilers: +; SJAsmPlus, + + ifndef ay_def_included + define ay_def_included + +ay_freq_Spectrum: equ 1773400 +ay_freq_Pentagon: equ 1750000 +ay_freq_AmstradCPC: equ 1000000 + +ay_ctrl_port: equ $fffd +ay_data_port: equ $bffd + + endif ; !ay_def_included diff --git a/sdk/include/filestc.def b/sdk/include/filestc.def new file mode 100644 index 0000000..1e6958e --- /dev/null +++ b/sdk/include/filestc.def @@ -0,0 +1,62 @@ +; filestc.def - Sound Tracker .STC compiled file structure. +; +; Authors: +; 2021 Ivan Tatarinov +; +; This is free and unencumbered software released into the public domain. +; For more information, please refer to . +; +; SPDX-FileCopyrightText: 2021 Ivan Tatarinov +; +; SPDX-License-Identifier: Unlicense + +; Compatible compilers: +; SJAsmPlus, + + ifndef filestc_def_included + define filestc_def_included + + struct ST_Header ; 27 bytes +bDelay ds 1 +wPositionsOff ds 2 +wOrnamentsOff ds 2 +wPatternsOff ds 2 +sIdentifier ds 18 ; "SONG BY ST COMPILE" by default +wSize ds 2 ; not reliable +rSamples ds 0 ; ST_Sample * SamplesCnt +; Data ds ? + ends + + struct ST_Positions +bCount ds 1 +rPositions ds 0 ; ST_PositionRec * bCount + ends + + struct ST_PositionRec +bPNum ds 1 +bTransposition ds 1 + ends + +ST_SampleLength: equ 32 ; power of 2 ! +ST_SamplePosMask: equ (ST_SampleLength - 1) + + struct ST_Sample ; 99 bytes +bNumber ds 1 +rData ds ST_SampleLength * 3 +bRepeatPosition ds 1 +bRepeatLength ds 1 + ends + + struct ST_Ornament ; 33 bytes +bNumber ds 1 +rData ds ST_SampleLength + ends + + struct ST_Pattern ; 7 bytes +bNumber ds 1 +wChannelAOff ds 2 +wChannelBOff ds 2 +wChannelCOff ds 2 + ends + + endif ; !filestc_def_included \ No newline at end of file diff --git a/sdk/include/playstc.inc b/sdk/include/playstc.inc new file mode 100644 index 0000000..b803e9a --- /dev/null +++ b/sdk/include/playstc.inc @@ -0,0 +1,660 @@ +; 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 +; +; This is free and unencumbered software released into the public domain. +; For more information, please refer to . +; +; SPDX-FileCopyrightText: 2021 Ivan Tatarinov +; +; SPDX-FileContributor: 2016 Michal B. (a.k.a. Yerzmyey) (binary file) +; +; SPDX-License-Identifier: Unlicense + +; Compatible compilers: +; 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