; The second of three duplicated sound engines. ; This copy has a few differences relating to battle sound effects ; and the low health alarm that plays in battle Audio2_UpdateMusic:: ld c, Ch1 .loop ld b, 0 ld hl, wChannelSoundIDs add hl, bc ld a, [hl] and a jr z, .nextChannel ld a, c cp Ch5 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 ldh [rNR51], a ldh [rNR30], a ld a, $80 ldh [rNR30], a jr .nextChannel .applyAffects call Audio2_ApplyMusicAffects .nextChannel ld a, c inc c ; inc channel number cp Ch8 jr nz, .loop ret ; this routine checks flags for music effects currently applied ; to the channel and calls certain functions based on flags. Audio2_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, Audio2_PlayNextNote dec a ; otherwise, decrease the delay timer ld [hl], a ld a, c cp Ch5 jr nc, .startChecks ; if a sfx channel ld hl, wChannelSoundIDs + Ch5 add hl, bc ld a, [hl] and a jr z, .startChecks ret .startChecks ld hl, wChannelFlags1 add hl, bc bit BIT_ROTATE_DUTY_CYCLE, [hl] jr z, .checkForExecuteMusic call Audio2_ApplyDutyCyclePattern .checkForExecuteMusic ld b, 0 ld hl, wChannelFlags2 add hl, bc bit BIT_EXECUTE_MUSIC, [hl] jr nz, .checkForPitchSlide ld hl, wChannelFlags1 add hl, bc bit BIT_NOISE_OR_SFX, [hl] jr nz, .skipPitchSlideVibrato .checkForPitchSlide ld hl, wChannelFlags1 add hl, bc bit BIT_PITCH_SLIDE_ON, [hl] jr z, .checkVibratoDelay jp Audio2_ApplyPitchSlide .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 .skipPitchSlideVibrato 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 Audio2_GetRegisterPointer ld [hl], d ret ; this routine executes all music commands that take up no time, ; like tempo changes, duty cycle changes etc. and doesn't return ; until the first note is reached Audio2_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_SLIDE_ON, [hl] res BIT_PITCH_SLIDE_DECREASING, [hl] ; --- this section is only present in this copy of the sound engine ld a, c cp Ch5 jr nz, .beginChecks ld a, [wLowHealthAlarm] ; low health alarm enabled? bit 7, a ret nz .beginChecks ; --- call Audio2_sound_ret ret Audio2_sound_ret: call Audio2_GetNextMusicByte ld d, a cp $ff ; is this command a sound_ret? jp nz, Audio2_sound_call ; no ld b, 0 ld hl, wChannelFlags1 add hl, bc bit BIT_SOUND_CALL, [hl] jr nz, .returnFromCall ld a, c cp Ch4 jr nc, .noiseOrSfxChannel jr .disableChannelOutput .noiseOrSfxChannel res BIT_NOISE_OR_SFX, [hl] ld hl, wChannelFlags2 add hl, bc res BIT_EXECUTE_MUSIC, [hl] cp Ch7 jr nz, .skipSfxChannel3 ; restart hardware channel 3 (wave channel) output ld a, $0 ldh [rNR30], a ld a, $80 ldh [rNR30], a .skipSfxChannel3 jr nz, .dontDisable ld a, [wDisableChannelOutputWhenSfxEnds] and a jr z, .dontDisable xor a ld [wDisableChannelOutputWhenSfxEnds], a jr .disableChannelOutput .dontDisable jr .afterDisable .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 Audio2_sound_ret .disableChannelOutput ld hl, Audio2_HWChannelDisableMasks add hl, bc ldh a, [rNR51] and [hl] ldh [rNR51], a .afterDisable ld a, [wChannelSoundIDs + Ch5] cp CRY_SFX_START jr nc, .maybeCry jr .skipCry .maybeCry ld a, [wChannelSoundIDs + Ch5] cp CRY_SFX_END jr z, .skipCry jr c, .cry jr .skipCry .cry ld a, c cp Ch5 jr z, .skipRewind call Audio2_GoBackOneCommandIfCry ret c .skipRewind ld a, [wSavedVolume] ldh [rNR50], a xor a ld [wSavedVolume], a .skipCry ld hl, wChannelSoundIDs add hl, bc ld [hl], b ret Audio2_sound_call: cp $fd ; is this command a sound_call? jp nz, Audio2_sound_loop ; no call Audio2_GetNextMusicByte push af call Audio2_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_SOUND_CALL, [hl] ; set the call flag jp Audio2_sound_ret Audio2_sound_loop: cp $fe ; is this command a sound_loop? jp nz, Audio2_note_type ; no call Audio2_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 Audio2_GetNextMusicByte ; skip pointer call Audio2_GetNextMusicByte jp Audio2_sound_ret .loopAgain ; inc loop count inc a ld [hl], a ; fall through .infiniteLoop ; overwrite current address with pointer call Audio2_GetNextMusicByte push af call Audio2_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 Audio2_sound_ret Audio2_note_type: and $f0 cp $d0 ; is this command a note_type? jp nz, Audio2_toggle_perfect_pitch ; 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 Ch4 jr z, .noiseChannel ; noise channel has 0 params call Audio2_GetNextMusicByte ld d, a ld a, c cp Ch3 jr z, .musicChannel3 cp Ch7 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 Audio2_sound_ret Audio2_toggle_perfect_pitch: ld a, d cp $e8 ; is this command a toggle_perfect_pitch? jr nz, Audio2_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 Audio2_sound_ret Audio2_vibrato: cp $ea ; is this command a vibrato? jr nz, Audio2_pitch_slide ; no call Audio2_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 Audio2_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 Audio2_sound_ret Audio2_pitch_slide: cp $eb ; is this command a pitch_slide? jr nz, Audio2_duty_cycle ; no call Audio2_GetNextMusicByte ld b, 0 ld hl, wChannelPitchSlideLengthModifiers add hl, bc ld [hl], a call Audio2_GetNextMusicByte ld d, a and $f0 swap a ld b, a ld a, d and $f call Audio2_CalculateFrequency ld b, 0 ld hl, wChannelPitchSlideTargetFrequencyHighBytes add hl, bc ld [hl], d ld hl, wChannelPitchSlideTargetFrequencyLowBytes add hl, bc ld [hl], e ld b, 0 ld hl, wChannelFlags1 add hl, bc set BIT_PITCH_SLIDE_ON, [hl] call Audio2_GetNextMusicByte ld d, a jp Audio2_note_length Audio2_duty_cycle: cp $ec ; is this command a duty_cycle? jr nz, Audio2_tempo ; no call Audio2_GetNextMusicByte rrca rrca and $c0 ld b, 0 ld hl, wChannelDutyCycles add hl, bc ld [hl], a ; store duty cycle jp Audio2_sound_ret Audio2_tempo: cp $ed ; is this command a tempo? jr nz, Audio2_stereo_panning ; no ld a, c cp Ch5 jr nc, .sfxChannel call Audio2_GetNextMusicByte ld [wMusicTempo], a ; store first param call Audio2_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 Audio2_GetNextMusicByte ld [wSfxTempo], a ; store first param call Audio2_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 Audio2_sound_ret Audio2_stereo_panning: cp $ee ; is this command a stereo_panning? jr nz, Audio2_unknownmusic0xef ; no call Audio2_GetNextMusicByte ld [wStereoPanning], a ; store panning jp Audio2_sound_ret ; this appears to never be used Audio2_unknownmusic0xef: cp $ef ; is this command an unknownmusic0xef? jr nz, Audio2_duty_cycle_pattern ; no call Audio2_GetNextMusicByte push bc call Audio2_PlaySound pop bc ld a, [wDisableChannelOutputWhenSfxEnds] and a jr nz, .skip ld a, [wChannelSoundIDs + Ch8] ld [wDisableChannelOutputWhenSfxEnds], a xor a ld [wChannelSoundIDs + Ch8], a .skip jp Audio2_sound_ret Audio2_duty_cycle_pattern: cp $fc ; is this command a duty_cycle_pattern? jr nz, Audio2_volume ; no call Audio2_GetNextMusicByte ld b, 0 ld hl, wChannelDutyCyclePatterns add hl, bc ld [hl], a ; store full pattern and %11000000 ld hl, wChannelDutyCycles add hl, bc ld [hl], a ; store first duty cycle ld hl, wChannelFlags1 add hl, bc set BIT_ROTATE_DUTY_CYCLE, [hl] jp Audio2_sound_ret Audio2_volume: cp $f0 ; is this command a volume? jr nz, Audio2_execute_music ; no call Audio2_GetNextMusicByte ldh [rNR50], a ; store volume jp Audio2_sound_ret Audio2_execute_music: cp $f8 ; is this command an execute_music? jr nz, Audio2_octave ; no ld b, $0 ld hl, wChannelFlags2 add hl, bc set BIT_EXECUTE_MUSIC, [hl] jp Audio2_sound_ret Audio2_octave: and $f0 cp $e0 ; is this command an octave? jr nz, Audio2_sfx_note ; no ld hl, wChannelOctaves ld b, 0 add hl, bc ld a, d and $f ld [hl], a ; store low nibble as octave jp Audio2_sound_ret ; sfx_note is either square_note or noise_note depending on the channel Audio2_sfx_note: cp $20 ; is this command a sfx_note? jr nz, Audio2_pitch_sweep ld a, c cp Ch4 ; is this a noise or sfx channel? jr c, Audio2_pitch_sweep ; no ld b, 0 ld hl, wChannelFlags2 add hl, bc bit BIT_EXECUTE_MUSIC, [hl] ; is execute_music being used? jr nz, Audio2_pitch_sweep ; yes call Audio2_note_length ; This code seems to do the same thing as what Audio2_ApplyDutyCycleAndSoundLength ; does below. ld d, a ld b, 0 ld hl, wChannelDutyCycles add hl, bc ld a, [hl] or d ld d, a ld b, REG_DUTY_SOUND_LEN call Audio2_GetRegisterPointer ld [hl], d call Audio2_GetNextMusicByte ld d, a ld b, REG_VOLUME_ENVELOPE call Audio2_GetRegisterPointer ld [hl], d call Audio2_GetNextMusicByte ld e, a ld a, c cp Ch8 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 Audio2_GetNextMusicByte pop de .skip ld d, a push de call Audio2_ApplyDutyCycleAndSoundLength call Audio2_EnableChannelOutput pop de call Audio2_ApplyWavePatternAndFrequency ret Audio2_pitch_sweep: ld a, c cp Ch5 jr c, Audio2_note ; if not a sfx ld a, d cp $10 ; is this command a pitch_sweep? jr nz, Audio2_note ; no ld b, $0 ld hl, wChannelFlags2 add hl, bc bit BIT_EXECUTE_MUSIC, [hl] jr nz, Audio2_note ; no call Audio2_GetNextMusicByte ldh [rNR10], a jp Audio2_sound_ret Audio2_note: ld a, c cp Ch4 jr nz, Audio2_note_length ; if not noise channel ld a, d and $f0 cp $b0 ; is this command a drum_note? jr z, .drum_note jr nc, Audio2_note_length ; no ; this executes when on the noise channel and ; the command id is less than $b0 ; in this case, the upper nybble is used as the noise instrument ($1-$a) ; and the lower nybble is the length minus 1 (0-15) ; however, this doesn't work for instrument #2 because the command id ; is captured by the noise_note command (command id $2x) ; this essentially acts like a drum_note command that is only 1 byte ; instead of 2 and can only be used with instruments 1 and 3 through 10 ; this is unused by the game swap a ld b, a ld a, d and $f ld d, a ld a, b push de push bc jr .playDnote .drum_note ld a, d and $f push af push bc call Audio2_GetNextMusicByte ; get drum_note instrument .playDnote ld d, a ld a, [wDisableChannelOutputWhenSfxEnds] and a jr nz, .skipDnote ld a, d call Audio2_PlaySound .skipDnote pop bc pop de Audio2_note_length: 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 Audio2_MultiplyAdd ld a, c cp Ch5 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 Ch8 jr z, .skip ; if noise channel call Audio2_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 Audio2_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, Audio2_note_pitch ld hl, wChannelFlags1 add hl, bc bit BIT_NOISE_OR_SFX, [hl] jr z, Audio2_note_pitch pop hl ret Audio2_note_pitch: pop af and $f0 cp $c0 ; compare to rest jr nz, .notRest ld a, c cp Ch5 jr nc, .next ; If this isn't an SFX channel, try the corresponding SFX channel. ld hl, wChannelSoundIDs + Ch5 add hl, bc ld a, [hl] and a jr nz, .done ; fall through .next ld a, c cp Ch3 jr z, .channel3 cp Ch7 jr nz, .notChannel3 .channel3 ld b, 0 ld hl, Audio2_HWChannelDisableMasks add hl, bc ldh a, [rNR51] and [hl] ldh [rNR51], a ; disable hardware channel 3's output jr .done .notChannel3 ld b, REG_VOLUME_ENVELOPE call Audio2_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 Audio2_CalculateFrequency ld b, 0 ld hl, wChannelFlags1 add hl, bc bit BIT_PITCH_SLIDE_ON, [hl] jr z, .skipPitchSlide call Audio2_InitPitchSlideVars .skipPitchSlide push de ld a, c cp Ch5 jr nc, .sfxChannel ; if sfx channel ; If this isn't an SFX channel, try the corresponding SFX channel. ld hl, wChannelSoundIDs + Ch5 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 Audio2_GetRegisterPointer ld [hl], d call Audio2_ApplyDutyCycleAndSoundLength call Audio2_EnableChannelOutput pop de ld b, $0 ld hl, wChannelFlags1 add hl, bc bit BIT_PERFECT_PITCH, [hl] ; has toggle_perfect_pitch 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 Audio2_ApplyWavePatternAndFrequency ret Audio2_EnableChannelOutput: ld b, 0 ld hl, Audio2_HWChannelEnableMasks add hl, bc ldh a, [rNR51] or [hl] ; set this channel's bits ld d, a ld a, c cp Ch8 jr z, .noiseChannelOrNoSfx cp Ch5 jr nc, .skip ; if sfx channel ; If this isn't an SFX channel, try the corresponding SFX channel. ld hl, wChannelSoundIDs + Ch5 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, Audio2_HWChannelEnableMasks add hl, bc and [hl] ld d, a ldh a, [rNR51] ld hl, Audio2_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 ldh [rNR51], a ret Audio2_ApplyDutyCycleAndSoundLength: ld b, 0 ld hl, wChannelNoteDelayCounters ; use the note delay as sound length add hl, bc ld d, [hl] ld a, c cp Ch3 jr z, .skipDuty ; if music channel 3 cp Ch7 jr z, .skipDuty ; if sfx channel 3 ; include duty cycle (except on channel 3 which doesn't have it) ld a, d and $3f ld d, a ld hl, wChannelDutyCycles add hl, bc ld a, [hl] or d ld d, a .skipDuty ld b, REG_DUTY_SOUND_LEN call Audio2_GetRegisterPointer ld [hl], d ret Audio2_ApplyWavePatternAndFrequency: ld a, c cp Ch3 jr z, .channel3 cp Ch7 jr nz, .notChannel3 ; fall through .channel3 push de ld de, wMusicWaveInstrument cp Ch3 jr z, .next ld de, wSfxWaveInstrument .next ld a, [de] add a ld d, 0 ld e, a ld hl, Audio2_WavePointers add hl, de ld e, [hl] inc hl ld d, [hl] ld hl, rWave_0 ld b, $f ld a, $0 ; stop hardware channel 3 ldh [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 ldh [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 Audio2_GetRegisterPointer ld [hl], e ; store frequency low byte inc hl ld [hl], d ; store frequency high byte ; --- this section is only present in this copy of the sound engine ld a, c cp Ch5 jr c, .musicChannel call Audio2_ApplyFrequencyModifier .musicChannel ; --- ret ; --- this section is only present in this copy of the sound engine ; unused Audio2_ResetCryModifiers: ld a, c cp Ch5 jr nz, .skip ld a, [wLowHealthAlarm] bit 7, a jr z, .skip xor a ld [wFrequencyModifier], a ld a, $80 ld [wTempoModifier], a .skip ret ; --- Audio2_SetSfxTempo: call Audio2_IsCry jr c, .skipCryCheck call Audio2_IsBattleSFX jr nc, .notCry .skipCryCheck 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 Audio2_ApplyFrequencyModifier: call Audio2_IsCry jr c, .skipCryCheck call Audio2_IsBattleSFX jr nc, .done .skipCryCheck ; 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 Audio2_GoBackOneCommandIfCry: call Audio2_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 Audio2_IsCry: ; Returns whether the currently playing audio is a cry in carry. ld a, [wChannelSoundIDs + Ch5] 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 ; --- this section is only present in this copy of the sound engine Audio2_IsBattleSFX: ; Returns whether the currently playing audio is a cry in carry. ld a, [wChannelSoundIDs + Ch8] ld b, a ld a, [wChannelSoundIDs + Ch5] or b cp BATTLE_SFX_START jr nc, .next jr .no .next cp BATTLE_SFX_END jr z, .no jr c, .yes .no scf ccf ret .yes scf ret ; --- Audio2_ApplyPitchSlide: ld hl, wChannelFlags1 add hl, bc bit BIT_PITCH_SLIDE_DECREASING, [hl] jp nz, .frequencyDecreasing ; frequency increasing ld hl, wChannelPitchSlideCurrentFrequencyLowBytes add hl, bc ld e, [hl] ld hl, wChannelPitchSlideCurrentFrequencyHighBytes add hl, bc ld d, [hl] ld hl, wChannelPitchSlideFrequencySteps add hl, bc ld l, [hl] ld h, b add hl, de ld d, h ld e, l ld hl, wChannelPitchSlideCurrentFrequencyFractionalPart add hl, bc push hl ld hl, wChannelPitchSlideFrequencyStepsFractionalPart 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, wChannelPitchSlideTargetFrequencyHighBytes add hl, bc ld a, [hl] cp d jp c, .reachedTargetFrequency jr nz, .applyUpdatedFrequency ld hl, wChannelPitchSlideTargetFrequencyLowBytes add hl, bc ld a, [hl] cp e jp c, .reachedTargetFrequency jr .applyUpdatedFrequency .frequencyDecreasing ld hl, wChannelPitchSlideCurrentFrequencyLowBytes add hl, bc ld a, [hl] ld hl, wChannelPitchSlideCurrentFrequencyHighBytes add hl, bc ld d, [hl] ld hl, wChannelPitchSlideFrequencySteps add hl, bc ld e, [hl] sub e ld e, a ld a, d sbc b ld d, a ld hl, wChannelPitchSlideFrequencyStepsFractionalPart 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, wChannelPitchSlideTargetFrequencyHighBytes add hl, bc ld a, d cp [hl] jr c, .reachedTargetFrequency jr nz, .applyUpdatedFrequency ld hl, wChannelPitchSlideTargetFrequencyLowBytes add hl, bc ld a, e cp [hl] jr c, .reachedTargetFrequency .applyUpdatedFrequency ld hl, wChannelPitchSlideCurrentFrequencyLowBytes add hl, bc ld [hl], e ld hl, wChannelPitchSlideCurrentFrequencyHighBytes add hl, bc ld [hl], d ld b, REG_FREQUENCY_LO call Audio2_GetRegisterPointer ld a, e ld [hli], a ld [hl], d ret .reachedTargetFrequency ; Turn off pitch slide when the target frequency has been reached. ld hl, wChannelFlags1 add hl, bc res BIT_PITCH_SLIDE_ON, [hl] res BIT_PITCH_SLIDE_DECREASING, [hl] ret Audio2_InitPitchSlideVars: ld hl, wChannelPitchSlideCurrentFrequencyHighBytes add hl, bc ld [hl], d ld hl, wChannelPitchSlideCurrentFrequencyLowBytes add hl, bc ld [hl], e ld hl, wChannelNoteDelayCounters add hl, bc ld a, [hl] ld hl, wChannelPitchSlideLengthModifiers add hl, bc sub [hl] jr nc, .next ld a, 1 .next ld [hl], a ld hl, wChannelPitchSlideTargetFrequencyLowBytes add hl, bc ld a, e sub [hl] ld e, a ld a, d sbc b ld hl, wChannelPitchSlideTargetFrequencyHighBytes add hl, bc sub [hl] jr c, .targetFrequencyGreater ld d, a ld b, 0 ld hl, wChannelFlags1 add hl, bc set BIT_PITCH_SLIDE_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, wChannelPitchSlideCurrentFrequencyHighBytes add hl, bc ld d, [hl] ld hl, wChannelPitchSlideCurrentFrequencyLowBytes add hl, bc ld e, [hl] ld hl, wChannelPitchSlideTargetFrequencyLowBytes 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, wChannelPitchSlideTargetFrequencyHighBytes add hl, bc ld a, [hl] sub d ld d, a ld b, 0 ld hl, wChannelFlags1 add hl, bc res BIT_PITCH_SLIDE_DECREASING, [hl] .next2 ld hl, wChannelPitchSlideLengthModifiers 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, wChannelPitchSlideFrequencySteps add hl, bc ld [hl], d ; store quotient + 1 ld hl, wChannelPitchSlideFrequencyStepsFractionalPart add hl, bc ld [hl], a ; store remainder - dividend ld hl, wChannelPitchSlideCurrentFrequencyFractionalPart add hl, bc ld [hl], a ; store remainder - dividend ret Audio2_ApplyDutyCyclePattern: ld b, 0 ld hl, wChannelDutyCyclePatterns add hl, bc ld a, [hl] rlca rlca ld [hl], a and $c0 ld d, a ld b, REG_DUTY_SOUND_LEN call Audio2_GetRegisterPointer ld a, [hl] and $3f or d ld [hl], a ret Audio2_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 Audio2_GetRegisterPointer: ; hl = address of hardware sound register b for software channel c ld a, c ld hl, Audio2_HWChannelBaseAddresses add l jr nc, .noCarry inc h .noCarry ld l, a ld a, [hl] add b ld l, a ld h, $ff ret Audio2_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 Audio2_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, Audio2_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 Audio2_PlaySound:: ld [wSoundID], a cp $ff jp z, .stopAllAudio cp MAX_SFX_ID_2 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, NUM_CHANNELS ld hl, wChannelReturnAddresses call .FillMem ld hl, wChannelCommandPointers call .FillMem ld d, NUM_MUSIC_CHANS ld hl, wChannelSoundIDs call .FillMem ld hl, wChannelFlags1 call .FillMem ld hl, wChannelDutyCycles call .FillMem ld hl, wChannelDutyCyclePatterns 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, wChannelPitchSlideLengthModifiers call .FillMem ld hl, wChannelPitchSlideFrequencySteps call .FillMem ld hl, wChannelPitchSlideFrequencyStepsFractionalPart call .FillMem ld hl, wChannelPitchSlideCurrentFrequencyFractionalPart call .FillMem ld hl, wChannelPitchSlideCurrentFrequencyHighBytes call .FillMem ld hl, wChannelPitchSlideCurrentFrequencyLowBytes call .FillMem ld hl, wChannelPitchSlideTargetFrequencyHighBytes call .FillMem ld hl, wChannelPitchSlideTargetFrequencyLowBytes 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 ldh [rNR50], a ld a, $8 ldh [rNR10], a ld a, 0 ldh [rNR51], a xor a ldh [rNR30], a ld a, $80 ldh [rNR30], a ld a, $77 ldh [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_2 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, .playChannel ld a, e cp Ch8 jr nz, .notNoiseChannel ld a, [wSoundID] cp NOISE_INSTRUMENTS_END jr nc, .notNoiseInstrument ret .notNoiseInstrument ld a, [hl] cp NOISE_INSTRUMENTS_END jr z, .playChannel jr c, .playChannel .notNoiseChannel ld a, [wSoundID] cp [hl] jr z, .playChannel jr c, .playChannel ret .playChannel 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, wChannelDutyCycles add hl, de ld [hl], a ld hl, wChannelDutyCyclePatterns 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, wChannelPitchSlideLengthModifiers add hl, de ld [hl], a ld hl, wChannelPitchSlideFrequencySteps add hl, de ld [hl], a ld hl, wChannelPitchSlideFrequencyStepsFractionalPart add hl, de ld [hl], a ld hl, wChannelPitchSlideCurrentFrequencyFractionalPart add hl, de ld [hl], a ld hl, wChannelPitchSlideCurrentFrequencyHighBytes add hl, de ld [hl], a ld hl, wChannelPitchSlideCurrentFrequencyLowBytes add hl, de ld [hl], a ld hl, wChannelPitchSlideTargetFrequencyHighBytes add hl, de ld [hl], a ld hl, wChannelPitchSlideTargetFrequencyLowBytes 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 Ch5 jr nz, .skipSweepDisable ld a, $8 ldh [rNR10], a ; sweep off .skipSweepDisable ld a, c and a jp z, .playSoundCommon dec c jp .sfxChannelLoop .stopAllAudio ld a, $80 ldh [rNR52], a ; sound hardware on ldh [rNR30], a ; wave playback on xor a ldh [rNR51], a ; no sound output ldh [rNR32], a ; mute channel 3 (wave channel) ld a, $8 ldh [rNR10], a ; sweep off ldh [rNR12], a ; mute channel 1 (pulse channel 1) ldh [rNR22], a ; mute channel 2 (pulse channel 2) ldh [rNR42], a ; mute channel 4 (noise channel) ld a, $40 ldh [rNR14], a ; counter mode ldh [rNR24], a ldh [rNR44], a ld a, $77 ldh [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_2 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 Ch4 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, .maybeCry jr .done .maybeCry ld a, [wSoundID] cp CRY_SFX_END jr z, .done jr c, .cry jr .done .cry ld hl, wChannelSoundIDs + Ch5 ld [hli], a ld [hli], a ld [hli], a ld [hl], a ld hl, wChannelCommandPointers + Ch7 * 2 ; sfx wave channel pointer ld de, Audio2_CryRet ld [hl], e inc hl ld [hl], d ; overwrite pointer to point to sound_ret ld a, [wSavedVolume] and a jr nz, .done ldh a, [rNR50] ld [wSavedVolume], a ld a, $77 ldh [rNR50], a ; full volume .done ret Audio2_CryRet: sound_ret Audio2_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 Audio2_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 Audio2_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 Audio2_Pitches: INCLUDE "audio/notes.asm"