.include "src/includes/sound_defines.asm"
.include "src/includes/sound_macros.asm"

.BANK 2 SLOT 2
.ORG $0000
Bank2:

; =============================================================================
; Sound_Update()
; -----------------------------------------------------------------------------
;  Main sound driver entrypoint. Called once per frame (vblank).
; -----------------------------------------------------------------------------
;  In:
;    None.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_Update:       ; $8000
    ; check the reset trigger and intialise the PSG
    ; if required
    ld    hl, Sound_ResetTrg
    ld    a, (hl)
    or    a
    jr    z, +
    
    ; if MSB is reset the sound driver is disabled
    ret   p

    dec   (hl)
    jp    Sound_InitPSG

+:  ; read music trigger and copy to "current sound"
    call  Sound_CheckSoundTriggers
    
    ; adjust channels to the correct tempo
    call  Sound_AdjustForSpeed
    
    ; check to see if the current music/sfx needs
    ; to be faded out
    call  Sound_FadeOut
    
    ; check the trigger byte. if it has changed we
    ; need to load a new music/sfx module
    call  Sound_CheckLoadMusic
    
    ld    a, ($DE91)
    or    a
    jp    z, Sound_UpdateChannels
    
    ld    a, ($DE92)
    inc   a
    cp    $05
    jr    z, LABEL_802D_123
    ld    ($DE92), a
    
    jp    Sound_UpdateChannels

LABEL_802D_123:
    xor   a
    ld    ($DE92), a
    call  Sound_UpdateChannels
    call  Sound_UpdateChannels
    ret


; =============================================================================
; Sound_UpdateChannels()
; -----------------------------------------------------------------------------
;  Updates each active channel structure.
; -----------------------------------------------------------------------------
;  In:
;    None.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_UpdateChannels:       ; $8038
    ld    ix, Sound_Channel_Music_0
    Sound_IsChannelActive
    call  nz, Sound_UpdateToneChannel
    
    ld    ix, Sound_Channel_Music_1
    Sound_IsChannelActive
    call  nz, Sound_UpdateToneChannel
    
    ld    ix, Sound_Channel_Music_2
    Sound_IsChannelActive
    call  nz, Sound_UpdateToneChannel
    
    ld    ix, Sound_Channel_Music_Noise
    Sound_IsChannelActive
    call  nz, Sound_UpdateNoiseChannel
    
    ld    ix, Sound_Channel_SFX_0
    Sound_IsChannelActive
    call  nz, Sound_UpdateToneChannel
    
    ld    ix, Sound_Channel_SFX_1
    Sound_IsChannelActive
    call  nz, Sound_UpdateToneChannel
    
    ld    ix, Sound_Channel_SFX_2
    Sound_IsChannelActive
    call  nz, Sound_UpdateToneChannel
    
    ret


; =============================================================================
;  Sound_AdjustForSpeed()
; -----------------------------------------------------------------------------
;  Inserts extra ticks into each channel's note duration counter so that
;  the module plays at the correct tempo.
; -----------------------------------------------------------------------------
;  In:
;    None.
;  Out:
;    None.
;  Destroys:
;    A, B, DE, HL
; -----------------------------------------------------------------------------
Sound_AdjustForSpeed:       ; $8086
    ld    hl, Sound_SpeedCounter        ;decrement counter value
    
    ; return if the speed counter is 0
    ld    a, (hl)
    or    a
    ret   z
    
    ; decrement the counter
    dec   (hl)
    ret   nz

    ; if we get here, the counter is zero. 
    ; reset the counter and insert an extra tick into each channel
    
    ; restore counter value
    ld    a, (Sound_Speed)
    ld    (hl), a
    
    ; add an extra tick to each channel
    ld    hl, Sound_Channel_Music_0 + Sound_ChnlToneDuration
    ld    de, Sound_ChannelSize        ;for the 4 channels
    ld    b, 4
    
-:  inc   (hl)
    add   hl, de
    djnz  -

    ret


; =============================================================================
; Sound_CheckSoundTriggers
; -----------------------------------------------------------------------------
;  Checks each of the 3 trigger bytes for new module numbers and, if the
;  priority value allows, copies the module index into the main trigger byte
;  at $DD03.
; -----------------------------------------------------------------------------
;  In:
;    None.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_CheckSoundTriggers:        ; $809F
    ; load a pointer to the sound triggers
    ld    de, $DD04
    ; pointer to the current sound's priority
    ld    ix, Sound_Priority
    ; pointer to current sound number
    ld    iy, $DD03
    
    ; check each trigger
    call  Sound_CheckSoundTriggers_CheckTrigger
    call  Sound_CheckSoundTriggers_CheckTrigger
Sound_CheckSoundTriggers_CheckTrigger:
    ; fetch the trigger value
    ld    a, (de)
    and   $7F               ;ignore MSB
    
    ; jump if the trigger is 0 (no new module)
    jr    z, +
    
    ; fetch a priority value for the module number
    dec   a
    ld    hl, Sound_Data_Priorities
    ld    c, a
    ld    b, $00
    add   hl, bc
    ld    a, (hl)

    ; if the current sound's priority is greater than the
    ; new sound's don't bother changing anything...
    cp    (ix + 0)
    jr    c, +            ;jump if $DD0F < A

    ; ...otherwise, set the new priority...
    and   $7F
    ld    (ix + 0), a        ;DD0F
    
    ; ... and copy module number to the main trigger
    ld    a, (de)
    ld    (iy + 0), a        ;DD03

+:  ; reset the trigger
    xor   a
    ld    (de), a
    ; move to the next trigger byte
    inc   de
    
    ret


; =============================================================================
; Sound_FadeOut()
; -----------------------------------------------------------------------------
;  Fades the volume of the primary tone channels by increasing attenuation.
;  If any sound effect channels are active they will be disabled.
; -----------------------------------------------------------------------------
;  In:
;    None.
;  Out:
;    None.
;  Destroys:
;    A, BC, DE, HL
; -----------------------------------------------------------------------------
Sound_FadeOut:           ; $80D0
    ; check to see if the volume fadeout counter
    ; has been set
    ld    a, (Sound_FadeMajorCount)
    or    a
    ret   z

    ; check that none of the sfx channels are active.
    ; if they are they should be disabled and the correct
    ; primary channel reactivated.
    
    ; load HL with a pointer to the first SFX channel
    ld    hl, $DE00
    
    ; loop 3 times
    ld    b, $03
    
    ; this will be used to move the pointer to the next channel
    ld    de, $0030

-:  ; check to see if the channel is active
    bit   Sound_ChannelActiveBit, (hl)
    jp    z, Sound_FadeOut_LoopNext

    push  hl
    
    ; check the PSG channel number
    inc   hl
    ld    a, (hl)
    
    ; check for tone 1 channel
    cp    $A0
    ; jump if channel != tone 1
    jp    nz, +

    ; chear the primary tone 1 channel's suppress bit
    ld    hl, $DD70
    res   Sound_ChannelSuppressBit, (hl)
    jp    Sound_FadeOut_DisableSFXChannel

    
+:  ; check for the noise channel
    cp    $E0
    ; jump if channel != noise
    jp    nz, ++

    ; clear the noise channel's suppress bit
    ld    hl, $DDD0
    res   Sound_ChannelSuppressBit, (hl)


++: ; clear the primary tone 2 channel's suppress bit
    ld    hl, $DDA0
    res   Sound_ChannelSuppressBit, (hl)


Sound_FadeOut_DisableSFXChannel:            ; $8101
    pop   hl
    ; clear channel control byte
    xor   a
    ld    (hl), a

Sound_FadeOut_LoopNext:
    add   hl, de
    djnz  -

    ; decrement the fadeout minor count
    ld    a, (Sound_FadeMinorCount)
    dec   a
    ; if the minor count value is 0 we need to
    ; reset it and decrement the major count
    jr    z, +
    ld    (Sound_FadeMinorCount), a
    
    ret


+:  ; reset the minor count value
    ld    a, (Sound_FadeMinorReset)
    ld    (Sound_FadeMinorCount), a
   
    ; decrement the major countdown value
    ld    a, (Sound_FadeMajorCount)
    dec   a
    ld    (Sound_FadeMajorCount), a
    ; reset the psg when the counter == 0
    jp    z, Sound_ResetAll
    
    ; load a pointer to the first channel's volume value
    ld    hl, Sound_Channel_Music_0 + Sound_ChnlVolumeAdjust
    ; DE will be added to HL to move the pointer to the next channel
    ld    de, Sound_ChannelSize
    ; loop 3 times (3 tone channels)
    ld    b, 3
    
-:  ; check and attenuate as necessary
    call  Sound_FadeOut_IncreaseAttenuation
    add   hl, de
    djnz  -

    ; adjust the noise channel volume
    ld    hl, Sound_NoiseChnlVolume

Sound_FadeOut_IncreaseAttenuation:      ; $8132
    ; if attenuation < $0C then increase
    ld    a, (hl)
    inc   a
    ; return if value >= $0C
    cp    $0C
    ret   nc
    
    ; store attenuation value
    ld    (hl), a
    
    ret


; =============================================================================
; Sound_CheckLoadMusic()
; -----------------------------------------------------------------------------
;  Checks the main trigger at $DD03 for a value > $80. If it finds such a
;  value it loads the relevant music/sfx data.
; -----------------------------------------------------------------------------
;  In:
;    ($DD03) - Music/SFX trigger
;  Out:
;    None.
;------------------------------------------------------------------------------
Sound_CheckLoadMusic:       ; $8139
    ; load "current music" number into A
    ld    a, ($DD03)
    ; if value < $80 reset the channels
    bit   7, a
    jp    z, Sound_ResetAll
    
    ; check to see if value corresponds to music slot
    ; (i.e. jump if A < $9A)
    cp    SFX_FirstSlot
    jr    c, Sound_LoadMusicModule
    
    ; check to see if value corresponds to SFX slot
    ; (i.e. jump if A is between $9A and $C0)
    cp    $C0
    jp    c, Sound_LoadSFXModule
    
    ; if value >= $C4 then reset the PSG
    cp    $C4
    jp    nc, Sound_ResetAll

    ; value is between $C0 and $C3. use as an index
    ; into the jump table below
    sub   $C0
    ld    hl, DATA_8158
    call  Sound_CalcIndex_Int16
    jp    (hl)


DATA_8158:
.dw LABEL_8160
.dw Sound_ResetAll
.dw Sound_StopSFX
.dw $7000

LABEL_8160:
    ; fade out over 216 ticks
    ld    a, $0C
    ld    (Sound_FadeMajorCount), a
    ld    a, $12
    ld    (Sound_FadeMinorCount), a
    ld    (Sound_FadeMinorReset), a
    jp    Sound_SetActive


; =============================================================================
; Sound_StopSFX()
; -----------------------------------------------------------------------------
;  Causes each sound effect tone channel to execute a "stop" command with the
;  next tick.
; -----------------------------------------------------------------------------
;  In:
;    None.
;  Out:
;    None.
;  Destroys:
;    B, DE, HL, IY
; -----------------------------------------------------------------------------
Sound_StopSFX:      ; $8170
    ; load IY with a pointer to the first SFX channel
    ld    iy, Sound_Channel_SFX_0
    ; this will be added to IY with each iteration of the loop
    ; to move to the next channel structure
    ld    de, Sound_ChannelSize
    ; loop 3 times
    ld    b, 3
    
    ; this will be stored as the next sound command for the channel
    ld    hl, _Sound_StopSFX_command

-:  ; store the "next command" pointer
    ld    (iy + Sound_ChnlNextCommand), l
    ld    (iy + Sound_ChnlNextCommand + 1), h
    
    ; move to the next channel structure
    add   iy, de
    djnz  -

    ret

_Sound_StopSFX_command
    Stop


; =============================================================================
; Sound_LoadMusicModule(uint8 module_index)
; -----------------------------------------------------------------------------
;  Loads music module header data into the channel structures.
; -----------------------------------------------------------------------------
;  In:
;    A  - Module number.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_LoadMusicModule:        ; $8188
    ; First music slot is $81. Subtract $81 from the value 
    ; passed in A to get a zero-based index
    sub   Music_FirstSlot
    ; return if < $81
    ret   m
    
    ; reset all channel structures
    ex    af, af'
        call  Sound_ResetAll
    ex    af, af'
    
    
    ; fetch the tempo/speed value for the module
    ; FIXME: this is completely pointless as it is overwritten
    ;   further on in this subroutine by the value stored in 
    ;   the module header
    ld    hl, Sound_Data_MusicSpeeds
    
    ; put the module number into C for the call to 
    ; Sound_CalcIndex_Int8
    ld    c, a
    
    ex    af, af'
        call  Sound_CalcIndex_Int8
        ld    (Sound_SpeedCounter), a
        ld    (Sound_Speed), a
    ex    af, af'
    
    
    ; fetch a pointer to the music data
    ld    hl, Sound_Data_MusicPointers
    ; calculate the address of the module data
    call  Sound_CalcIndex_Int16
    
    ; skip past the volume envelope pointer
    inc   hl
    inc   hl
    
    ; get number of channels
    ld    b, (hl)

    ; skip the next byte
    inc   hl
    
    ; copy the channel's tempo modifier into the shadow accumulator
    inc   hl
    ld    a, (hl)
    ex    af, af'
    
    
    ; read the module's global speed modifier (note: this overwrites
    ; the value written earlier).
    inc   hl
    ld    a, (hl)
    ld    (Sound_SpeedCounter), a
    ld    (Sound_Speed), a


    ; load IY with a pointer to the sound channel numbers
    ; for each structure.
    ld    iy, Sound_Data_ChannelNumbers

    inc   hl

    ; initialise required number of channels
    ; starting with structure at $DD40
    ld    de, Sound_Channel_Music_0
-:  call  Sound_InitChannel
    djnz  -


; =============================================================================
; Sound_SetActive()
; -----------------------------------------------------------------------------
;  Marks the main trigger byte ($DD03) as active.
; -----------------------------------------------------------------------------
;  In:
;    None.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_SetActive:        ; $81C1
    ld    a, $80        
    ld    ($DD03), a
    ret


; =============================================================================
; Sound_LoadSFXModule(uint8 module_index)
; -----------------------------------------------------------------------------
;  Loads sound effect module header data into the channel structures. Will 
;  activate a secondary channel and suppress a primary channel.
; -----------------------------------------------------------------------------
;  In:
;    A  - Module number.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_LoadSFXModule:        ; $81C7
    ; store the sound effect value here
    ld    (Sound_SFXIndex), a
    
    ; subtract the index of the first SFX slot to
    ; get a zero-based offset value
    sub   SFX_FirstSlot
    
    ; calculate an index into the array at $8C0D
    ld    hl, Sound_Data_SFXPointers
    call  Sound_CalcIndex_Int16
    
    ; skip past the volume envelope pointer
    inc   hl
    inc   hl
    
    ld    a, (hl)
    inc   hl
    ex    af, af'
    
    ; fetch number of channels
    ld    b, (hl)
    inc   hl

    ; loop over each channel
-:  inc   hl
    ld    a, (hl)
    dec   hl

    ; determine which primary channel to suppress
    ; and which secondary channel to activate
    cp    Sound_PSG_Latch | Sound_PSG_Tone1
    jr    z, Sound_LoadSFXModule_PriT1_SecT0
    
    cp    Sound_PSG_Latch | Sound_PSG_Tone2
    jr    z, LABEL_81ED_114
    
    ; if we get here A == $E0  (i.e. noise channel)
    ld    de, Sound_Channel_SFX_2
    ld    iy, Sound_Channel_Music_Noise
    jr    LABEL_820F_115

LABEL_81ED_114:
    ld    iy, Sound_Channel_SFX_2
    bit   6, (iy + Sound_ChnlControl)
    jr    nz, Sound_LoadSFXModule_PriT2_SecT1

    set   Sound_ChannelSuppressBit, (iy + Sound_ChnlControl)
    ld    a, $FF            ;noise volume = off
    out   (Ports_PSG), a

Sound_LoadSFXModule_PriT2_SecT1:      ; $81FF
    ld    de, Sound_Channel_SFX_1
    ld    iy, Sound_Channel_Music_2
    jr    LABEL_820F_115

Sound_LoadSFXModule_PriT1_SecT0:      ; $8208
    ld    de, Sound_Channel_SFX_0
    ld    iy, Sound_Channel_Music_1

LABEL_820F_115:
    call  Sound_LoadSFXModule_InitChannel
    djnz  -

    jp    Sound_SetActive


Sound_LoadSFXModule_InitChannel:      ; $8217
    ; suppress psg writes for the primary channel
    set   Sound_ChannelSuppressBit, (iy + Sound_ChnlControl)
    
    ; initialise the secondary channel structure
    ld    c, Sound_ChannelSize + 6
    push  de
    pop   ix
    ldi
    ldi
    
    ex    af, af'
    ld    (de), a
    inc   de
    ex    af, af'
    
    xor   a
    ldi
    ldi
    ldi
    ldi
    
    ld    (de), a
    inc   de
    ld    (de), a
    inc   de
    
    ld    a, c
    ld    (de), a
    inc   de
    
    ; reset the channel's 3 loop counters
    xor   a
    ld    (ix + Sound_ChnlLoopCounter0), a
    ld    (ix + Sound_ChnlLoopCounter1), a
    ld    (ix + Sound_ChnlLoopCounter2), a
    
    inc   a
    ld    (de), a
    push  hl

    ld    hl, Sound_ChannelSize - $0A
    add   hl, de
    
    ex    de, hl
    pop   hl

    ret


; =============================================================================
; Sound_InitChannel(uint8 tempo, uint16 channel_ptr, uint16 source)
; -----------------------------------------------------------------------------
;  Initialise a channel structure with data from a module's channel header.
; -----------------------------------------------------------------------------
;  In:
;    A'  - Tempo
;    DE  - Pointer to channel structure.
;    HL  - Pointer to source header data.
;  Out:
;    DE  - Pointer to next channel structure (e.g. if DE was $DD40 on entry
;          it will be $DD70 on exit).
;    HL  - Pointer to data immediately after channel init data.
;  Destroys:
;    A, BC, IX, A'
; -----------------------------------------------------------------------------
Sound_InitChannel:        ;$824C
    ; load C with $34. C will be used to set the initial
    ; stack pointer for the channel. The stack pointer will 
    ; start at $30. 4 LDIs will decrement C to $30 before
    ; we set the stack pointer at (ix + 9)
    ld    c, Sound_ChannelSize + 4
    push  de
    pop   ix
    
    ; mark the channel as active
    ld    a, Sound_ChannelActive
    ld    (de), a
    inc   de
    
    ; fetch the PSG channel number and copy it into
    ; the structure
    ld    a, (iy + 0)
    ld    (de), a
    inc   de
    inc   iy

    ; fetch the tempo value from the shadow accumulator
    ex    af, af'
    ld    (de), a
    inc   de
    ex    af, af'

    ; copy 4 bytes into the channel starting at +$03 bytes
    ; (e.g. $DD43)
    xor   a
    ldi         ; first 2 bytes are command data pointer
    ldi
    ldi         ; next byte is tone adjustment
    ldi         ; next byte is volume attenuation
    
    ; reset tone effect flags
    ld    (de), a
    inc   de
    ; reset volume effect flags
    ld    (de), a
    inc   de

    ; set the default stack pointer for the channel
    ld    a, c
    ld    (de), a
    inc   de

    ; reset the channel's 3 loop counters
    xor   a
    ld    (ix + Sound_ChnlLoopCounter0), a
    ld    (ix + Sound_ChnlLoopCounter1), a
    ld    (ix + Sound_ChnlLoopCounter2), a
    
    ; set the tone duration counter to 1
    inc   a
    ld    (de), a

    ; move DE to the next channel structure
    push  hl
    ld    hl, Sound_ChannelSize - $0A
    add   hl, de
    ex    de, hl
    pop   hl
    
    ret

; these bytes are copied into the "channel number" byte
; of each channel structure (e.g. $DD41)
Sound_Data_ChannelNumbers:        ;$8284
.db Sound_PSG_Latch | Sound_PSG_Tone0
.db Sound_PSG_Latch | Sound_PSG_Tone1
.db Sound_PSG_Latch | Sound_PSG_Tone2
.db Sound_PSG_Latch | Sound_PSG_Noise


; =============================================================================
; Sound_LoadPitchBendData(uint16 channel_ptr)
; -----------------------------------------------------------------------------
;  Copies parameter values required by the pitch bend effect into the 
;  channel structure.
; -----------------------------------------------------------------------------
;  In:
;    IX  - Pointer to channel structure.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_LoadPitchBendData:        ; $8288
    ; return if the pitch bend bit is reset
    bit   Sound_ChnlPitchBendBit, (ix + Sound_ToneEffectFlags)
    ret   z

    ; bail out if we need to preserve effect state
    bit   Sound_ChnlKeepStateBit, (ix + Sound_ChnlControl)
    ret   nz

    ; load the pointer at (ix + $10) into DE
    ld    e, (ix + Sound_PitchBendDataPtr)
    ld    d, (ix + Sound_PitchBendDataPtr + 1)
    
    ; load the channel pointer into HL
    push  ix
    pop   hl
    
    ; adjust HL to IX+$14
    ld    b, $00
    ld    c, Sound_PitchBendCountdown & $FF
    add   hl, bc
    
    ; copy 3 bytes from the data pointer at (IX+$10)
    ; into the channel structure
    ex    de, hl
    ldi         ; pitch bend countdown
    ldi         ; pitch bend length
    ldi         ; pitch bend step size
    
    ; read the repeat count
    ld    a, (hl)
    ; divide by 2
    srl   a
    ; ...and store in the channel structure at (ix + $17)
    ld    (de), a
    
    ;clear the tone adjustment value
    xor   a
    ld    (ix + Sound_ChnlToneAdjustment), a
    ld    (ix + Sound_ChnlToneAdjustment + 1), a
    
    ret


LABEL_82B3_142:
    bit   Sound_VolPreserveBit, (ix + Sound_VolumeEffectFlags)
    ret   z

    bit   Sound_ChnlKeepStateBit, (ix + Sound_ChnlControl)
    ret   nz

    ; dont update if the "suppress updates" bit is set
    bit   7, (ix + $1D)
    ret   nz

    ; set volume attenuation to max
    ld    a, $FF
    ld    (ix + Sound_ChnlVolume), a
    and   $10
    or    (ix + $1E)
    ld    (ix + $1D), a
    ret


; =============================================================================
; Sound_ApplyToneEffect(uint16 channel_ptr)
; -----------------------------------------------------------------------------
;  Determins whether a tone effect is in place and adjusts the tone data
;  accordingly.
; -----------------------------------------------------------------------------
;  In:
;    IX  - Pointer to channel structure.
;  Out:
;    HL  - Tone data.
; -----------------------------------------------------------------------------
Sound_ApplyToneEffect:      ; $82D0
    ; read the tone value from the channel structure
    ld    l, (ix + Sound_ChnlToneData)
    ld    h, (ix + Sound_ChnlToneData + 1)

    ; should an effect be applied?
    ld    a, (ix + Sound_ToneEffectFlags)
    or    a
    ret   z

    ; jump if bit 7 is reset
    jp    p, Sound_ApplyPitchEnvelope

    ; msb was set - apply pitch bend
    
    ; decrement countdown value and return if != 0
    dec   (ix + Sound_PitchBendCountdown) 
    ret   nz

    inc   (ix + Sound_PitchBendCountdown)    ;ix+$14 = 1
    
    ; push the tone value to the stack
    push  hl
    
    ; fetch the tone adjustment
    ld    l, (ix + Sound_ChnlToneAdjustment)
    ld    h, (ix + Sound_ChnlToneAdjustment + 1)
    
    ; decrement the counter
    dec   (ix + Sound_PitchBendLength)
    jr    nz, +

    ; the counter is zero. we need to apply the effect
    ; and reset the counter value
    
    ; load the effect data pointer into IY
    ld    e, (ix + Sound_PitchBendDataPtr)
    ld    d, (ix + Sound_PitchBendDataPtr + 1)
    push  de
    pop   iy
    
    ; reset the step duration counter
    ld    a, (iy + $01)
    ld    (ix + Sound_PitchBendLength), a

    ; load the signed 8bit step value into the
    ; bc register pair
    ld    a, (ix + Sound_PitchBendStepValue)
    ld    c, a
    ; rotate MSB into carry flag
    and   $80
    rlca
    neg
    ld    b, a
    
    ; add to the current tone adjustment value
    add   hl, bc
    
    ; store the tone adjustment value
    ld    (ix + Sound_ChnlToneAdjustment), l
    ld    (ix + Sound_ChnlToneAdjustment + 1), h

+:  ; pop the tone value into BC
    pop   bc

    ; apply the effect to the tone value
    add   hl, bc

    ; decrement the repeat counter
    dec   (ix + Sound_PitchBendRepeat)
    ret   nz

    ; restore the effect repeat counter
    ld    a, (iy + $03)
    ld    (ix + Sound_PitchBendRepeat), a
    
    ; negate the effect step value
    ld    a, (ix + Sound_PitchBendStepValue)
    neg
    ld    (ix + Sound_PitchBendStepValue), a
    ret


Sound_ApplyPitchEnvelope:       ; $8326
    dec   a
    ; put the tone data into the DE register pair
    ex    de, hl
    ; fetch a pointer to the effect data
    ld    hl, Sound_Data_PitchEnvelopes
    call  Sound_CalcIndex_Int16
    jr    Sound_PitchEnvelope_GetAdjustment


Sound_PitchEnvelope_SetIndex:     ; $8330
    ld    (ix + Sound_PitchEnvlpIndex), a


Sound_PitchEnvelope_GetAdjustment:        ; $8333
    ; read a value from the effect data array
    push  hl
    ld    c, (ix + Sound_PitchEnvlpIndex)
    call  Sound_CalcIndex_Int8
    pop   hl
    
    ; if the value < $80 add it to the tone data
    bit   7, a
    jr    z, Sound_PitchEnvelope_CalculateTone
    
    ; if the value == $83 it is a "set index" command
    cp    $83
    jr    z, Sound_PitchEnvelope_ReadIndex
    
    ; if the value is > $83 it is a negative adjustment
    ; value. sign extend and add to the tone data
    jr    nc, Sound_PitchEnvelope_SignExtend
    
    ; if the value == $80 it is a "reset index" command
    cp    $80
    jr    z, Sound_PitchEnvelope_ResetIndex
    
    ; value == $81 or $82
    set   5, (ix + Sound_ChnlControl)
    pop   hl
    ret

Sound_PitchEnvelope_ReadIndex:     ; $834F
    ; read the index from the effect data
    inc  de
    ld   a, (de)
    jr   Sound_PitchEnvelope_SetIndex


Sound_PitchEnvelope_ResetIndex:       ; $8353
    xor  a
    jr   Sound_PitchEnvelope_SetIndex


Sound_PitchEnvelope_SignExtend:       ; $8356
    ld   h, $FF
    jr   +

Sound_PitchEnvelope_CalculateTone:        ; $835A
    ld   h, $00
    
+:  ld   l, a
    ; add the effect data to the tone data
    add  hl, de
    
    ; increment the index value
    inc  (ix + Sound_PitchEnvlpIndex)
    ret


; =============================================================================
; Sound_ReadChannelStream(uint16 channel_ptr)
; -----------------------------------------------------------------------------
;  Updates a channel by reading from the module's sound command data.
; -----------------------------------------------------------------------------
;  In:
;    IX  - Channel pointer.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_ReadChannelStream:        ; $8362
    ;clear some flags
    res  Sound_ChnlKeepStateBit, (ix + Sound_ChnlControl)
    res  Sound_ChnlVolSuppressBit, (ix + Sound_ChnlControl)
    
    ; fetch the data pointer
    ld   e, (ix + Sound_ChnlNextCommand)
    ld   d, (ix + Sound_ChnlNextCommand + 1)
    ; FALL THROUGH


; =============================================================================
; Sound_ReadStreamData(uint16 channel_ptr, uint16 stream_ptr)
; -----------------------------------------------------------------------------
;  Read and interpret the next byte from the sound data.
; -----------------------------------------------------------------------------
;  In:
;    DE  - Current data pointer (points into sound data stream).
;    IX  - Channel pointer.
;  Out:
;
; -----------------------------------------------------------------------------
Sound_ReadStreamData:        ; $8370
    ; read a byte of data and increment the pointer
    ld    a, (de)
    inc   de
    
    ; jump if the byte is a control byte (i.e. >= $E0)?
    cp    $E0
    jp    nc, Sound_InterpretCommand_WithReturn
    
    bit   Sound_ChnlReadLiteralBit, (ix + Sound_ChnlControl)
    jp    nz, Sound_ReadLiteral
    
    ; jump if byte < $80
    cp    $80
    jr    c, Sound_ReadStreamData_CalcNoteHold
    
    ; jump if byte == $80
    jr    z, Sound_ReadStreamData_Rest
    
    
    ; value is a "note" command
    
    ; clear bit 7 of IX+$1D
    ex    af, af'
    ld    a, (ix + $1D)
    and   $7F
    ld    (ix + $1D), a
    ex    af, af'
    
    ; get the note value
    call  Sound_GetNoteValue
    ; store the note value in the channel structure
    ld    (ix + Sound_ChnlToneData), l
    ld    (ix + Sound_ChnlToneData + 1), h


; checks to see if the next byte is a "set ticks per note" command
; and sets the note duration as necessary
Sound_ReadStreamData_SetNoteHold:       ; $8397
    ; read the next byte
    ld   a, (de)
    inc  de
    or   a
    jp   p, Sound_ReadStreamData_CalcNoteHold
    
    ; copy the next tone duration to the current
    ld   a, (ix + Sound_ChnlToneDuration_Next)
    ld   (ix + Sound_ChnlToneDuration), a

    dec  de
    jr   ++


; calculate note duration based on the channel's tempo value
Sound_ReadStreamData_CalcNoteHold:      ; $83A6
    ; multiply the ticks-per-note value by the speed value
    call  Sound_MultiplyA
    ld    (ix + Sound_ChnlToneDuration), a
    ld    (ix + Sound_ChnlToneDuration_Next), a

++: ; store the data pointer
    ld    (ix + Sound_ChnlNextCommand), e
    ld    (ix + Sound_ChnlNextCommand + 1), d
    
    ; bail out if we need to preserve effect state
    bit   Sound_ChnlKeepStateBit, (ix + Sound_ChnlControl)
    ret   nz

    bit   Sound_ChnlVolumeOnlyBit, (ix + Sound_ChnlControl)
    jr    nz, +
    
    res   5, (ix + Sound_ChnlControl)
    
+:  ld    a, (ix + $0F)
    ld    (ix + $0E), a
    
    ; clear pitch bend length / pitch envelope index value
    xor  a
    ld   (ix + Sound_PitchBendLength), a
    
    ; check the volume effect flags
    bit  Sound_VolPreserveBit, (ix + Sound_VolumeEffectFlags)
    ret  nz

    ld   (ix + Sound_ChnlVolume), a
    ret


Sound_ReadStreamData_Rest:      ; $83D7
    call LABEL_85F8_134
    jr   Sound_ReadStreamData_SetNoteHold


; =============================================================================
; Sound_ReadLiteral(uint8 tone_lo_byte, uint16 src_ptr, uint16 channel_ptr)
; -----------------------------------------------------------------------------
;  Reads 16-bit, big-endian, tone data from the stream.
; -----------------------------------------------------------------------------
;  In:
;    A   - high-byte of the 16bit tone value.
;    DE  - Current data pointer (points into sound data).
;    IX  - Channel pointer.
;  Out:
;    A   - Next byte of data from the stream.
;    DE  - Pointer to the byte immediately following A.
;  Destroys:
;    BC, HL
; -----------------------------------------------------------------------------
Sound_ReadLiteral:       ;$83DC
    ; read another byte and copy the 16-bit value into HL
    ; NOTE: BIG ENDIAN READ
    ld    h, a      ;<-- 'low' byte is copied into H
    ld    a, (de)
    inc   de
    
    ; if the value is 0 don't bother adjusting
    ld    l, a      ;<-- 'high' byte is copied into L
    or    h
    jr    z, +

    ; apply the pitch adjustment
    
    ld    b, $00
    
    ; retrieve the tone adjustment value from
    ; the channel structure
    ld    a, (ix + Sound_ChnlPitchAdjust)
    or    a
    ld    c, a
    
    ; check the accumulator's sign bit.
    ; if the bit is set we need to sign extend into
    ; the B register
    jp    p, ++

    dec   b
    
++: ; adjust the tone value
    add   hl, bc

+:  ; store the tone data
    ld    (ix + Sound_ChnlToneData), l
    ld    (ix + Sound_ChnlToneData+1), h
    
    ; read another byte of data and increment the pointer
    ld    a, (de)
    inc   de
    jp    Sound_ReadStreamData_CalcNoteHold


; =============================================================================
; Sound_ResetAll()
; -----------------------------------------------------------------------------
;  Clears all memory used by the sound driver and initialises the PSG.
; -----------------------------------------------------------------------------
;  In:
;    None
;  Out:
;    None.
;  Destroys:
;    None.
; -----------------------------------------------------------------------------
Sound_ResetAll:        ; $83FA
    push  hl
    push  bc
    push  de
    
    ; reset all control bytes and channel structures
    ld    hl, $DD03
    ld    de, $DD04
    ld    bc, $018C
    ld    (hl), $00
    ldir
    
    pop   de
    pop   bc
    pop   hl
    ; fall through


; =============================================================================
; Sound_InitPSG()
; -----------------------------------------------------------------------------
;  Initialises the PSG registers with default values.
; -----------------------------------------------------------------------------
;  In:
;    None
;  Out:
;    None.
;  Destroys:
;    None.
; -----------------------------------------------------------------------------
Sound_InitPSG:        ;$840D
    push hl
    push bc
    ; write 10 bytes to the PSG
    ld   hl, Sound_Data_DefaultRegisterValues
    ld   b, $0A
    ld   c, Ports_PSG
    otir
    pop  bc
    pop  hl
    jp   Sound_SetActive

Sound_Data_DefaultRegisterValues:
.db Sound_PSG_Latch | Sound_PSG_Tone0, $00    ;set tone 0 = 0
.db Sound_PSG_Latch | Sound_PSG_Tone1, $00    ;set tone 1 = 0
.db Sound_PSG_Latch | Sound_PSG_Tone2, $00    ;set tone 2 = 0
.db Sound_PSG_Latch | Sound_PSG_Type_Volume | Sound_PSG_Tone0 | $0F
.db Sound_PSG_Latch | Sound_PSG_Type_Volume | Sound_PSG_Tone1 | $0F
.db Sound_PSG_Latch | Sound_PSG_Type_Volume | Sound_PSG_Tone2 | $0F
.db Sound_PSG_Latch | Sound_PSG_Type_Volume | Sound_PSG_Noise | $0F



; =============================================================================
; Sound_GetNoteValue(uint8 note_index)
; -----------------------------------------------------------------------------
;  Fetches the PSG tone counter register value for the specified note index.
; -----------------------------------------------------------------------------
;  In:
;    A   - Note number.
;  Out:
;    HL  - Note value.
;  Destroys:
;    BC
; -----------------------------------------------------------------------------
Sound_GetNoteValue:        ; $8427
    and   $7F
    add   a, (ix + Sound_ChnlPitchAdjust)
    ld    hl, Sound_Data_NoteValues
    ; FALL THROUGH


; =============================================================================
; Sound_CalcIndex_Int16(uint16 array_base, uint8 index)
; -----------------------------------------------------------------------------
;  Get an element from an array of 16-bit integers.
; -----------------------------------------------------------------------------
;  In:
;    A   - Element index.
;    HL  - Base address of the array.
;  Out:
;    HL  - Element value.
;  Destroys:
;    BC
; -----------------------------------------------------------------------------
Sound_CalcIndex_Int16:        ; $842F
    ld    c, a
    ld    b, $00
    add   hl, bc
    add   hl, bc
    ld    c, (hl)
    inc   hl
    ld    h, (hl)
    ld    l, c
    ret


; =============================================================================
; Sound_CalcIndex_Int8(uint32 array_base, uint8 index)
; -----------------------------------------------------------------------------
;  Calculate a pointer to an element in an array of bytes.
; -----------------------------------------------------------------------------
;  In:
;    C   - Element index.
;    HL  - Base address of the array.
;  Out:
;    A   - Element value.
;    HL  - Pointer to element within the array.
;  Destroys:
;    BC
; -----------------------------------------------------------------------------
Sound_CalcIndex_Int8:        ; $8439
    ld    b, $00
    add   hl, bc
    ld    a, (hl)
    ret


; =============================================================================
; Sound_MultiplyA(uint8 multiplicand, uint8 multiplier)
; -----------------------------------------------------------------------------
;  Performs 8-bit multiplication with the speed value stored in the channel.
; -----------------------------------------------------------------------------
;  In:
;    A         - Multiplicand
;    (ix + 2)  - Multiplier
;  Out:
;    A  - Value
;  Destroys:
;    BC
; -----------------------------------------------------------------------------
Sound_MultiplyA:        ; $843E
    ; fetch the multiplier and check that it's not 1
    ld    b, (ix + Sound_ChnlSpeed)
    dec   b
    ret   z

    ; multiply using repeated add
    ld    c, a
-:  add   a, c
    djnz  -
    ret



; PSG values approximately corresponding to MIDI notes
Sound_Data_NoteValues:        ; $8448
.include "src/sound_note_values.asm"
;.include "src/sound_note_values_tuned.asm"


; =============================================================================
; Sound_UpdateToneChannel(uint16 channel_ptr)
; -----------------------------------------------------------------------------
;  Runs the specified tone channel for 1 tick.
; -----------------------------------------------------------------------------
;  In:
;    IX - The channel pointer.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_UpdateToneChannel:        ; $84D4
    ; decrement the note duration counter
    dec   (ix + Sound_ChnlToneDuration)
    
    ; if the duration counter is > 0 we dont need to bother fetching
    ; new data but we may need to update a tone effect
    jr    nz, +
    
    ; duration counter == 0
    ; fetch data from the stream
    call  Sound_ReadChannelStream
    
    ; check that PSG volume writes are not suppressed
    bit   Sound_ChnlVolSuppressBit, (ix + Sound_ChnlControl)
    ret   nz
    
    ; check that the channel hasn't been suppressed by
    ; a sound effect module
    bit   Sound_ChannelSuppressBit, (ix + Sound_ChnlControl)
    ret   nz
    
    ; load any pitch effects 
    call  Sound_LoadPitchBendData
    call  LABEL_82B3_142
    
    ; and write to the PSG
    jr    Sound_WriteChannelData

+:  ; check to see if PSG writes should be suppressed
    bit   Sound_ChannelSuppressBit, (ix + Sound_ChnlControl)
    ret   nz
    
    ld    a, (ix + $0E)
    or    a
    jr    z, +
    
    dec   (ix + $0E)
    call  z, LABEL_85F8_134
    
+:  ld    a, (ix + Sound_ToneEffectFlags)
    or    a
    jr    z, Sound_UpdateChannelVolume
    
    bit   5, (ix + Sound_ChnlControl)
    jr    nz, Sound_UpdateChannelVolume
    ; FALL THROUGH

; =============================================================================
; Sound_WriteChannelData(uint16 channel_ptr, uint16 channel_data)
; -----------------------------------------------------------------------------
;  Writes data to the PSG and updates the volume.
; -----------------------------------------------------------------------------
;  In:
;    IX  - Pointer to a channel descriptor.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_WriteChannelData:        ;$850B
    ; check to see if we need to update volume only
    bit   Sound_ChnlVolumeOnlyBit, (ix + Sound_ChnlControl)
    jr    nz, Sound_UpdateChannelVolume
    
    ; apply any pitch effects
    call  Sound_ApplyToneEffect
    
    ; fetch the detune value
    ld    d, $00
    ld    a, (ix + Sound_ChnlDetune)    
    or    a
    ; check the value's sign and extend into D if necessary
    jp    p, ++    
    dec   d
    
++: ; add the detune value to the data
    ld    e, a
    add   hl, de
    
    
    ; get the current channel number
    ld    a, (ix + Sound_ChnlNumber)
    
    ; jump if it's a tone channel
    cp    Sound_PSG_Latch | Sound_PSG_Noise
    jr    nz, ++ 
    
    ; if we get here, the channel is a noise channel
    ; change to channel 2 instead
    ld    a, Sound_PSG_Latch | Sound_PSG_Tone2

++: ; store the channel number in the C register
    ld    c, a

    ; get the low 4-bits of data and combine them with the channel number
    ; to create a PSG latch byte
    ld    a, l
    and   $0F
    or    c
    ; write the latch byte to the PSG
    out   (Ports_PSG), a
    
    ; extract the upper 4 bits and combine with the second byte then
    ; rotate into the correct position
    ld    a, l
    and   $F0
    or    h
    rrca
    rrca
    rrca
    rrca
    ; write the data byte
    out   (Ports_PSG), a
    ; FALL THROUGH

; =============================================================================
; Sound_UpdateChannelVolume(uint16 channel_ptr)
; -----------------------------------------------------------------------------
;  Copies a channel's volume data to the PSG.
; -----------------------------------------------------------------------------
;  In:
;    IX  - Pointer to a channel descriptor.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_UpdateChannelVolume:        ; $853A
    ; grab any volume effect data
    call  Sound_CheckApplyVolumeEffect
    
    ; check that PSG writes are not suppressed
    bit   Sound_ChannelSuppressBit, (ix + Sound_ChnlControl)
    ret   nz
    
    ; check that volume updates are not suppressed
    bit   Sound_ChnlVolSuppressBit, (ix + Sound_ChnlControl)
    ret   nz
    
    ; add the channel's volume adjustment value to the effect
    add   a, (ix + Sound_ChnlVolumeAdjust)
    
    ; make sure that we don't overflow the 4 data bits
    bit   4, a
    jr    z, +
    ld    a, $0F

+:  ; fetch channel number from the structure
    or    (ix + Sound_ChnlNumber)
    ; set the "type" bit to volume
    add   a, Sound_PSG_Type_Volume
    ; write to the PSG
    out   (Ports_PSG), a

    ret


; =============================================================================
; Sound_CheckApplyVolumeEffect(uint16 channel_ptr)
; -----------------------------------------------------------------------------
;  Checks the channel's volume effect flags and applies an affect as
;  necessary.
; -----------------------------------------------------------------------------
;  In:
;    IX  - Pointer to a channel descriptor.
;  Out:
;    A   - Effect data to be added to volume adjustment.
;  Destroys:
;
; -----------------------------------------------------------------------------
Sound_CheckApplyVolumeEffect:       ; $8558
    ; get the effect flags from the channel structure
    ld    a, (ix + Sound_VolumeEffectFlags)
    or    a
    ; if flags == 0 no effect should be applied
    ret   z

    ; if MSB is reset apply a volume envelope
    jp    p, Sound_ApplyEnvelope

    ; check the "fade in" bit
    bit   4, (ix + $1D)
    jr    z, LABEL_8580_159
    
    ; fade volume in using effect value
    ld    d, (ix + Sound_ChnlVolEffectAdjust)   ;get volume adjustment value
    ld    a, (ix + Sound_ChnlVolume)            ;get current volume value
    sub   d
    jr    nc, +

    xor   a                ;resulting volume was < 0. reset to 0

+:  or    a
    ld    (ix + Sound_ChnlVolume), a        ;set current volume
    jr    nz, LABEL_85EE_161
    
    ; attenuation is now at zero. turn the "fade out"
    ; bit off. set bit 5
    ld    a, (ix + $1D)
    xor   $30               ;flip bits 4 & 5
    ld    (ix + $1D), a

    jr    LABEL_85EE_161


LABEL_8580_159:
    bit   5, (ix + $1D)
    jr    z, LABEL_85B0_162

    ld    a, (ix + Sound_ChnlVolume)
    ld    d, (ix + $21)
    ld    e, (ix + $22)
    add   a, d
    jr    c, +
    
    cp    e
    jr    c, ++
    
+:  ld    a, e

++: cp    e
    ld    (ix + Sound_ChnlVolume), a
    jr    nz, LABEL_85EE_161
    ld    a, (ix + $1D)
    bit   3, (ix + $1D)
    
    jr    z, +
    xor   $30
    jr    ++

+:  xor   $60

++: ld    (ix + $1D), a
    jr    LABEL_85EE_161


LABEL_85B0_162:
    bit   6, (ix + $1D)
    jr    z, LABEL_85D2_167
    
    ; fetch the current volume & adjustment value
    ld    a, (ix + Sound_ChnlVolume)
    ld    d, (ix + $23)
    
    ; add the adjustment value (increase attenuation)
    add   a, d
    jr    nc, +
    
    ; there was a carry - set volume off
    ld    a, $FF
    
+:  cp    $FF
    ld    (ix + Sound_ChnlVolume), a
    jr    nz, LABEL_85EE_161
    
    ; if attenuation == $FF
    ld    a, (ix + $1D)
    ; turn off bits 4, 5 & 6 of the volume effects flags
    and   $8F
    ld    (ix + $1D), a
    jr    LABEL_85EE_161


LABEL_85D2_167:
    ld    a, (ix + Sound_ChnlVolume)
    ld    d, (ix + $24)
    add   a, d
    jr    nc, +
    
    ld    a, (ix + $1D)
    and   $0F
    ld    (ix + $1D), a
    ld    a, $FF
    ld    (ix + Sound_ChnlVolume), a
    jp    Sound_SetVolumeOff

+:  ld    (ix + Sound_ChnlVolume), a
    ; FALL THROUGH

LABEL_85EE_161:
    ld    a, (ix + Sound_ChnlVolume)
    rrca
    rrca
    rrca
    rrca
    and   $0F
    ret

LABEL_85F8_134:
    ; bail out if we need to preserve effect state
    bit    Sound_ChnlKeepStateBit, (ix + Sound_ChnlControl)
    ret    nz

    ; check to see if a volume effect is being used
    bit    Sound_VolPreserveBit, (ix + Sound_VolumeEffectFlags)
    ; jump if there isnt
    jp     z, Sound_SetVolumeOff

    ; clear bits 4,5 & 6, set bit 7
    ld     a, (ix + $1D)
    and    $0F
    or     $80
    ld     (ix + $1D), a
    ret


; apply a volume envelope
Sound_ApplyEnvelope:        ; $860F
    dec   a
    ld    hl, Sound_Data_VolumeEnvelopes
    call  Sound_CalcIndex_Int16
    jr    Sound_ApplyEnvelope_GetData

Sound_ApplyEnvelope_SetIndex:       ; $8175
    ; set the index
    ld    (ix + Sound_VolEnvlpIndex), a

Sound_ApplyEnvelope_GetData:        ; $861B
    ; fetch the effect value from the array addressed by HL
    ; using (IX+$1F) as an index
    push  hl
    ld    c, (ix + Sound_VolEnvlpIndex)
    call  Sound_CalcIndex_Int8
    pop   hl
    
    ; if effect value < $80
    bit   7, a
    jr    z, Sound_ApplyEnvelope_NextIndex

    ; if value > $80 it is a command byte
    cp    $82
    jr    z, Sound_ApplyEnvelope_SetVolumeOff
    
    cp    $81
    jr    z, Sound_ApplyEnvelope_HoldVolume
    
    cp    $80
    jr    z, Sound_ApplyEnvelope_ResetIndex

    ; any command bytes >= $83 are a "set index" command
    inc   de
    ld    a, (de)
    jr    Sound_ApplyEnvelope_SetIndex


Sound_ApplyEnvelope_SetVolumeOff:       ; $8637
    ; suppress psg writes
    ; this SET op seems a bit pointless since Sound_SetVolumeOff
    ; does the same thing
    set   Sound_ChnlVolSuppressBit, (ix + Sound_ChnlControl)
    ; CHECK: what is this POPping?
    pop   hl
    jr    Sound_SetVolumeOff


Sound_ApplyEnvelope_ResetIndex:     ; $863E
    xor   a
    jr    Sound_ApplyEnvelope_SetIndex

Sound_ApplyEnvelope_HoldVolume:     ; $8641
    ; suppress volume writes
    set   Sound_ChnlVolSuppressBit, (ix + Sound_ChnlControl)
    pop   hl
    ret

Sound_ApplyEnvelope_NextIndex:      ; $8647
    inc   (ix + Sound_VolEnvlpIndex)
    ret


; =============================================================================
;  Sound_SetVolumeOff()
; -----------------------------------------------------------------------------
;  Sets PSG volume attenuation to full without affecting volume state
;  variables.
; -----------------------------------------------------------------------------
;  In:
;    None.
;  Out:
;    None.
;  Destroys:
;    A
; -----------------------------------------------------------------------------
Sound_SetVolumeOff:        ; $864B
    set   Sound_ChnlVolSuppressBit, (ix + Sound_ChnlControl)
    ; are PSG writes suppressed?
    bit   Sound_ChannelSuppressBit, (ix + Sound_ChnlControl)
    ret   nz
    
    ; set volume attenuation to full
    ld    a, Sound_PSG_Type_Volume | $0F
    add   a, (ix + Sound_ChnlNumber)
    out   (Ports_PSG), a
    ret


; =============================================================================
; Sound_UpdateNoiseChannel(uint16 channel_ptr)
; -----------------------------------------------------------------------------
;  Runs the noise channel for one tick.
; -----------------------------------------------------------------------------
;  In:
;    IX - The channel pointer.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_UpdateNoiseChannel:        ; $865C
    ; decrement counter
    dec   (ix + Sound_ChnlToneDuration)
    
    ; update volume if counter != 0
    jp    nz, Sound_UpdateChannelVolume
    
    ; re-enable PSG volume writes
    res   Sound_ChnlVolSuppressBit, (ix + Sound_ChnlControl)
    
    ; fetch the pointer to the next command
    ld    e, (ix + Sound_ChnlNextCommand)
    ld    d, (ix + Sound_ChnlNextCommand  + 1)
    
-:  ; fetch a byte of data from the source address
    ld    a, (de)
    inc   de
    
    ; is the byte a command byte?
    cp    $E0
    ; jump if it is
    jr    nc, Sound_UpdateNoiseChannel_InterpretCommand
    
    ; jump if A < $80
    cp    $80
    jp    c, Sound_ReadStreamData_CalcNoteHold
    
    ; if we get here, A is between $80 and $E0
    ; does something with noise data
    call  LABEL_8686_180
    jp    Sound_ReadStreamData_SetNoteHold

Sound_UpdateNoiseChannel_InterpretCommand:      ; $867D
    ld    hl, Sound_UpdateNoiseChannel_ReturnStub
    jp    Sound_InterpretCommand


Sound_UpdateNoiseChannel_ReturnStub:        ; $8663
    inc   de
    jr    -    



LABEL_8686_180:
    ; TODO: since accumulator value is not used after the
    ;   jump, using RRCA here would be cheaper
    bit   0, a
    jr    nz, LABEL_86C9_181
    bit   1, a
    jr    nz, LABEL_86A9_182
    bit   2, a
    jr    nz, LABEL_86C1_183
    bit   3, a
    jr    nz, LABEL_86A1_184
    bit   4, a
    jr    nz, LABEL_86B9_185
    bit   5, a
    jr    nz, LABEL_86B1_186
    jp    Sound_SetVolumeOff

LABEL_86A1_184:
    ld    a, $03        ; volume envelope
    ld    b, $02        ; volume adjustment
    ld    c, Sound_PSG_Latch | Sound_PSG_Noise | 4      ; white noise, reset to $10
    jr    LABEL_86CF_187

LABEL_86A9_182:
    ld    a, $03
    ld    b, $02
    ld    c, Sound_PSG_Latch | Sound_PSG_Noise | 4      ; white noise, reset to $10
    jr    LABEL_86CF_187

LABEL_86B1_186:
    ld    a, $04
    ld    b, $04
    ld    c, Sound_PSG_Latch | Sound_PSG_Noise | 4      ; white noise, reset to $10
    jr    LABEL_86CF_187

LABEL_86B9_185:
    ld    a, $03
    ld    b, $03
    ld    c, Sound_PSG_Latch | Sound_PSG_Noise | 6      ; white noise, reset to $40
    jr    LABEL_86CF_187

LABEL_86C1_183:
    ld    a, $02
    ld    b, $02
    ld    c, Sound_PSG_Latch | Sound_PSG_Noise | 5      ; white noise, reset to $20
    jr    LABEL_86CF_187

LABEL_86C9_181:
    ld    a, $01
    ld    b, $02
    ld    c, Sound_PSG_Latch | Sound_PSG_Noise | 4      ; white noise, reset to $10

LABEL_86CF_187:
    ld    (ix + Sound_VolumeEffectFlags), a
    
    ; adjust the noise channel attenuation
    ld    a, (Sound_NoiseChnlVolume)
    add   a, b
    ld    (ix + Sound_ChnlVolumeAdjust), a
    
    ; are PSG writes suppressed?
    bit   Sound_ChannelSuppressBit, (ix + Sound_ChnlControl)
    ret   nz
    
    ; set the noise channel data
    ld    a, ($DD15)
    add   a, c
    ld    ($DD11), a
    out   (Ports_PSG), a
    ret


; =============================================================================
; Sound_InterpretCommand_WithReturn(uint8 command_value, uint16 data_ptr)
; -----------------------------------------------------------------------------
;  Interprets a command and calls the relevant subroutine. Control will
;  be returned to the Sound_ReadStreamData function.
; -----------------------------------------------------------------------------
;  In:
;    A   - The command value.
;    DE  - Sound data pointer.
;    IX  - Channel pointer.
;  Out:
;
;  Destroys:
;
; -----------------------------------------------------------------------------
Sound_InterpretCommand_WithReturn:        ; $86E8
    ; load this function pointer as a return address
    ld    hl, Sound_InterpretCommand_ReturnStub
    ; FALL THROUGH

; =============================================================================
; Sound_InterpretCommand(uint8 cmd, uint16 data, unit16 chnl, uint16 rtrn)
; -----------------------------------------------------------------------------
;  Interprets a command and calls the relevant subroutine.
; -----------------------------------------------------------------------------
;  In:
;    A   - The command value.
;    DE  - Sound data pointer.
;    IX  - Channel pointer.
;    HL  - Return address for the command function.
;  Out:
;    None.
; -----------------------------------------------------------------------------
Sound_InterpretCommand:        ; $86EB
    ; push a function pointer to the stack so that
    ; the indirect jump has a valid return address
    push  hl
    
    ; use A as an index into the array of functions
    sub   $E0
    ld    hl, Sound_CommandDispatchTable
    add   a, a
    ld    c, a
    ld    b, $00
    add   hl, bc
    
    ; fetch the function pointer from the array.
    ld    c, (hl)
    inc   hl
    ld    h, (hl)
    ld    l, c
    
    ; load the next data byte into A
    ld    a, (de)
    
    ; jump to the function.
    jp    (hl)

; This stub will return control to the Sound_ReadStreamData function
Sound_InterpretCommand_ReturnStub:        ;$86FC
    inc   de
    jp    Sound_ReadStreamData


Sound_CommandDispatchTable:         ;$8700
.dw LABEL_88BB                      ; $E0
.dw Sound_Command_Detune            ; $E1
.dw Sound_Command_AdjustToneVolume  ; $E2
.dw Sound_Command_DoNothing         ; $E3
.dw Sound_Command_AdjustNoiseVolume ; $E4
.dw LABEL_88D5                      ; $E5
.dw Sound_Command_AdjustToneVolume  ; $E6
.dw Sound_Command_RepeatNote        ; $E7
.dw LABEL_8892                      ; $E8
.dw Sound_Command_DoNothing         ; $E9
.dw Sound_Command_DoNothing         ; $EA
.dw Sound_Command_DoNothing         ; $EB
.dw Sound_Command_DoNothing         ; $EC
.dw Sound_Command_SetGlobalSpeed    ; $ED
.dw Sound_Command_DoNothing         ; $EE
.dw Sound_Data_PitchEnvelopes       ; $EF - invalid command
.dw Sound_Command_PitchBend         ; $F0
.dw Sound_Data_PitchEnvelopes       ; $F1 - invalid command
.dw LABEL_87DB                      ; $F2
.dw LABEL_8766                      ; $F3
.dw Sound_Command_PitchEnvelope     ; $F4
.dw Sound_Command_VolumeEnvelope    ; $F5
.dw Sound_Command_Jump              ; $F6
.dw Sound_Command_ConditionalJump   ; $F7
.dw Sound_Command_Branch            ; $F8
.dw Sound_Command_Return            ; $F9 
.dw Sound_Command_SetChnlSpeed      ; $FA
.dw Sound_Command_PitchAdjust       ; $FB
.dw Sound_Command_DoNothing         ; $FC
.dw Sound_Command_ReadLiteral       ; $FD - set/reset reading of literal tone data
.dw Sound_Command_DoNothing         ; $FE
.dw Sound_Command_DoNothing         ; $FF
    

Sound_Command_AdjustToneVolume:     ; $8740
    ; check to see if the sound is fading out
    ex    af, af'
        ld    a, (Sound_FadeMajorCount)
        or    a
        ret   nz
    ex    af, af'
    
    ; adjust tone volume attenuation
    add   a, (ix + Sound_ChnlVolumeAdjust)
    ; cap at 15
    and   $0F
    ld    (ix + Sound_ChnlVolumeAdjust), a
    ; FALL THROUGH
  

Sound_Command_DoNothing:            ; $874F
    ret


Sound_Command_AdjustNoiseVolume:    ; $8750
    ld    c, a
    ld    a, (Sound_NoiseChnlVolume)
    add   a, c
    ; cap attenuation at 15
    and   $0F
    ld    (Sound_NoiseChnlVolume), a
    ret


Sound_Command_PitchAdjust:    ; $875B
    add   a, (ix + Sound_ChnlPitchAdjust)
    ld    (ix + Sound_ChnlPitchAdjust), a
    ret


Sound_Command_SetChnlSpeed:         ; $8762
    ld    (ix + Sound_ChnlSpeed), a
    ret 


LABEL_8766:
    ; check the SFX index. jump if the MSB is set.
    ex    af, af'
        ld    a, (Sound_SFXIndex)
        or    a
        ; jump if SFX is playing
        jp    m, LABEL_878F
    ex    af, af'
    
    
    or    $FC
    inc   a
    jr    nz, LABEL_878F

    ; load pointer to secondary tone1 channel
    ld    hl, $DE30
    ; check to see if the channel is active
    bit   7, (hl)
    ; jump if it isn't
    jr    z, LABEL_878F

    ; CHECK: is this offset in the noise channel?
    ld    hl, $DDD0
    res   2, (hl)
    set   4, (hl)
    
    ; clear the channel's control byte
    xor   a
    ld    (ix + Sound_ChnlControl), a
    
    ; write  $FF to the PSG (set noise attenuation to $F)
    dec   a
    out   (Ports_PSG), a
    
    ; write $FF to the sfx index
    ld    (Sound_SFXIndex), a
    
    pop   hl
    pop   hl
    ret

LABEL_878F:
    ; read a byte of data from the stream and
    ; write it to the PSG
    ld    a, (de)
    out   (Ports_PSG), a
    
    ; get a pointer to primary tone2 channel
    ld    hl, $DDA0
    ; get a pointer to secondary tone1 channel
    ld    iy, $DE30
    
    or    $FC
    inc   a
    jr    nz, LABEL_878D
    
    ; latch tone 2 and set volume to $F
    ld    a, $DF
    out   (Ports_PSG), a
    
    ; reset the "volume only" flag for the current channel
    res   Sound_ChnlVolumeOnlyBit, (ix + Sound_ChnlControl)
    
    ; suppress PSG writes for primary channel2
    set   2, (hl)
    ; suppress PSG writes for secondary channel1
    set   2, (iy + Sound_ChnlControl)
    
    ret

LABEL_878D:
    ; set "volume updates only" flag for the current channel
    set   Sound_ChnlVolumeOnlyBit, (ix + Sound_ChnlControl)
    
    ; jump if secondary channel 1 is active
    bit   7, (iy + $00)
    jr    nz, LABEL_87BA
    
    ; clear "suppress PSG writes" flag for primary channel2
    res   2, (hl)
    ret

LABEL_87BA:
    ; clear "suppress PSG writes" flag for secondary channel1
    res   2, (iy + Sound_ChnlControl)
    ret   


Sound_Command_VolumeEnvelope:       ; $87BF
    ; store the envelope index in the volume effect flags
    ld    (ix + Sound_VolumeEffectFlags), a
    ret


Sound_Command_PitchEnvelope:        ; $87C3
    ; store the envelope index in the tone effect flags
    ld    (ix + Sound_ToneEffectFlags), a
    ret


Sound_Command_Jump:     ; $87C7
    ; retrieve the new pointer from the source data
    ex    de, hl
    ld    e, (hl)
    inc   hl
    ld    d, (hl)
    
    ; decrement the pointer since the next instruction
    ; after the RET will INC DE.
    dec   de
    ret


; these 2 functions set and clear the 16bit read flag
; in the channel control byte
Sound_Command_ReadLiteral:      ; $87CD
    cp    $01
    jr    nz, +
    set   Sound_ChnlReadLiteralBit, (ix + Sound_ChnlControl)
    ret

+:  res   Sound_ChnlReadLiteralBit, (ix + Sound_ChnlControl)
    ret


LABEL_87DB:
    ; fetch the channel number
    ld    a, (ix + Sound_ChnlNumber)
    
    ; jump tone channel 1
    cp    Sound_PSG_Latch | Sound_PSG_Tone1     ;$A0
    jr    z, LABEL_881A
    
    ; jump if tone channel 2
    cp    Sound_PSG_Latch | Sound_PSG_Tone2     ;$C0
    jr    z, LABEL_880E
    
    
    bit   Sound_ChnlVolumeOnlyBit, (ix + Sound_ChnlControl)
    jr    nz, +
    
    ; load HL with pointer to secondary tone 2 channel
    ld    hl, $DE30
    ; jump if channel is active
    bit   7, (hl)
    jp    nz, +

    ; load HL with pointer to primary tone 2 channel
    ld    hl, $DDA0
    res   2, (hl)
    res   6, (hl)
    set   4, (hl)
    
    ; clear the secondary tone2 channel's control byte
    ld    hl, $DE30
    ld    (hl), $00

+:  push  af
    ; read noise channel data?
    ld    a, ($DD11)
    out   (Ports_PSG), a
    pop   af
    
    ld    hl, $DDD0
    jr    LABEL_881D

LABEL_880E:
    ; check to see if secondary tone2 channel is active
    ld    hl, $DE60
    bit   7, (hl)
    ; jump if it is
    jr    nz, LABEL_881D
    
    ; load HL with a pointer to the primary tone2 channel
    ld    hl, $DDA0
    jr    LABEL_881D


LABEL_881A:
    ; load HL with pointer to primary tone1 channel
    ld    hl, $DD70
LABEL_881D:
    res   2, (hl)
    set   4, (hl)
    
    ; turn channel volume off
    or    $1F
    out   (Ports_PSG), a

    ; clear the channel control byte (disable channel)
    xor   a
    ld    (ix + Sound_ChnlControl), a
    
    ld    ix, $DE00
    bit   7, (ix+$00)
    jr    nz, +
    
    ld    ix, $DE30
    bit   7, (ix+$00)
    jr    nz, +
    
    ld    ix, $DE60
    bit   7, (ix+$00)
    jr    nz, +
    
    ; clear the priority value
    xor   a
    ld    (Sound_Priority), a

+:  pop   bc
    pop   bc
    ret


Sound_Command_Branch:        ; $884E
    ; read new data pointer into BC
    ld    c, a
    inc   de
    ld    a, (de)
    ld    b, a

    ; push the new data pointer onto the main stack
    push  bc
    
    ; load the channel structure pointer into HL
    push  ix
    pop   hl
    
    ; read the channel stack pointer into BC and
    ; decrement it by 2 (we'll be pushing a word 
    ; onto the channel stack).
    dec   (ix + Sound_ChnlStackPointer)
    ld    c, (ix + Sound_ChnlStackPointer)
    dec   (ix + Sound_ChnlStackPointer)
    ld    b, $00
    
    ; calculate the absolute address for the calculated 
    ; channel stack pointer
    add   hl, bc
    
    ; store the current data pointer in the channel stack
    ld    (hl), d
    dec   hl
    ld    (hl), e
    
    ; restore the new data pointer into DE
    pop   de
    ; decrement DE since the next instruction after the RET 
    ; will increment DE
    dec   de
    ret


Sound_Command_Return:        ; $8868
    ; load channel pointer into HL
    push  ix
    pop   hl
    
    ; load channel stack pointer into BC
    ld    c, (ix + Sound_ChnlStackPointer)
    ld    b, $00
    
    ; calculate absolute address of channel
    ; stack pointer
    add   hl, bc
    
    ; read old data pointer back into DE
    ld    e, (hl)
    inc   hl
    ld    d, (hl)
    
    ; restore the stack pointer
    inc   (ix + Sound_ChnlStackPointer)
    inc   (ix + Sound_ChnlStackPointer)
    
    ret


Sound_Command_ConditionalJump:      ; $887B
    inc   de
    
    ; loop counter memory starts at offset IX+$27
    add   a, Sound_ChnlLoopCounters

    ld    c, a
    ld    b, $00

    ; load the channel structure pointer into HL
    push  ix
    pop   hl
    
    ; adjust the pointer to the correct offset
    add   hl, bc

    ; read the loop counter from the channel structure
    ld    a, (hl)
    or    a
    jr    nz, +

    ; if the counter is zero this must be the first iteration
    ; of the loop. we need to read the counter value from the 
    ; source data
    ld    a, (de)
    ld    (hl), a

+:  inc   de

    ; decrement the loop counter
    dec   (hl)
    ; branch while the counter != 0
    jp    nz, Sound_Command_Jump

    inc   de
    ret


LABEL_8892:
    call  Sound_MultiplyA
    ld    (ix + $0E), a
    ld    (ix + $0F), a
    ret


Sound_Command_PitchBend:      ; $889C
    ; store the effect data pointer in the channel structure
    ld    (ix + Sound_PitchBendDataPtr), e
    ld    (ix + Sound_PitchBendDataPtr + 1), d
    ; set the effect flag
    ld    (ix + Sound_ToneEffectFlags), Sound_ChnlPitchBend
    ; move the data pointer past the parameter bytes
    inc   de
    inc   de
    inc   de
    ret


Sound_Command_RepeatNote:       ; $88AA
    set   Sound_ChnlKeepStateBit, (ix + Sound_ChnlControl)
    dec   de
    ret

Sound_Command_Detune:        ;$88B0
    ld    (ix + Sound_ChnlDetune), a
    ret


Sound_Command_SetGlobalSpeed:       ;$88B4
    ld    (Sound_Speed), a
    ld    (Sound_SpeedCounter), a
    ret



LABEL_88BB:
    ld    (ix + Sound_VolumeEffectFlags), Sound_VolPreserve
    
    ; HL = pointer to channel structure
    push  ix
    pop   hl
    
    ; adjust the pointer to IX + $20
    ld    b, $00
    ld    c, $20
    add   hl, bc
    
    ; copy 5 bytes into the channel structure
    ex    de, hl
    ldi
    ldi
    ldi
    ldi
    ldi
    ex    de, hl
    
    dec   de
    ret


LABEL_88D5:
    or    a
    jr    z, +
    ld    a, $08
+:  ld    (ix + $1E), a
    ret



Sound_Data_PitchEnvelopes:      ; $88DE
.include "src/sound_pitch_effects.asm"

; volume envelopes
Sound_Data_VolumeEnvelopes:     ; $8A2F
.include "src/sound_volume_effects.asm"

Sound_Data_Priorities:
.incbin "sound\sound_priorities.bin"


; -----------------------------------------------------------------------------
;  Speed values for each music module. Note that these values are referenced
;  but not used. The speed value is stored in the module and any values
;  copied from here are overwritten later, when the module is loaded.
;  See Sound_LoadMusicModule() for more.
; -----------------------------------------------------------------------------
Sound_Data_MusicSpeeds:
.db $03, $00, $05, $00, $00, $03, $00, $00
.db $03, $04, $03, $03, $05, $05, $00, $05
.db $03, $05, $07


Sound_Data_MusicPointers:        ; $8BE7
.dw Sound_Data_Music_ALZ
.dw Sound_Data_Music_UGZ
.dw Sound_Data_Music_GMZ 
.dw Sound_Data_Music_CEZ
.dw Sound_Data_Music_GHZ
.dw Sound_Data_Music_SHZ
.dw Sound_Data_Music_SEZ
.dw Sound_Data_Music_Intro
.dw Sound_Data_Music_Boss
.dw Sound_Data_Music_SpeedShoes
.dw Sound_Data_Music_Invincibility
.dw Sound_Data_Music_EndOfLevel
.dw Sound_Data_Music_LoseLife 
.dw Sound_Data_Music_Continue
.dw Sound_Data_Music_Emerald
.dw Sound_Data_Music_GameOver
.dw Sound_Data_Music_Ending 
.dw Sound_Data_Music_Unknown
.dw Sound_Data_Music_TitleCard


Sound_Data_SFXPointers:         ; $8C0D
.dw Sound_Data_SFX_Ring         ; $00
.dw Sound_Data_SFX_Roll         ; $01
.dw Sound_Data_SFX_Spring       ; $02
.dw Sound_Data_SFX_Jump         ; $03
.dw Sound_Data_SFX_04
.dw Sound_Data_SFX_05
.dw Sound_Data_SFX_06
.dw Sound_Data_SFX_07
.dw Sound_Data_SFX_08
.dw Sound_Data_SFX_09
.dw Sound_Data_SFX_0A
.dw Sound_Data_SFX_0B
.dw Sound_Data_SFX_0C 
.dw Sound_Data_SFX_0D
.dw Sound_Data_SFX_0E
.dw Sound_Data_SFX_0F
.dw Sound_Data_SFX_10
.dw Sound_Data_SFX_11
.dw Sound_Data_SFX_12
.dw Sound_Data_SFX_13
.dw Sound_Data_SFX_14
.dw Sound_Data_SFX_15
.dw Sound_Data_SFX_16
.dw Sound_Data_SFX_17
.dw Sound_Data_SFX_18
.dw Sound_Data_SFX_19
.dw Sound_Data_SFX_1A
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B
.dw Sound_Data_SFX_1B

Sound_Data_Music_ALZ:           ; $8C59
;.incbin "sound/music_alz.bin"
.include "sound/music_alz.asm"

Sound_Data_Music_UGZ:           ; $8F98
;.incbin "sound/music_ugz.bin"
.include "sound/music_ugz.asm"

Sound_Data_Music_GMZ:           ; $94DD
;.incbin "sound/music_gmz.bin"
.include "sound/music_gmz.asm"

Sound_Data_Music_CEZ:           ; $9819
;.incbin "sound/music_cez.bin"
.include "sound/music_cez.asm"

Sound_Data_Music_GHZ:           ; $9B99
;.incbin "sound/music_ghz.bin"
.include "sound/music_ghz.asm"

Sound_Data_Music_SHZ:           ; $A053
;.incbin "sound/music_shz.bin"
.include "sound/music_shz.asm"

Sound_Data_Music_SEZ:           ; $A2CE
;.incbin "sound/music_sez.bin"
.include "sound/music_sez.asm"

Sound_Data_Music_Intro:         ; $A7C6
;.incbin "sound/music_intro.bin"
.include "sound/music_intro.asm"
;.include "sound/music_starlight.asm"

Sound_Data_Music_Boss:          ; $AA00
;.incbin "sound/music_boss.bin"
.include "sound/music_boss.asm"

Sound_Data_Music_SpeedShoes:    ; $AB9A
;.incbin "sound/music_speed_shoes.bin"
.include "sound/music_speed_shoes.asm"

Sound_Data_Music_Invincibility: ; $AC88
;.incbin "sound/music_invincibility.bin"
.include "sound/music_invincibility.asm"

Sound_Data_Music_EndOfLevel:    ; $AE1E
;.incbin "sound/music_end_of_level.bin"
.include "sound/music_end_of_level.asm"

Sound_Data_Music_LoseLife:      ; $AF1C
;.incbin "sound/music_lose_life.bin"
.include "sound/music_lose_life.asm"

Sound_Data_Music_Continue:      ; $AFA4
;.incbin "sound/music_continue.bin"
.include "sound/music_continue.asm"

Sound_Data_Music_Emerald:       ; $B0BE
;.incbin "sound/music_emerald.bin"
.include "sound/music_emerald.asm"

Sound_Data_Music_GameOver:      ; $B176
;.incbin "sound/music_gameover.bin"
.include "sound/music_gameover.asm"

Sound_Data_Music_Ending:        ; $B232
;.incbin "sound/music_ending.bin"
.include "sound/music_ending.asm"

Sound_Data_Music_Unknown:       ; $B946 
;.incbin "sound/music_unknown.bin"
.include "sound/music_unknown.asm"

Sound_Data_Music_TitleCard:     ; $BA90
;.incbin "sound/music_titlecard.bin"
.include "sound/music_titlecard.asm"


Sound_Data_SFX_Ring:            ; $BAEA
;.incbin "sound/sfx_ring.bin"
.include "sound/sfx_ring.asm"

Sound_Data_SFX_Roll:            ; $BB07
;.incbin "sound/sfx_roll.bin"
.include "sound/sfx_roll.asm"

Sound_Data_SFX_Spring:          ; $BB1F
;.incbin "sound/sfx_spring.bin"
.include "sound/sfx_spring.asm"

Sound_Data_SFX_Jump:            ; $BB45
;.incbin "sound/sfx_jump.bin"
.include "sound/sfx_jump.asm"

Sound_Data_SFX_04:              ; $BB6C
;.incbin "sound/sfx_04.bin"
.include "sound/sfx_04.asm"

Sound_Data_SFX_05:              ; $BB89
;.incbin "sound/sfx_05.bin"
.include "sound/sfx_05.asm"

Sound_Data_SFX_06:              ; $BBB8
;.incbin "sound/sfx_06.bin"
.include "sound/sfx_06.asm"

Sound_Data_SFX_07:              ; $BBD0
;.incbin "sound/sfx_07.bin"
.include "sound/sfx_07.asm"

Sound_Data_SFX_08:              ; $BBE6
;.incbin "sound/sfx_08.bin"
.include "sound/sfx_08.asm"

Sound_Data_SFX_09:              ; $BBF8
;.incbin "sound/sfx_09.bin"
.include "sound/sfx_09.asm"

Sound_Data_SFX_0A:              ; $BC0B
;.incbin "sound/sfx_0A.bin"
.include "sound/sfx_0A.asm"

Sound_Data_SFX_0B:              ; $BC33
;.incbin "sound/sfx_0B.bin"
.include "sound/sfx_0B.asm"

Sound_Data_SFX_0C:              ; $BC56
;.incbin "sound/sfx_0C.bin"
.include "sound/sfx_0C.asm"

Sound_Data_SFX_0D:              ; $BC7F
;.incbin "sound/sfx_0D.bin"
.include "sound/sfx_0D.asm"

Sound_Data_SFX_0E:              ; $BCBF
;.incbin "sound/sfx_0E.bin"
.include "sound/sfx_0E.asm"

Sound_Data_SFX_0F:              ; $BCDC
;.incbin "sound/sfx_0F.bin"
.include "sound/sfx_0F.asm"

Sound_Data_SFX_10:              ; $BCF0
;.incbin "sound/sfx_10.bin"
.include "sound/sfx_10.asm"

Sound_Data_SFX_11:              ; $BD0D
;.incbin "sound/sfx_11.bin"
.include "sound/sfx_11.asm"

Sound_Data_SFX_12:              ; $BD21
;.incbin "sound/sfx_12.bin"
.include "sound/sfx_12.asm"

Sound_Data_SFX_13:              ; $BD3C
;.incbin "sound/sfx_13.bin"
.include "sound/sfx_13.asm"

Sound_Data_SFX_14:              ; $BD53
;.incbin "sound/sfx_14.bin"
.include "sound/sfx_14.asm"

Sound_Data_SFX_15:              ; $BD6D
;.incbin "sound/sfx_15.bin"
.include "sound/sfx_15.asm"

Sound_Data_SFX_16:              ; $BD8D
;.incbin "sound/sfx_16.bin"
.include "sound/sfx_16.asm"

Sound_Data_SFX_17:              ; $BDC0
;.incbin "sound/sfx_17.bin"
.include "sound/sfx_17.asm"

Sound_Data_SFX_18:              ; $BDE0
;.incbin "sound/sfx_18.bin"
.include "sound/sfx_18.asm"

Sound_Data_SFX_19:              ; $BE0D
;.incbin "sound/sfx_19.bin"
.include "sound/sfx_19.asm"

Sound_Data_SFX_1A:              ; $BE2A 
    Stop

Sound_Data_SFX_1B:              ; $BE2F
.db $FF
