#include "patches.h" #include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h" #include "sound.h" void AudioSeq_ProcessSeqCmd(u32 cmd); void AudioThread_QueueCmd(u32 opArgs, void** data); // Direct audio command (skips the queueing system) #define SEQCMD_SET_SEQPLAYER_VOLUME_NOW(seqPlayerIndex, duration, volume) \ AudioSeq_ProcessSeqCmd((SEQCMD_OP_SET_SEQPLAYER_VOLUME << 28) | ((u8)(seqPlayerIndex) << 24) | \ ((u8)(duration) << 16) | ((u8)((volume)*127.0f))); bool is_bgm_player(u8 player_index) { return player_index == SEQ_PLAYER_BGM_MAIN || player_index == SEQ_PLAYER_BGM_SUB; } /** * Update different commands and requests for active sequences */ void AudioSeq_UpdateActiveSequences(void) { u32 tempoCmd; u16 tempoPrev; u16 seqId; u16 channelMask; u16 tempoTarget; u8 setupOp; u8 targetSeqPlayerIndex; u8 setupVal2; u8 setupVal1; u8 tempoOp; s32 pad[2]; u32 retMsg; f32 volume; u8 tempoTimer; u8 seqPlayerIndex; u8 j; u8 channelIndex; for (seqPlayerIndex = 0; seqPlayerIndex < SEQ_PLAYER_MAX; seqPlayerIndex++) { // The seqPlayer has finished initializing and is currently playing the active sequences if (gActiveSeqs[seqPlayerIndex].isSeqPlayerInit && gAudioCtx.seqPlayers[seqPlayerIndex].enabled) { gActiveSeqs[seqPlayerIndex].isSeqPlayerInit = false; } // The seqPlayer is no longer playing the active sequences if ((AudioSeq_GetActiveSeqId(seqPlayerIndex) != NA_BGM_DISABLED) && !gAudioCtx.seqPlayers[seqPlayerIndex].enabled && (!gActiveSeqs[seqPlayerIndex].isSeqPlayerInit)) { gActiveSeqs[seqPlayerIndex].seqId = NA_BGM_DISABLED; } // Check if the requested sequences is waiting for fonts to load if (gActiveSeqs[seqPlayerIndex].isWaitingForFonts) { switch ((s32)AudioThread_GetExternalLoadQueueMsg(&retMsg)) { case SEQ_PLAYER_BGM_MAIN + 1: case SEQ_PLAYER_FANFARE + 1: case SEQ_PLAYER_SFX + 1: case SEQ_PLAYER_BGM_SUB + 1: case SEQ_PLAYER_AMBIENCE + 1: // The fonts have been loaded successfully. gActiveSeqs[seqPlayerIndex].isWaitingForFonts = false; // Queue the same command that was stored previously, but without the 0x8000 AudioSeq_ProcessSeqCmd(gActiveSeqs[seqPlayerIndex].startAsyncSeqCmd); break; case 0xFF: // There was an error in loading the fonts gActiveSeqs[seqPlayerIndex].isWaitingForFonts = false; break; } } // Update global volume if (gActiveSeqs[seqPlayerIndex].fadeVolUpdate) { volume = 1.0f; for (j = 0; j < VOL_SCALE_INDEX_MAX; j++) { volume *= (gActiveSeqs[seqPlayerIndex].volScales[j] / 127.0f); } SEQCMD_SET_SEQPLAYER_VOLUME((u8)(seqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), gActiveSeqs[seqPlayerIndex].volFadeTimer, (u8)(volume * 127.0f)); gActiveSeqs[seqPlayerIndex].fadeVolUpdate = false; } if (gActiveSeqs[seqPlayerIndex].volTimer != 0) { gActiveSeqs[seqPlayerIndex].volTimer--; if (gActiveSeqs[seqPlayerIndex].volTimer != 0) { gActiveSeqs[seqPlayerIndex].volCur -= gActiveSeqs[seqPlayerIndex].volStep; } else { gActiveSeqs[seqPlayerIndex].volCur = gActiveSeqs[seqPlayerIndex].volTarget; } } // @recomp Send a volume scale command regardless of whether volTimer is active and scale it for background music players. f32 cur_volume = gActiveSeqs[seqPlayerIndex].volCur; if (is_bgm_player(seqPlayerIndex)) { cur_volume *= recomp_get_bgm_volume(); } AUDIOCMD_SEQPLAYER_FADE_VOLUME_SCALE(seqPlayerIndex, cur_volume); // Process tempo if (gActiveSeqs[seqPlayerIndex].tempoCmd != 0) { tempoCmd = gActiveSeqs[seqPlayerIndex].tempoCmd; tempoTimer = (tempoCmd & 0xFF0000) >> 15; tempoTarget = tempoCmd & 0xFFF; if (tempoTimer == 0) { tempoTimer++; } // Process tempo commands if (gAudioCtx.seqPlayers[seqPlayerIndex].enabled) { tempoPrev = gAudioCtx.seqPlayers[seqPlayerIndex].tempo / TATUMS_PER_BEAT; tempoOp = (tempoCmd & 0xF000) >> 12; switch (tempoOp) { case SEQCMD_SUB_OP_TEMPO_SPEED_UP: // Speed up tempo by `tempoTarget` amount tempoTarget += tempoPrev; break; case SEQCMD_SUB_OP_TEMPO_SLOW_DOWN: // Slow down tempo by `tempoTarget` amount if (tempoTarget < tempoPrev) { tempoTarget = tempoPrev - tempoTarget; } break; case SEQCMD_SUB_OP_TEMPO_SCALE: // Scale tempo by a multiplicative factor tempoTarget = tempoPrev * (tempoTarget / 100.0f); break; case SEQCMD_SUB_OP_TEMPO_RESET: // Reset tempo to original tempo tempoTarget = (gActiveSeqs[seqPlayerIndex].tempoOriginal != 0) ? gActiveSeqs[seqPlayerIndex].tempoOriginal : tempoPrev; break; default: // `SEQCMD_SUB_OP_TEMPO_SET` // `tempoTarget` is the new tempo break; } if (gActiveSeqs[seqPlayerIndex].tempoOriginal == 0) { gActiveSeqs[seqPlayerIndex].tempoOriginal = tempoPrev; } gActiveSeqs[seqPlayerIndex].tempoTarget = tempoTarget; gActiveSeqs[seqPlayerIndex].tempoCur = gAudioCtx.seqPlayers[seqPlayerIndex].tempo / 0x30; gActiveSeqs[seqPlayerIndex].tempoStep = (gActiveSeqs[seqPlayerIndex].tempoCur - gActiveSeqs[seqPlayerIndex].tempoTarget) / tempoTimer; gActiveSeqs[seqPlayerIndex].tempoTimer = tempoTimer; gActiveSeqs[seqPlayerIndex].tempoCmd = 0; } } // Step tempo to target if (gActiveSeqs[seqPlayerIndex].tempoTimer != 0) { gActiveSeqs[seqPlayerIndex].tempoTimer--; if (gActiveSeqs[seqPlayerIndex].tempoTimer != 0) { gActiveSeqs[seqPlayerIndex].tempoCur -= gActiveSeqs[seqPlayerIndex].tempoStep; } else { gActiveSeqs[seqPlayerIndex].tempoCur = gActiveSeqs[seqPlayerIndex].tempoTarget; } AUDIOCMD_SEQPLAYER_SET_TEMPO(seqPlayerIndex, gActiveSeqs[seqPlayerIndex].tempoCur); } // Update channel volumes if (gActiveSeqs[seqPlayerIndex].volChannelFlags != 0) { for (channelIndex = 0; channelIndex < SEQ_NUM_CHANNELS; channelIndex++) { if (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTimer != 0) { gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTimer--; if (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTimer != 0) { gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volCur -= gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volStep; } else { gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volCur = gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTarget; gActiveSeqs[seqPlayerIndex].volChannelFlags ^= (1 << channelIndex); } AUDIOCMD_CHANNEL_SET_VOL_SCALE(seqPlayerIndex, channelIndex, gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volCur); } } } // Update frequencies if (gActiveSeqs[seqPlayerIndex].freqScaleChannelFlags != 0) { for (channelIndex = 0; channelIndex < SEQ_NUM_CHANNELS; channelIndex++) { if (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTimer != 0) { gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTimer--; if (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTimer != 0) { gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleCur -= gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleStep; } else { gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleCur = gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTarget; gActiveSeqs[seqPlayerIndex].freqScaleChannelFlags ^= (1 << channelIndex); } AUDIOCMD_CHANNEL_SET_FREQ_SCALE(seqPlayerIndex, channelIndex, gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleCur); } } } // Process setup commands if (gActiveSeqs[seqPlayerIndex].setupCmdNum != 0) { // If there is a SeqCmd to reset the audio heap queued, then drop all setup commands if (!AudioSeq_IsSeqCmdNotQueued(SEQCMD_OP_RESET_AUDIO_HEAP << 28, SEQCMD_OP_MASK)) { gActiveSeqs[seqPlayerIndex].setupCmdNum = 0; break; } // Only process setup commands once the timer reaches zero if (gActiveSeqs[seqPlayerIndex].setupCmdTimer != 0) { gActiveSeqs[seqPlayerIndex].setupCmdTimer--; continue; } // Only process setup commands if `seqPlayerIndex` if no longer playing // i.e. the `seqPlayer` is no longer enabled if (gAudioCtx.seqPlayers[seqPlayerIndex].enabled) { continue; } for (j = 0; j < gActiveSeqs[seqPlayerIndex].setupCmdNum; j++) { setupOp = (gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xF00000) >> 20; targetSeqPlayerIndex = (gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xF0000) >> 16; setupVal2 = (gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFF00) >> 8; setupVal1 = gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFF; switch (setupOp) { case SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME: // Restore `targetSeqPlayerIndex` volume back to normal levels AudioSeq_SetVolumeScale(targetSeqPlayerIndex, VOL_SCALE_INDEX_FANFARE, 0x7F, setupVal1); break; case SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME_IF_QUEUED: // Restore `targetSeqPlayerIndex` volume back to normal levels, // but only if the number of sequence queue requests from `sSeqRequests` // exactly matches the argument to the command if (setupVal1 == sNumSeqRequests[seqPlayerIndex]) { AudioSeq_SetVolumeScale(targetSeqPlayerIndex, VOL_SCALE_INDEX_FANFARE, 0x7F, setupVal2); } break; case SEQCMD_SUB_OP_SETUP_SEQ_UNQUEUE: // Unqueue `seqPlayerIndex` from sSeqRequests //! @bug this command does not work as intended as unqueueing //! the sequence relies on `gActiveSeqs[seqPlayerIndex].seqId` //! However, `gActiveSeqs[seqPlayerIndex].seqId` is reset before the sequence on //! `seqPlayerIndex` is requested to stop, i.e. before the sequence is disabled and setup //! commands (including this command) can run. A simple fix would have been to unqueue based on //! `gActiveSeqs[seqPlayerIndex].prevSeqId` instead SEQCMD_UNQUEUE_SEQUENCE((u8)(seqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), 0, gActiveSeqs[seqPlayerIndex].seqId); break; case SEQCMD_SUB_OP_SETUP_RESTART_SEQ: // Restart the currently active sequence on `targetSeqPlayerIndex` with full volume. // Sequence on `targetSeqPlayerIndex` must still be active to play (can be muted) SEQCMD_PLAY_SEQUENCE((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), 1, gActiveSeqs[targetSeqPlayerIndex].seqId); gActiveSeqs[targetSeqPlayerIndex].fadeVolUpdate = true; gActiveSeqs[targetSeqPlayerIndex].volScales[1] = 0x7F; break; case SEQCMD_SUB_OP_SETUP_TEMPO_SCALE: // Scale tempo by a multiplicative factor SEQCMD_SCALE_TEMPO((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), setupVal2, setupVal1); break; case SEQCMD_SUB_OP_SETUP_TEMPO_RESET: // Reset tempo to previous tempo SEQCMD_RESET_TEMPO((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), setupVal1); break; case SEQCMD_SUB_OP_SETUP_PLAY_SEQ: // Play the requested sequence // Uses the fade timer set by `SEQCMD_SUB_OP_SETUP_SET_FADE_TIMER` seqId = gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFFFF; SEQCMD_PLAY_SEQUENCE((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), gActiveSeqs[targetSeqPlayerIndex].setupFadeTimer, seqId); AudioSeq_SetVolumeScale(targetSeqPlayerIndex, VOL_SCALE_INDEX_FANFARE, 0x7F, 0); gActiveSeqs[targetSeqPlayerIndex].setupFadeTimer = 0; break; case SEQCMD_SUB_OP_SETUP_SET_FADE_TIMER: // A command specifically to support `SEQCMD_SUB_OP_SETUP_PLAY_SEQ` // Sets the fade timer for the sequence requested in `SEQCMD_SUB_OP_SETUP_PLAY_SEQ` gActiveSeqs[seqPlayerIndex].setupFadeTimer = setupVal2; break; case SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME_WITH_SCALE_INDEX: // Restore the volume back to default levels // Allows a `scaleIndex` to be specified. AudioSeq_SetVolumeScale(targetSeqPlayerIndex, setupVal2, 0x7F, setupVal1); break; case SEQCMD_SUB_OP_SETUP_POP_PERSISTENT_CACHE: // Discard audio data by popping one more audio caches from the audio heap if (setupVal1 & (1 << SEQUENCE_TABLE)) { AUDIOCMD_GLOBAL_POP_PERSISTENT_CACHE(SEQUENCE_TABLE); } if (setupVal1 & (1 << FONT_TABLE)) { AUDIOCMD_GLOBAL_POP_PERSISTENT_CACHE(FONT_TABLE); } if (setupVal1 & (1 << SAMPLE_TABLE)) { AUDIOCMD_GLOBAL_POP_PERSISTENT_CACHE(SAMPLE_TABLE); } break; case SEQCMD_SUB_OP_SETUP_SET_CHANNEL_DISABLE_MASK: // Disable (or reenable) specific channels of `targetSeqPlayerIndex` channelMask = gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFFFF; SEQCMD_SET_CHANNEL_DISABLE_MASK((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), channelMask); break; case SEQCMD_SUB_OP_SETUP_SET_SEQPLAYER_FREQ: // Scale all channels of `targetSeqPlayerIndex` SEQCMD_SET_SEQPLAYER_FREQ((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), setupVal2, setupVal1 * 10); break; default: break; } } gActiveSeqs[seqPlayerIndex].setupCmdNum = 0; } } } // @recomp Patched to add the ability to turn off low health beeps. void LifeMeter_UpdateSizeAndBeep(PlayState* play) { InterfaceContext* interfaceCtx = &play->interfaceCtx; if (interfaceCtx->lifeSizeChangeDirection != 0) { interfaceCtx->lifeSizeChange--; if (interfaceCtx->lifeSizeChange <= 0) { interfaceCtx->lifeSizeChange = 0; interfaceCtx->lifeSizeChangeDirection = 0; // @recomp Additional check for whether low health beeps are enabled. if (recomp_get_low_health_beeps_enabled() && !Player_InCsMode(play) && (play->pauseCtx.state == PAUSE_STATE_OFF) && (play->pauseCtx.debugEditor == DEBUG_EDITOR_NONE) && LifeMeter_IsCritical() && !Play_InCsMode(play)) { Audio_PlaySfx(NA_SE_SY_HITPOINT_ALARM); } } } else { interfaceCtx->lifeSizeChange++; if ((s32)interfaceCtx->lifeSizeChange >= 10) { interfaceCtx->lifeSizeChange = 10; interfaceCtx->lifeSizeChangeDirection = 1; } } }