pokered/audio/engine_1.asm
2016-07-27 22:08:13 -05:00

1728 lines
32 KiB
NASM

; The first of three duplicated sound engines.
Audio1_UpdateMusic::
ld c, Ch0
.loop
ld b, 0
ld hl, wChannelSoundIDs
add hl, bc
ld a, [hl]
and a
jr z, .nextChannel
ld a, c
cp Ch4
jr nc, .applyAffects ; if sfx channel
ld a, [wMuteAudioAndPauseMusic]
and a
jr z, .applyAffects
bit 7, a
jr nz, .nextChannel
set 7, a
ld [wMuteAudioAndPauseMusic], a
xor a ; disable all channels' output
ld [rNR51], a
ld [rNR30], a
ld a, $80
ld [rNR30], a
jr .nextChannel
.applyAffects
call Audio1_ApplyMusicAffects
.nextChannel
ld a, c
inc c ; inc channel number
cp Ch7
jr nz, .loop
ret
; this routine checks flags for music effects currently applied
; to the channel and calls certain functions based on flags.
Audio1_ApplyMusicAffects:
ld b, $0
ld hl, wChannelNoteDelayCounters ; delay until next note
add hl, bc
ld a, [hl]
cp $1 ; if the delay is 1, play next note
jp z, Audio1_PlayNextNote
dec a ; otherwise, decrease the delay timer
ld [hl], a
ld a, c
cp Ch4
jr nc, .startChecks ; if a sfx channel
ld hl, wChannelSoundIDs + Ch4
add hl, bc
ld a, [hl]
and a
jr z, .startChecks
ret
.startChecks
ld hl, wChannelFlags1
add hl, bc
bit BIT_ROTATE_DUTY, [hl]
jr z, .checkForExecuteMusic
call Audio1_ApplyDutyCycle
.checkForExecuteMusic
ld b, 0
ld hl, wChannelFlags2
add hl, bc
bit BIT_EXECUTE_MUSIC, [hl]
jr nz, .checkForPitchBend
ld hl, wChannelFlags1
add hl, bc
bit BIT_NOISE_OR_SFX, [hl]
jr nz, .skipPitchBendVibrato
.checkForPitchBend
ld hl, wChannelFlags1
add hl, bc
bit BIT_PITCH_BEND_ON, [hl]
jr z, .checkVibratoDelay
jp Audio1_ApplyPitchBend
.checkVibratoDelay
ld hl, wChannelVibratoDelayCounters
add hl, bc
ld a, [hl]
and a ; check if delay is over
jr z, .checkForVibrato
dec [hl] ; otherwise, dec delay
.skipPitchBendVibrato
ret
.checkForVibrato
ld hl, wChannelVibratoExtents
add hl, bc
ld a, [hl]
and a
jr nz, .vibrato
ret ; no vibrato
.vibrato
ld d, a
ld hl, wChannelVibratoRates
add hl, bc
ld a, [hl]
and $f
and a
jr z, .applyVibrato
dec [hl] ; decrement counter
ret
.applyVibrato
ld a, [hl]
swap [hl]
or [hl]
ld [hl], a ; reload the counter
ld hl, wChannelFrequencyLowBytes
add hl, bc
ld e, [hl] ; get note pitch
ld hl, wChannelFlags1
add hl, bc
; This is the only code that sets/resets the vibrato direction bit, so it
; continuously alternates which path it takes.
bit BIT_VIBRATO_DIRECTION, [hl]
jr z, .unset
res BIT_VIBRATO_DIRECTION, [hl]
ld a, d
and $f
ld d, a
ld a, e
sub d
jr nc, .noCarry
ld a, 0
.noCarry
jr .done
.unset
set BIT_VIBRATO_DIRECTION, [hl]
ld a, d
and $f0
swap a
add e
jr nc, .done
ld a, $ff
.done
ld d, a
ld b, REG_FREQUENCY_LO
call Audio1_GetRegisterPointer
ld [hl], d
ret
; this routine executes all music commands that take up no time,
; like tempo changes, duty changes etc. and doesn't return
; until the first note is reached
Audio1_PlayNextNote:
; reload the vibrato delay counter
ld hl, wChannelVibratoDelayCounterReloadValues
add hl, bc
ld a, [hl]
ld hl, wChannelVibratoDelayCounters
add hl, bc
ld [hl], a
ld hl, wChannelFlags1
add hl, bc
res BIT_PITCH_BEND_ON, [hl]
res BIT_PITCH_BEND_DECREASING, [hl]
call Audio1_endchannel
ret
Audio1_endchannel:
call Audio1_GetNextMusicByte
ld d, a
cp $ff ; is this command an endchannel?
jp nz, Audio1_callchannel ; no
ld b, 0
ld hl, wChannelFlags1
add hl, bc
bit BIT_CHANNEL_CALL, [hl]
jr nz, .returnFromCall
ld a, c
cp Ch3
jr nc, .noiseOrSfxChannel
jr .disableChannelOutput
.noiseOrSfxChannel
res BIT_NOISE_OR_SFX, [hl]
ld hl, wChannelFlags2
add hl, bc
res BIT_EXECUTE_MUSIC, [hl]
cp Ch6
jr nz, .skipSfxChannel3
; restart hardware channel 3 (wave channel) output
ld a, $0
ld [rNR30], a
ld a, $80
ld [rNR30], a
.skipSfxChannel3
jr nz, .asm_9222
ld a, [wDisableChannelOutputWhenSfxEnds]
and a
jr z, .asm_9222
xor a
ld [wDisableChannelOutputWhenSfxEnds], a
jr .disableChannelOutput
.asm_9222
jr .asm_9248
.returnFromCall
res 1, [hl]
ld d, $0
ld a, c
add a
ld e, a
ld hl, wChannelCommandPointers
add hl, de
push hl ; store current channel address
ld hl, wChannelReturnAddresses
add hl, de
ld e, l
ld d, h
pop hl
ld a, [de]
ld [hli], a
inc de
ld a, [de]
ld [hl], a ; loads channel address to return to
jp Audio1_endchannel
.disableChannelOutput
ld hl, Audio1_HWChannelDisableMasks
add hl, bc
ld a, [rNR51]
and [hl]
ld [rNR51], a
.asm_9248
ld a, [wChannelSoundIDs + Ch4]
cp CRY_SFX_START
jr nc, .asm_9251
jr .skipCry
.asm_9251
ld a, [wChannelSoundIDs + Ch4]
cp CRY_SFX_END
jr z, .skipCry
jr c, .cry
jr .skipCry
.cry
ld a, c
cp Ch4
jr z, .asm_9265
call Audio1_GoBackOneCommandIfCry
ret c
.asm_9265
ld a, [wSavedVolume]
ld [rNR50], a
xor a
ld [wSavedVolume], a
.skipCry
ld hl, wChannelSoundIDs
add hl, bc
ld [hl], b
ret
Audio1_callchannel:
cp $fd ; is this command a callchannel?
jp nz, Audio1_loopchannel ; no
call Audio1_GetNextMusicByte
push af
call Audio1_GetNextMusicByte
ld d, a
pop af
ld e, a
push de ; store pointer
ld d, $0
ld a, c
add a
ld e, a
ld hl, wChannelCommandPointers
add hl, de
push hl
ld hl, wChannelReturnAddresses
add hl, de
ld e, l
ld d, h
pop hl
ld a, [hli]
ld [de], a
inc de
ld a, [hld]
ld [de], a ; copy current channel address
pop de
ld [hl], e
inc hl
ld [hl], d ; overwrite current address with pointer
ld b, $0
ld hl, wChannelFlags1
add hl, bc
set BIT_CHANNEL_CALL, [hl] ; set the call flag
jp Audio1_endchannel
Audio1_loopchannel:
cp $fe ; is this command a loopchannel?
jp nz, Audio1_notetype ; no
call Audio1_GetNextMusicByte
ld e, a
and a
jr z, .infiniteLoop
ld b, 0
ld hl, wChannelLoopCounters
add hl, bc
ld a, [hl]
cp e
jr nz, .loopAgain
ld a, $1 ; if no more loops to make,
ld [hl], a
call Audio1_GetNextMusicByte ; skip pointer
call Audio1_GetNextMusicByte
jp Audio1_endchannel
.loopAgain ; inc loop count
inc a
ld [hl], a
; fall through
.infiniteLoop ; overwrite current address with pointer
call Audio1_GetNextMusicByte
push af
call Audio1_GetNextMusicByte
ld b, a
ld d, $0
ld a, c
add a
ld e, a
ld hl, wChannelCommandPointers
add hl, de
pop af
ld [hli], a
ld [hl], b
jp Audio1_endchannel
Audio1_notetype:
and $f0
cp $d0 ; is this command a notetype?
jp nz, Audio1_toggleperfectpitch ; no
ld a, d
and $f
ld b, $0
ld hl, wChannelNoteSpeeds
add hl, bc
ld [hl], a ; store low nibble as speed
ld a, c
cp Ch3
jr z, .noiseChannel ; noise channel has 0 params
call Audio1_GetNextMusicByte
ld d, a
ld a, c
cp Ch2
jr z, .musicChannel3
cp Ch6
jr nz, .skipChannel3
ld hl, wSfxWaveInstrument
jr .channel3
.musicChannel3
ld hl, wMusicWaveInstrument
.channel3
ld a, d
and $f
ld [hl], a ; store low nibble of param as wave instrument
ld a, d
and $30
sla a
ld d, a
; fall through
; if channel 3, store high nibble as volume
; else, store volume (high nibble) and fade (low nibble)
.skipChannel3
ld b, 0
ld hl, wChannelVolumes
add hl, bc
ld [hl], d
.noiseChannel
jp Audio1_endchannel
Audio1_toggleperfectpitch:
ld a, d
cp $e8 ; is this command a toggleperfectpitch?
jr nz, Audio1_vibrato ; no
ld b, 0
ld hl, wChannelFlags1
add hl, bc
ld a, [hl]
xor $1
ld [hl], a ; flip bit 0 of wChannelFlags1
jp Audio1_endchannel
Audio1_vibrato:
cp $ea ; is this command a vibrato?
jr nz, Audio1_pitchbend ; no
call Audio1_GetNextMusicByte
ld b, 0
ld hl, wChannelVibratoDelayCounters
add hl, bc
ld [hl], a ; store delay
ld hl, wChannelVibratoDelayCounterReloadValues
add hl, bc
ld [hl], a ; store delay
call Audio1_GetNextMusicByte
ld d, a
; The high nybble of the command byte is the extent of the vibrato.
; Let n be the extent.
; The upper nybble of the channel's byte in the wChannelVibratoExtents
; array will store the extent above the note: (n / 2) + (n % 2).
; The lower nybble will store the extent below the note: (n / 2).
; These two values add to the total extent, n.
and $f0
swap a
ld b, 0
ld hl, wChannelVibratoExtents
add hl, bc
srl a
ld e, a
adc b
swap a
or e
ld [hl], a
; The low nybble of the command byte is the rate of the vibrato.
; The high and low nybbles of the channel's byte in the wChannelVibratoRates
; array are both initialised to this value because the high nybble is the
; counter reload value and the low nybble is the counter itself, which should
; start at its value upon reload.
ld a, d
and $f
ld d, a
ld hl, wChannelVibratoRates
add hl, bc
swap a
or d
ld [hl], a
jp Audio1_endchannel
Audio1_pitchbend:
cp $eb ; is this command a pitchbend?
jr nz, Audio1_duty ; no
call Audio1_GetNextMusicByte
ld b, 0
ld hl, wChannelPitchBendLengthModifiers
add hl, bc
ld [hl], a
call Audio1_GetNextMusicByte
ld d, a
and $f0
swap a
ld b, a
ld a, d
and $f
call Audio1_CalculateFrequency
ld b, 0
ld hl, wChannelPitchBendTargetFrequencyHighBytes
add hl, bc
ld [hl], d
ld hl, wChannelPitchBendTargetFrequencyLowBytes
add hl, bc
ld [hl], e
ld b, 0
ld hl, wChannelFlags1
add hl, bc
set BIT_PITCH_BEND_ON, [hl]
call Audio1_GetNextMusicByte
ld d, a
jp Audio1_notelength
Audio1_duty:
cp $ec ; is this command a duty?
jr nz, Audio1_tempo ; no
call Audio1_GetNextMusicByte
rrca
rrca
and $c0
ld b, 0
ld hl, wChannelDuties
add hl, bc
ld [hl], a ; store duty
jp Audio1_endchannel
Audio1_tempo:
cp $ed ; is this command a tempo?
jr nz, Audio1_stereopanning ; no
ld a, c
cp Ch4
jr nc, .sfxChannel
call Audio1_GetNextMusicByte
ld [wMusicTempo], a ; store first param
call Audio1_GetNextMusicByte
ld [wMusicTempo + 1], a ; store second param
xor a
ld [wChannelNoteDelayCountersFractionalPart], a ; clear RAM
ld [wChannelNoteDelayCountersFractionalPart + 1], a
ld [wChannelNoteDelayCountersFractionalPart + 2], a
ld [wChannelNoteDelayCountersFractionalPart + 3], a
jr .musicChannelDone
.sfxChannel
call Audio1_GetNextMusicByte
ld [wSfxTempo], a ; store first param
call Audio1_GetNextMusicByte
ld [wSfxTempo + 1], a ; store second param
xor a
ld [wChannelNoteDelayCountersFractionalPart + 4], a ; clear RAM
ld [wChannelNoteDelayCountersFractionalPart + 5], a
ld [wChannelNoteDelayCountersFractionalPart + 6], a
ld [wChannelNoteDelayCountersFractionalPart + 7], a
.musicChannelDone
jp Audio1_endchannel
Audio1_stereopanning:
cp $ee ; is this command a stereopanning?
jr nz, Audio1_unknownmusic0xef ; no
call Audio1_GetNextMusicByte
ld [wStereoPanning], a ; store panning
jp Audio1_endchannel
; this appears to never be used
Audio1_unknownmusic0xef:
cp $ef ; is this command an unknownmusic0xef?
jr nz, Audio1_dutycycle ; no
call Audio1_GetNextMusicByte
push bc
call Audio1_PlaySound
pop bc
ld a, [wDisableChannelOutputWhenSfxEnds]
and a
jr nz, .skip
ld a, [wChannelSoundIDs + Ch7]
ld [wDisableChannelOutputWhenSfxEnds], a
xor a
ld [wChannelSoundIDs + Ch7], a
.skip
jp Audio1_endchannel
Audio1_dutycycle:
cp $fc ; is this command a dutycycle?
jr nz, Audio1_volume ; no
call Audio1_GetNextMusicByte
ld b, 0
ld hl, wChannelDutyCycles
add hl, bc
ld [hl], a ; store full cycle
and $c0
ld hl, wChannelDuties
add hl, bc
ld [hl], a ; store first duty
ld hl, wChannelFlags1
add hl, bc
set BIT_ROTATE_DUTY, [hl]
jp Audio1_endchannel
Audio1_volume:
cp $f0 ; is this command a volume?
jr nz, Audio1_executemusic ; no
call Audio1_GetNextMusicByte
ld [rNR50], a ; store volume
jp Audio1_endchannel
Audio1_executemusic:
cp $f8 ; is this command an executemusic?
jr nz, Audio1_octave ; no
ld b, $0
ld hl, wChannelFlags2
add hl, bc
set BIT_EXECUTE_MUSIC, [hl]
jp Audio1_endchannel
Audio1_octave:
and $f0
cp $e0 ; is this command an octave?
jr nz, Audio1_unknownsfx0x20 ; no
ld hl, wChannelOctaves
ld b, 0
add hl, bc
ld a, d
and $f
ld [hl], a ; store low nibble as octave
jp Audio1_endchannel
Audio1_unknownsfx0x20:
cp $20 ; is this command an unknownsfx0x20?
jr nz, Audio1_unknownsfx0x10
ld a, c
cp Ch3 ; is this a noise or sfx channel?
jr c, Audio1_unknownsfx0x10 ; no
ld b, 0
ld hl, wChannelFlags2
add hl, bc
bit BIT_EXECUTE_MUSIC, [hl] ; is executemusic being used?
jr nz, Audio1_unknownsfx0x10 ; yes
call Audio1_notelength
; This code seems to do the same thing as what Audio1_ApplyDutyAndSoundLength
; does below.
ld d, a
ld b, 0
ld hl, wChannelDuties
add hl, bc
ld a, [hl]
or d
ld d, a
ld b, REG_DUTY_SOUND_LEN
call Audio1_GetRegisterPointer
ld [hl], d
call Audio1_GetNextMusicByte
ld d, a
ld b, REG_VOLUME_ENVELOPE
call Audio1_GetRegisterPointer
ld [hl], d
call Audio1_GetNextMusicByte
ld e, a
ld a, c
cp Ch7
ld a, 0
jr z, .skip
; Channels 1 through 3 have 2 registers that control frequency, but the noise
; channel a single register (the polynomial counter) that controls frequency,
; so this command has one less byte on the noise channel.
push de
call Audio1_GetNextMusicByte
pop de
.skip
ld d, a
push de
call Audio1_ApplyDutyAndSoundLength
call Audio1_EnableChannelOutput
pop de
call Audio1_ApplyWavePatternAndFrequency
ret
Audio1_unknownsfx0x10:
ld a, c
cp Ch4
jr c, Audio1_note ; if not a sfx
ld a, d
cp $10 ; is this command a unknownsfx0x10?
jr nz, Audio1_note ; no
ld b, $0
ld hl, wChannelFlags2
add hl, bc
bit BIT_EXECUTE_MUSIC, [hl]
jr nz, Audio1_note ; no
call Audio1_GetNextMusicByte
ld [rNR10], a
jp Audio1_endchannel
Audio1_note:
ld a, c
cp Ch3
jr nz, Audio1_notelength ; if not noise channel
ld a, d
and $f0
cp $b0 ; is this command a dnote?
jr z, Audio1_dnote
jr nc, Audio1_notelength ; no
swap a
ld b, a
ld a, d
and $f
ld d, a
ld a, b
push de
push bc
jr asm_94fd
Audio1_dnote:
ld a, d
and $f
push af
push bc
call Audio1_GetNextMusicByte ; get dnote instrument
asm_94fd
ld d, a
ld a, [wDisableChannelOutputWhenSfxEnds]
and a
jr nz, .asm_9508
ld a, d
call Audio1_PlaySound
.asm_9508
pop bc
pop de
Audio1_notelength:
ld a, d
push af
and $f
inc a
ld b, 0
ld e, a ; store note length (in 16ths)
ld d, b
ld hl, wChannelNoteSpeeds
add hl, bc
ld a, [hl]
ld l, b
call Audio1_MultiplyAdd
ld a, c
cp Ch4
jr nc, .sfxChannel
ld a, [wMusicTempo]
ld d, a
ld a, [wMusicTempo + 1]
ld e, a
jr .skip
.sfxChannel
ld d, $1
ld e, $0
cp Ch7
jr z, .skip ; if noise channel
call Audio1_SetSfxTempo
ld a, [wSfxTempo]
ld d, a
ld a, [wSfxTempo + 1]
ld e, a
.skip
ld a, l ; a = note_length * note_speed
ld b, 0
ld hl, wChannelNoteDelayCountersFractionalPart
add hl, bc
ld l, [hl]
call Audio1_MultiplyAdd
ld e, l
ld d, h ; de = note_delay_frac_part + (note_length * note_speed * tempo)
ld hl, wChannelNoteDelayCountersFractionalPart
add hl, bc
ld [hl], e
ld a, d
ld hl, wChannelNoteDelayCounters
add hl, bc
ld [hl], a
ld hl, wChannelFlags2
add hl, bc
bit BIT_EXECUTE_MUSIC, [hl]
jr nz, Audio1_notepitch
ld hl, wChannelFlags1
add hl, bc
bit BIT_NOISE_OR_SFX, [hl]
jr z, Audio1_notepitch
pop hl
ret
Audio1_notepitch:
pop af
and $f0
cp $c0 ; compare to rest
jr nz, .notRest
ld a, c
cp Ch4
jr nc, .next
; If this isn't an SFX channel, try the corresponding SFX channel.
ld hl, wChannelSoundIDs + Ch4
add hl, bc
ld a, [hl]
and a
jr nz, .done
; fall through
.next
ld a, c
cp Ch2
jr z, .channel3
cp Ch6
jr nz, .notChannel3
.channel3
ld b, 0
ld hl, Audio1_HWChannelDisableMasks
add hl, bc
ld a, [rNR51]
and [hl]
ld [rNR51], a ; disable hardware channel 3's output
jr .done
.notChannel3
ld b, REG_VOLUME_ENVELOPE
call Audio1_GetRegisterPointer
ld a, $8 ; fade in sound
ld [hli], a
inc hl
ld a, $80 ; restart sound
ld [hl], a
.done
ret
.notRest
swap a
ld b, 0
ld hl, wChannelOctaves
add hl, bc
ld b, [hl]
call Audio1_CalculateFrequency
ld b, 0
ld hl, wChannelFlags1
add hl, bc
bit BIT_PITCH_BEND_ON, [hl]
jr z, .skipPitchBend
call Audio1_InitPitchBendVars
.skipPitchBend
push de
ld a, c
cp Ch4
jr nc, .sfxChannel ; if sfx channel
; If this isn't an SFX channel, try the corresponding SFX channel.
ld hl, wChannelSoundIDs + Ch4
ld d, 0
ld e, a
add hl, de
ld a, [hl]
and a
jr nz, .noSfx
jr .sfxChannel
.noSfx
pop de
ret
.sfxChannel
ld b, 0
ld hl, wChannelVolumes
add hl, bc
ld d, [hl]
ld b, REG_VOLUME_ENVELOPE
call Audio1_GetRegisterPointer
ld [hl], d
call Audio1_ApplyDutyAndSoundLength
call Audio1_EnableChannelOutput
pop de
ld b, $0
ld hl, wChannelFlags1
add hl, bc
bit BIT_PERFECT_PITCH, [hl] ; has toggleperfectpitch been used?
jr z, .skipFrequencyInc
inc e ; if yes, increment the frequency by 1
jr nc, .skipFrequencyInc
inc d
.skipFrequencyInc
ld hl, wChannelFrequencyLowBytes
add hl, bc
ld [hl], e
call Audio1_ApplyWavePatternAndFrequency
ret
Audio1_EnableChannelOutput:
ld b, 0
ld hl, Audio1_HWChannelEnableMasks
add hl, bc
ld a, [rNR51]
or [hl] ; set this channel's bits
ld d, a
ld a, c
cp Ch7
jr z, .noiseChannelOrNoSfx
cp Ch4
jr nc, .skip ; if sfx channel
; If this isn't an SFX channel, try the corresponding SFX channel.
ld hl, wChannelSoundIDs + Ch4
add hl, bc
ld a, [hl]
and a
jr nz, .skip
.noiseChannelOrNoSfx
; If this is the SFX noise channel or a music channel whose corresponding
; SFX channel is off, apply stereo panning.
ld a, [wStereoPanning]
ld hl, Audio1_HWChannelEnableMasks
add hl, bc
and [hl]
ld d, a
ld a, [rNR51]
ld hl, Audio1_HWChannelDisableMasks
add hl, bc
and [hl] ; reset this channel's output bits
or d ; set this channel's output bits that enabled in [wStereoPanning]
ld d, a
.skip
ld a, d
ld [rNR51], a
ret
Audio1_ApplyDutyAndSoundLength:
ld b, 0
ld hl, wChannelNoteDelayCounters ; use the note delay as sound length
add hl, bc
ld d, [hl]
ld a, c
cp Ch2
jr z, .skipDuty ; if music channel 3
cp Ch6
jr z, .skipDuty ; if sfx channel 3
; include duty (except on channel 3 which doesn't have it)
ld a, d
and $3f
ld d, a
ld hl, wChannelDuties
add hl, bc
ld a, [hl]
or d
ld d, a
.skipDuty
ld b, REG_DUTY_SOUND_LEN
call Audio1_GetRegisterPointer
ld [hl], d
ret
Audio1_ApplyWavePatternAndFrequency:
ld a, c
cp Ch2
jr z, .channel3
cp Ch6
jr nz, .notChannel3
; fall through
.channel3
push de
ld de, wMusicWaveInstrument
cp Ch2
jr z, .next
ld de, wSfxWaveInstrument
.next
ld a, [de]
add a
ld d, 0
ld e, a
ld hl, Audio1_WavePointers
add hl, de
ld e, [hl]
inc hl
ld d, [hl]
ld hl, $ff30 ; wave pattern RAM
ld b, $f
ld a, $0 ; stop hardware channel 3
ld [rNR30], a
.loop
ld a, [de]
inc de
ld [hli], a
ld a, b
dec b
and a
jr nz, .loop
ld a, $80 ; start hardware channel 3
ld [rNR30], a
pop de
.notChannel3
ld a, d
or $80 ; use counter mode (i.e. disable output when the counter reaches 0)
and $c7 ; zero the unused bits in the register
ld d, a
ld b, REG_FREQUENCY_LO
call Audio1_GetRegisterPointer
ld [hl], e ; store frequency low byte
inc hl
ld [hl], d ; store frequency high byte
call Audio1_ApplyFrequencyModifier
ret
Audio1_SetSfxTempo:
call Audio1_IsCry
jr nc, .notCry
ld d, 0
ld a, [wTempoModifier]
add $80
jr nc, .next
inc d
.next
ld [wSfxTempo + 1], a
ld a, d
ld [wSfxTempo], a
jr .done
.notCry
xor a
ld [wSfxTempo + 1], a
ld a, $1
ld [wSfxTempo], a
.done
ret
Audio1_ApplyFrequencyModifier:
call Audio1_IsCry
jr nc, .done
; if playing a cry, add the cry's frequency modifier
ld a, [wFrequencyModifier]
add e
jr nc, .noCarry
inc d
.noCarry
dec hl
ld e, a
ld [hl], e
inc hl
ld [hl], d
.done
ret
Audio1_GoBackOneCommandIfCry:
call Audio1_IsCry
jr nc, .done
ld hl, wChannelCommandPointers
ld e, c
ld d, 0
sla e
rl d
add hl, de
ld a, [hl]
sub 1
ld [hl], a
inc hl
ld a, [hl]
sbc 0
ld [hl], a
scf
ret
.done
scf
ccf
ret
Audio1_IsCry:
; Returns whether the currently playing audio is a cry in carry.
ld a, [wChannelSoundIDs + Ch4]
cp CRY_SFX_START
jr nc, .next
jr .no
.next
cp CRY_SFX_END
jr z, .no
jr c, .yes
.no
scf
ccf
ret
.yes
scf
ret
Audio1_ApplyPitchBend:
ld hl, wChannelFlags1
add hl, bc
bit BIT_PITCH_BEND_DECREASING, [hl]
jp nz, .frequencyDecreasing
; frequency increasing
ld hl, wChannelPitchBendCurrentFrequencyLowBytes
add hl, bc
ld e, [hl]
ld hl, wChannelPitchBendCurrentFrequencyHighBytes
add hl, bc
ld d, [hl]
ld hl, wChannelPitchBendFrequencySteps
add hl, bc
ld l, [hl]
ld h, b
add hl, de
ld d, h
ld e, l
ld hl, wChannelPitchBendCurrentFrequencyFractionalPart
add hl, bc
push hl
ld hl, wChannelPitchBendFrequencyStepsFractionalPart
add hl, bc
ld a, [hl]
pop hl
add [hl]
ld [hl], a
ld a, 0
adc e
ld e, a
ld a, 0
adc d
ld d, a
ld hl, wChannelPitchBendTargetFrequencyHighBytes
add hl, bc
ld a, [hl]
cp d
jp c, .reachedTargetFrequency
jr nz, .applyUpdatedFrequency
ld hl, wChannelPitchBendTargetFrequencyLowBytes
add hl, bc
ld a, [hl]
cp e
jp c, .reachedTargetFrequency
jr .applyUpdatedFrequency
.frequencyDecreasing
ld hl, wChannelPitchBendCurrentFrequencyLowBytes
add hl, bc
ld a, [hl]
ld hl, wChannelPitchBendCurrentFrequencyHighBytes
add hl, bc
ld d, [hl]
ld hl, wChannelPitchBendFrequencySteps
add hl, bc
ld e, [hl]
sub e
ld e, a
ld a, d
sbc b
ld d, a
ld hl, wChannelPitchBendFrequencyStepsFractionalPart
add hl, bc
ld a, [hl]
add a
ld [hl], a
ld a, e
sbc b
ld e, a
ld a, d
sbc b
ld d, a
ld hl, wChannelPitchBendTargetFrequencyHighBytes
add hl, bc
ld a, d
cp [hl]
jr c, .reachedTargetFrequency
jr nz, .applyUpdatedFrequency
ld hl, wChannelPitchBendTargetFrequencyLowBytes
add hl, bc
ld a, e
cp [hl]
jr c, .reachedTargetFrequency
.applyUpdatedFrequency
ld hl, wChannelPitchBendCurrentFrequencyLowBytes
add hl, bc
ld [hl], e
ld hl, wChannelPitchBendCurrentFrequencyHighBytes
add hl, bc
ld [hl], d
ld b, REG_FREQUENCY_LO
call Audio1_GetRegisterPointer
ld a, e
ld [hli], a
ld [hl], d
ret
.reachedTargetFrequency
; Turn off pitch bend when the target frequency has been reached.
ld hl, wChannelFlags1
add hl, bc
res BIT_PITCH_BEND_ON, [hl]
res BIT_PITCH_BEND_DECREASING, [hl]
ret
Audio1_InitPitchBendVars:
ld hl, wChannelPitchBendCurrentFrequencyHighBytes
add hl, bc
ld [hl], d
ld hl, wChannelPitchBendCurrentFrequencyLowBytes
add hl, bc
ld [hl], e
ld hl, wChannelNoteDelayCounters
add hl, bc
ld a, [hl]
ld hl, wChannelPitchBendLengthModifiers
add hl, bc
sub [hl]
jr nc, .next
ld a, 1
.next
ld [hl], a
ld hl, wChannelPitchBendTargetFrequencyLowBytes
add hl, bc
ld a, e
sub [hl]
ld e, a
ld a, d
sbc b
ld hl, wChannelPitchBendTargetFrequencyHighBytes
add hl, bc
sub [hl]
jr c, .targetFrequencyGreater
ld d, a
ld b, 0
ld hl, wChannelFlags1
add hl, bc
set BIT_PITCH_BEND_DECREASING, [hl]
jr .next2
.targetFrequencyGreater
; If the target frequency is greater, subtract the current frequency from
; the target frequency to get the absolute difference.
ld hl, wChannelPitchBendCurrentFrequencyHighBytes
add hl, bc
ld d, [hl]
ld hl, wChannelPitchBendCurrentFrequencyLowBytes
add hl, bc
ld e, [hl]
ld hl, wChannelPitchBendTargetFrequencyLowBytes
add hl, bc
ld a, [hl]
sub e
ld e, a
; Bug. Instead of borrowing from the high byte of the target frequency as it
; should, it borrows from the high byte of the current frequency instead.
; This means that the result will be 0x200 greater than it should be if the
; low byte of the current frequency is greater than the low byte of the
; target frequency.
ld a, d
sbc b
ld d, a
ld hl, wChannelPitchBendTargetFrequencyHighBytes
add hl, bc
ld a, [hl]
sub d
ld d, a
ld b, 0
ld hl, wChannelFlags1
add hl, bc
res BIT_PITCH_BEND_DECREASING, [hl]
.next2
ld hl, wChannelPitchBendLengthModifiers
add hl, bc
.divideLoop
inc b
ld a, e
sub [hl]
ld e, a
jr nc, .divideLoop
ld a, d
and a
jr z, .doneDividing
dec a
ld d, a
jr .divideLoop
.doneDividing
ld a, e ; a = remainder - dividend
add [hl]
ld d, b ; d = quotient + 1
ld b, 0
ld hl, wChannelPitchBendFrequencySteps
add hl, bc
ld [hl], d ; store quotient + 1
ld hl, wChannelPitchBendFrequencyStepsFractionalPart
add hl, bc
ld [hl], a ; store remainder - dividend
ld hl, wChannelPitchBendCurrentFrequencyFractionalPart
add hl, bc
ld [hl], a ; store remainder - dividend
ret
Audio1_ApplyDutyCycle:
ld b, 0
ld hl, wChannelDutyCycles
add hl, bc
ld a, [hl]
rlca
rlca
ld [hl], a
and $c0
ld d, a
ld b, REG_DUTY_SOUND_LEN
call Audio1_GetRegisterPointer
ld a, [hl]
and $3f
or d
ld [hl], a
ret
Audio1_GetNextMusicByte:
ld d, 0
ld a, c
add a
ld e, a
ld hl, wChannelCommandPointers
add hl, de
ld a, [hli]
ld e, a
ld a, [hld]
ld d, a
ld a, [de] ; get next music command
inc de
ld [hl], e ; store address of next command
inc hl
ld [hl], d
ret
Audio1_GetRegisterPointer:
; hl = address of hardware sound register b for software channel c
ld a, c
ld hl, Audio1_HWChannelBaseAddresses
add l
jr nc, .noCarry
inc h
.noCarry
ld l, a
ld a, [hl]
add b
ld l, a
ld h, $ff
ret
Audio1_MultiplyAdd:
; hl = l + (a * de)
ld h, 0
.loop
srl a
jr nc, .skipAdd
add hl, de
.skipAdd
sla e
rl d
and a
jr z, .done
jr .loop
.done
ret
Audio1_CalculateFrequency:
; return the frequency for note a, octave b in de
ld h, 0
ld l, a
add hl, hl
ld d, h
ld e, l
ld hl, Audio1_Pitches
add hl, de
ld e, [hl]
inc hl
ld d, [hl]
ld a, b
.loop
cp 7
jr z, .done
sra d
rr e
inc a
jr .loop
.done
ld a, 8
add d
ld d, a
ret
Audio1_PlaySound::
ld [wSoundID], a
cp $ff
jp z, .stopAllAudio
cp MAX_SFX_ID
jp z, .playSfx
jp c, .playSfx
cp $fe
jr z, .playMusic
jp nc, .playSfx
.playMusic
xor a
ld [wUnusedC000], a
ld [wDisableChannelOutputWhenSfxEnds], a
ld [wMusicTempo + 1], a
ld [wMusicWaveInstrument], a
ld [wSfxWaveInstrument], a
ld d, $8
ld hl, wChannelReturnAddresses
call .FillMem
ld hl, wChannelCommandPointers
call .FillMem
ld d, $4
ld hl, wChannelSoundIDs
call .FillMem
ld hl, wChannelFlags1
call .FillMem
ld hl, wChannelDuties
call .FillMem
ld hl, wChannelDutyCycles
call .FillMem
ld hl, wChannelVibratoDelayCounters
call .FillMem
ld hl, wChannelVibratoExtents
call .FillMem
ld hl, wChannelVibratoRates
call .FillMem
ld hl, wChannelFrequencyLowBytes
call .FillMem
ld hl, wChannelVibratoDelayCounterReloadValues
call .FillMem
ld hl, wChannelFlags2
call .FillMem
ld hl, wChannelPitchBendLengthModifiers
call .FillMem
ld hl, wChannelPitchBendFrequencySteps
call .FillMem
ld hl, wChannelPitchBendFrequencyStepsFractionalPart
call .FillMem
ld hl, wChannelPitchBendCurrentFrequencyFractionalPart
call .FillMem
ld hl, wChannelPitchBendCurrentFrequencyHighBytes
call .FillMem
ld hl, wChannelPitchBendCurrentFrequencyLowBytes
call .FillMem
ld hl, wChannelPitchBendTargetFrequencyHighBytes
call .FillMem
ld hl, wChannelPitchBendTargetFrequencyLowBytes
call .FillMem
ld a, $1
ld hl, wChannelLoopCounters
call .FillMem
ld hl, wChannelNoteDelayCounters
call .FillMem
ld hl, wChannelNoteSpeeds
call .FillMem
ld [wMusicTempo], a
ld a, $ff
ld [wStereoPanning], a
xor a
ld [rNR50], a
ld a, $8
ld [rNR10], a
ld a, 0
ld [rNR51], a
xor a
ld [rNR30], a
ld a, $80
ld [rNR30], a
ld a, $77
ld [rNR50], a
jp .playSoundCommon
.playSfx
ld l, a
ld e, a
ld h, 0
ld d, h
add hl, hl
add hl, de
ld de, SFX_Headers_1
add hl, de
ld a, h
ld [wSfxHeaderPointer], a
ld a, l
ld [wSfxHeaderPointer + 1], a
ld a, [hl]
and $c0
rlca
rlca
ld c, a
.sfxChannelLoop
ld d, c
ld a, c
add a
add c
ld c, a
ld b, 0
ld a, [wSfxHeaderPointer]
ld h, a
ld a, [wSfxHeaderPointer + 1]
ld l, a
add hl, bc
ld c, d
ld a, [hl]
and $f
ld e, a ; software channel ID
ld d, 0
ld hl, wChannelSoundIDs
add hl, de
ld a, [hl]
and a
jr z, .asm_99a3
ld a, e
cp $7
jr nz, .asm_999a
ld a, [wSoundID]
cp $14
jr nc, .asm_9993
ret
.asm_9993
ld a, [hl]
cp $14
jr z, .asm_99a3
jr c, .asm_99a3
.asm_999a
ld a, [wSoundID]
cp [hl]
jr z, .asm_99a3
jr c, .asm_99a3
ret
.asm_99a3
xor a
push de
ld h, d
ld l, e
add hl, hl
ld d, h
ld e, l
ld hl, wChannelReturnAddresses
add hl, de
ld [hli], a
ld [hl], a
ld hl, wChannelCommandPointers
add hl, de
ld [hli], a
ld [hl], a
pop de
ld hl, wChannelSoundIDs
add hl, de
ld [hl], a
ld hl, wChannelFlags1
add hl, de
ld [hl], a
ld hl, wChannelDuties
add hl, de
ld [hl], a
ld hl, wChannelDutyCycles
add hl, de
ld [hl], a
ld hl, wChannelVibratoDelayCounters
add hl, de
ld [hl], a
ld hl, wChannelVibratoExtents
add hl, de
ld [hl], a
ld hl, wChannelVibratoRates
add hl, de
ld [hl], a
ld hl, wChannelFrequencyLowBytes
add hl, de
ld [hl], a
ld hl, wChannelVibratoDelayCounterReloadValues
add hl, de
ld [hl], a
ld hl, wChannelPitchBendLengthModifiers
add hl, de
ld [hl], a
ld hl, wChannelPitchBendFrequencySteps
add hl, de
ld [hl], a
ld hl, wChannelPitchBendFrequencyStepsFractionalPart
add hl, de
ld [hl], a
ld hl, wChannelPitchBendCurrentFrequencyFractionalPart
add hl, de
ld [hl], a
ld hl, wChannelPitchBendCurrentFrequencyHighBytes
add hl, de
ld [hl], a
ld hl, wChannelPitchBendCurrentFrequencyLowBytes
add hl, de
ld [hl], a
ld hl, wChannelPitchBendTargetFrequencyHighBytes
add hl, de
ld [hl], a
ld hl, wChannelPitchBendTargetFrequencyLowBytes
add hl, de
ld [hl], a
ld hl, wChannelFlags2
add hl, de
ld [hl], a
ld a, $1
ld hl, wChannelLoopCounters
add hl, de
ld [hl], a
ld hl, wChannelNoteDelayCounters
add hl, de
ld [hl], a
ld hl, wChannelNoteSpeeds
add hl, de
ld [hl], a
ld a, e
cp Ch4
jr nz, .asm_9a2b
ld a, $8
ld [rNR10], a ; sweep off
.asm_9a2b
ld a, c
and a
jp z, .playSoundCommon
dec c
jp .sfxChannelLoop
.stopAllAudio
ld a, $80
ld [rNR52], a ; sound hardware on
ld [rNR30], a ; wave playback on
xor a
ld [rNR51], a ; no sound output
ld [rNR32], a ; mute channel 3 (wave channel)
ld a, $8
ld [rNR10], a ; sweep off
ld [rNR12], a ; mute channel 1 (pulse channel 1)
ld [rNR22], a ; mute channel 2 (pulse channel 2)
ld [rNR42], a ; mute channel 4 (noise channel)
ld a, $40
ld [rNR14], a ; counter mode
ld [rNR24], a
ld [rNR44], a
ld a, $77
ld [rNR50], a ; full volume
xor a
ld [wUnusedC000], a
ld [wDisableChannelOutputWhenSfxEnds], a
ld [wMuteAudioAndPauseMusic], a
ld [wMusicTempo + 1], a
ld [wSfxTempo + 1], a
ld [wMusicWaveInstrument], a
ld [wSfxWaveInstrument], a
ld d, $a0
ld hl, wChannelCommandPointers
call .FillMem
ld a, $1
ld d, $18
ld hl, wChannelNoteDelayCounters
call .FillMem
ld [wMusicTempo], a
ld [wSfxTempo], a
ld a, $ff
ld [wStereoPanning], a
ret
; fills d bytes at hl with a
.FillMem
ld b, d
.loop
ld [hli], a
dec b
jr nz, .loop
ret
.playSoundCommon
ld a, [wSoundID]
ld l, a
ld e, a
ld h, 0
ld d, h
add hl, hl
add hl, de
ld de, SFX_Headers_1
add hl, de
ld e, l
ld d, h
ld hl, wChannelCommandPointers
ld a, [de] ; get channel number
ld b, a
rlca
rlca
and $3
ld c, a
ld a, b
and $f
ld b, c
inc b
inc de
ld c, 0
.commandPointerLoop
cp c
jr z, .next
inc c
inc hl
inc hl
jr .commandPointerLoop
.next
push hl
push bc
push af
ld b, 0
ld c, a
ld hl, wChannelSoundIDs
add hl, bc
ld a, [wSoundID]
ld [hl], a
pop af
cp Ch3
jr c, .skipSettingFlag
ld hl, wChannelFlags1
add hl, bc
set BIT_NOISE_OR_SFX, [hl]
.skipSettingFlag
pop bc
pop hl
ld a, [de] ; get channel pointer
ld [hli], a
inc de
ld a, [de]
ld [hli], a
inc de
inc c
dec b
ld a, b
and a
ld a, [de]
inc de
jr nz, .commandPointerLoop
ld a, [wSoundID]
cp CRY_SFX_START
jr nc, .asm_9aeb
jr .done
.asm_9aeb
ld a, [wSoundID]
cp CRY_SFX_END
jr z, .done
jr c, .cry
jr .done
.cry
ld hl, wChannelSoundIDs + Ch4
ld [hli], a
ld [hli], a
ld [hli], a
ld [hl], a
ld hl, wChannelCommandPointers + Ch6 * 2 ; sfx wave channel pointer
ld de, Audio1_CryEndchannel
ld [hl], e
inc hl
ld [hl], d ; overwrite pointer to point to endchannel
ld a, [wSavedVolume]
and a
jr nz, .done
ld a, [rNR50]
ld [wSavedVolume], a
ld a, $77
ld [rNR50], a ; full volume
.done
ret
Audio1_CryEndchannel:
endchannel
Audio1_HWChannelBaseAddresses:
; the low bytes of each HW channel's base address
db HW_CH1_BASE, HW_CH2_BASE, HW_CH3_BASE, HW_CH4_BASE ; channels 0-3
db HW_CH1_BASE, HW_CH2_BASE, HW_CH3_BASE, HW_CH4_BASE ; channels 4-7
Audio1_HWChannelDisableMasks:
db HW_CH1_DISABLE_MASK, HW_CH2_DISABLE_MASK, HW_CH3_DISABLE_MASK, HW_CH4_DISABLE_MASK ; channels 0-3
db HW_CH1_DISABLE_MASK, HW_CH2_DISABLE_MASK, HW_CH3_DISABLE_MASK, HW_CH4_DISABLE_MASK ; channels 4-7
Audio1_HWChannelEnableMasks:
db HW_CH1_ENABLE_MASK, HW_CH2_ENABLE_MASK, HW_CH3_ENABLE_MASK, HW_CH4_ENABLE_MASK ; channels 0-3
db HW_CH1_ENABLE_MASK, HW_CH2_ENABLE_MASK, HW_CH3_ENABLE_MASK, HW_CH4_ENABLE_MASK ; channels 4-7
Audio1_Pitches:
dw $F82C ; C_
dw $F89D ; C#
dw $F907 ; D_
dw $F96B ; D#
dw $F9CA ; E_
dw $FA23 ; F_
dw $FA77 ; F#
dw $FAC7 ; G_
dw $FB12 ; G#
dw $FB58 ; A_
dw $FB9B ; A#
dw $FBDA ; B_