/** * @file code_800F9280.c * * This file implements a set of high-level audio sequence commands that allow sequences to be modified in real-time. * These commands are intended to interface external to the audio library. * * These commands are generated using `Audio_QueueSeqCmd`, and a user-friendly interface for this function * can be found in `seqcmd.h` * * These commands change sequences by generating internal audio commands `Audio_QueueCmd` which allows these * sequence requests to be passed onto the audio thread. It is worth noting all functions in this file are * called from the graph thread. * * These commands are not to be confused with the sequence instructions used by the sequences themselves * which are a midi-based scripting language. * * Nor are these commands to be confused with the internal audio commands used to transfer requests from * the graph thread to the audio thread. */ #include "ultra64.h" #include "global.h" #include "ultra64/abi.h" // Direct audio command (skips the queueing system) #define SEQCMD_SET_PLAYER_VOLUME_NOW(seqPlayerIndex, duration, volume) \ Audio_ProcessSeqCmd((SEQCMD_OP_SET_PLAYER_VOLUME << 28) | ((u8)(seqPlayerIndex) << 24) | ((u8)(duration) << 16) | \ ((u8)((volume)*127.0f))); typedef struct { u8 seqId; u8 priority; // higher values have higher priority } SeqRequest; SeqRequest sSeqRequests[4][5]; u8 sNumSeqRequests[4]; u32 sAudioSeqCmds[0x100]; ActiveSequence gActiveSeqs[4]; void Audio_StartSequence(u8 seqPlayerIndex, u8 seqId, u8 seqArgs, u16 fadeInDuration) { u8 channelIndex; u16 duration; s32 pad; if (!gStartSeqDisabled || (seqPlayerIndex == SEQ_PLAYER_SFX)) { seqArgs &= 0x7F; if (seqArgs == 0x7F) { // `fadeInDuration` is interpreted as skip ticks duration = (fadeInDuration >> 3) * 60 * gAudioCtx.audioBufferParameters.updatesPerFrame; Audio_QueueCmdS32(0x85000000 | _SHIFTL(seqPlayerIndex, 16, 8) | _SHIFTL(seqId, 8, 8), duration); } else { // `fadeInDuration` is interpreted as number of frames at 30 fps Audio_QueueCmdS32(0x82000000 | _SHIFTL(seqPlayerIndex, 16, 8) | _SHIFTL(seqId, 8, 8), (fadeInDuration * (u16)gAudioCtx.audioBufferParameters.updatesPerFrame) / 4); } gActiveSeqs[seqPlayerIndex].seqId = seqId | (seqArgs << 8); gActiveSeqs[seqPlayerIndex].prevSeqId = seqId | (seqArgs << 8); if (gActiveSeqs[seqPlayerIndex].volCur != 1.0f) { Audio_QueueCmdF32(0x41000000 | _SHIFTL(seqPlayerIndex, 16, 8), gActiveSeqs[seqPlayerIndex].volCur); } gActiveSeqs[seqPlayerIndex].tempoTimer = 0; gActiveSeqs[seqPlayerIndex].tempoOriginal = 0; gActiveSeqs[seqPlayerIndex].tempoCmd = 0; for (channelIndex = 0; channelIndex < SEQ_NUM_CHANNELS; channelIndex++) { gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volCur = 1.0f; gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTimer = 0; gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleCur = 1.0f; gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTimer = 0; } gActiveSeqs[seqPlayerIndex].freqScaleChannelFlags = 0; gActiveSeqs[seqPlayerIndex].volChannelFlags = 0; } } void Audio_StopSequence(u8 seqPlayerIndex, u16 fadeOutDuration) { Audio_QueueCmdS32(0x83000000 | ((u8)seqPlayerIndex << 16), (fadeOutDuration * (u16)gAudioCtx.audioBufferParameters.updatesPerFrame) / 4); gActiveSeqs[seqPlayerIndex].seqId = NA_BGM_DISABLED; } void Audio_ProcessSeqCmd(u32 cmd) { s32 priority; s32 channelMaskEnable; u16 channelMaskDisable; u16 fadeTimer; u16 val; u8 oldSpec; u8 spec; u8 op; u8 subOp; u8 seqPlayerIndex; u8 seqId; u8 seqArgs; u8 found; u8 port; u8 duration; u8 channelIndex; u8 i; f32 freqScaleTarget; s32 pad; if (gAudioDebugPrintSeqCmd && (cmd & SEQCMD_OP_MASK) != (SEQCMD_OP_SET_PLAYER_IO << 28)) { AudioDebug_ScrPrt("SEQ H", (cmd >> 16) & 0xFFFF); AudioDebug_ScrPrt(" L", cmd & 0xFFFF); } op = cmd >> 28; seqPlayerIndex = (cmd & 0xF000000) >> 24; switch (op) { case SEQCMD_OP_PLAY_SEQUENCE: // Play a new sequence seqId = cmd & 0xFF; seqArgs = (cmd & 0xFF00) >> 8; // `fadeTimer` is only shifted 13 bits instead of 16 bits. // `fadeTimer` continues to be scaled in `Audio_StartSequence` fadeTimer = (cmd & 0xFF0000) >> 13; if (!gActiveSeqs[seqPlayerIndex].isWaitingForFonts && (seqArgs < 0x80)) { Audio_StartSequence(seqPlayerIndex, seqId, seqArgs, fadeTimer); } break; case SEQCMD_OP_STOP_SEQUENCE: // Stop a sequence and disable the sequence player fadeTimer = (cmd & 0xFF0000) >> 13; Audio_StopSequence(seqPlayerIndex, fadeTimer); break; case SEQCMD_OP_QUEUE_SEQUENCE: // Queue a sequence into `sSeqRequests` seqId = cmd & 0xFF; seqArgs = (cmd & 0xFF00) >> 8; fadeTimer = (cmd & 0xFF0000) >> 13; priority = seqArgs; // Checks if the requested sequence is first in the list of requests // If it is already queued and first in the list, then play the sequence immediately for (i = 0; i < sNumSeqRequests[seqPlayerIndex]; i++) { if (sSeqRequests[seqPlayerIndex][i].seqId == seqId) { if (i == 0) { Audio_StartSequence(seqPlayerIndex, seqId, seqArgs, fadeTimer); } return; } } // Searches the sequence requests for the first request that does not have a higher priority // than the current incoming request found = sNumSeqRequests[seqPlayerIndex]; for (i = 0; i < sNumSeqRequests[seqPlayerIndex]; i++) { if (priority >= sSeqRequests[seqPlayerIndex][i].priority) { found = i; i = sNumSeqRequests[seqPlayerIndex]; // "break;" } } // Check if the queue is full if (sNumSeqRequests[seqPlayerIndex] < ARRAY_COUNT(sSeqRequests[seqPlayerIndex])) { sNumSeqRequests[seqPlayerIndex]++; } for (i = sNumSeqRequests[seqPlayerIndex] - 1; i != found; i--) { // Move all requests of lower priority backwards 1 place in the queue // If the queue is full, overwrite the entry with the lowest priority sSeqRequests[seqPlayerIndex][i].priority = sSeqRequests[seqPlayerIndex][i - 1].priority; sSeqRequests[seqPlayerIndex][i].seqId = sSeqRequests[seqPlayerIndex][i - 1].seqId; } // Fill the newly freed space in the queue with the new request sSeqRequests[seqPlayerIndex][found].priority = seqArgs; sSeqRequests[seqPlayerIndex][found].seqId = seqId; // The sequence is first in queue, so start playing. if (found == 0) { Audio_StartSequence(seqPlayerIndex, seqId, seqArgs, fadeTimer); } break; case SEQCMD_OP_UNQUEUE_SEQUENCE: // Unqueue sequence fadeTimer = (cmd & 0xFF0000) >> 13; found = sNumSeqRequests[seqPlayerIndex]; for (i = 0; i < sNumSeqRequests[seqPlayerIndex]; i++) { seqId = cmd & 0xFF; if (sSeqRequests[seqPlayerIndex][i].seqId == seqId) { found = i; i = sNumSeqRequests[seqPlayerIndex]; // "break;" } } if (found != sNumSeqRequests[seqPlayerIndex]) { // Move all requests of lower priority forward 1 place in the queue for (i = found; i < sNumSeqRequests[seqPlayerIndex] - 1; i++) { sSeqRequests[seqPlayerIndex][i].priority = sSeqRequests[seqPlayerIndex][i + 1].priority; sSeqRequests[seqPlayerIndex][i].seqId = sSeqRequests[seqPlayerIndex][i + 1].seqId; } sNumSeqRequests[seqPlayerIndex]--; } // If the sequence was first in queue (it is currently playing), // Then stop the sequence and play the next sequence in the queue. if (found == 0) { Audio_StopSequence(seqPlayerIndex, fadeTimer); if (sNumSeqRequests[seqPlayerIndex] != 0) { Audio_StartSequence(seqPlayerIndex, sSeqRequests[seqPlayerIndex][0].seqId, sSeqRequests[seqPlayerIndex][0].priority, fadeTimer); } } break; case SEQCMD_OP_SET_PLAYER_VOLUME: // Transition volume to a target volume for an entire player duration = (cmd & 0xFF0000) >> 15; val = cmd & 0xFF; if (duration == 0) { duration++; } // Volume is scaled relative to 127 gActiveSeqs[seqPlayerIndex].volTarget = (f32)val / 127.0f; if (gActiveSeqs[seqPlayerIndex].volCur != gActiveSeqs[seqPlayerIndex].volTarget) { gActiveSeqs[seqPlayerIndex].volStep = (gActiveSeqs[seqPlayerIndex].volCur - gActiveSeqs[seqPlayerIndex].volTarget) / (f32)duration; gActiveSeqs[seqPlayerIndex].volTimer = duration; } break; case SEQCMD_OP_SET_PLAYER_FREQ: // Transition freq scale to a target freq for all channels duration = (cmd & 0xFF0000) >> 15; val = cmd & 0xFFFF; if (duration == 0) { duration++; } // Frequency is scaled relative to 1000 freqScaleTarget = (f32)val / 1000.0f; for (i = 0; i < SEQ_NUM_CHANNELS; i++) { gActiveSeqs[seqPlayerIndex].channelData[i].freqScaleTarget = freqScaleTarget; gActiveSeqs[seqPlayerIndex].channelData[i].freqScaleTimer = duration; gActiveSeqs[seqPlayerIndex].channelData[i].freqScaleStep = (gActiveSeqs[seqPlayerIndex].channelData[i].freqScaleCur - freqScaleTarget) / (f32)duration; } gActiveSeqs[seqPlayerIndex].freqScaleChannelFlags = 0xFFFF; break; case SEQCMD_OP_SET_CHANNEL_FREQ: // Transition freq scale to a target for a specific channel duration = (cmd & 0xFF0000) >> 15; channelIndex = (cmd & 0xF000) >> 12; val = cmd & 0xFFF; if (duration == 0) { duration++; } // Frequency is scaled relative to 1000 freqScaleTarget = (f32)val / 1000.0f; gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTarget = freqScaleTarget; gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleStep = (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleCur - freqScaleTarget) / (f32)duration; gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTimer = duration; gActiveSeqs[seqPlayerIndex].freqScaleChannelFlags |= 1 << channelIndex; break; case SEQCMD_OP_SET_CHANNEL_VOLUME: // Transition volume to a target volume for a specific channel duration = (cmd & 0xFF0000) >> 15; channelIndex = (cmd & 0xF00) >> 8; val = cmd & 0xFF; if (duration == 0) { duration++; } // Volume is scaled relative to 127 gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTarget = (f32)val / 127.0f; if (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volCur != gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTarget) { gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volStep = (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volCur - gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTarget) / (f32)duration; gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTimer = duration; gActiveSeqs[seqPlayerIndex].volChannelFlags |= 1 << channelIndex; } break; case SEQCMD_OP_SET_PLAYER_IO: // Set global io port port = (cmd & 0xFF0000) >> 16; val = cmd & 0xFF; Audio_QueueCmdS8(0x46000000 | _SHIFTL(seqPlayerIndex, 16, 8) | _SHIFTL(port, 0, 8), val); break; case SEQCMD_OP_SET_CHANNEL_IO: // Set io port if channel masked channelIndex = (cmd & 0xF00) >> 8; port = (cmd & 0xFF0000) >> 16; val = cmd & 0xFF; if (!(gActiveSeqs[seqPlayerIndex].channelPortMask & (1 << channelIndex))) { Audio_QueueCmdS8(0x06000000 | _SHIFTL(seqPlayerIndex, 16, 8) | _SHIFTL(channelIndex, 8, 8) | _SHIFTL(port, 0, 8), val); } break; case SEQCMD_OP_SET_CHANNEL_IO_DISABLE_MASK: // Disable channel io specifically for // `SEQCMD_OP_SET_CHANNEL_IO` This can be bypassed by setting channel io through `Audio_QueueCmdS8` 0x6 // directly. This is accomplished by setting a channel mask. gActiveSeqs[seqPlayerIndex].channelPortMask = cmd & 0xFFFF; break; case SEQCMD_OP_SET_CHANNEL_DISABLE_MASK: // Disable or Reenable channels // Disable channels channelMaskDisable = cmd & 0xFFFF; if (channelMaskDisable != 0) { // Apply channel mask `channelMaskDisable` Audio_QueueCmdU16(0x90000000 | _SHIFTL(seqPlayerIndex, 16, 8), channelMaskDisable); // Disable channels Audio_QueueCmdS8(0x08000000 | _SHIFTL(seqPlayerIndex, 16, 8) | 0xFF00, 1); } // Reenable channels channelMaskEnable = (channelMaskDisable ^ 0xFFFF); if (channelMaskEnable != 0) { // Apply channel mask `channelMaskEnable` Audio_QueueCmdU16(0x90000000 | _SHIFTL(seqPlayerIndex, 16, 8), channelMaskEnable); // Enable channels Audio_QueueCmdS8(0x08000000 | _SHIFTL(seqPlayerIndex, 16, 8) | 0xFF00, 0); } break; case SEQCMD_OP_TEMPO_CMD: // Update a tempo using a sub-command system. // Stores the cmd for processing elsewhere. gActiveSeqs[seqPlayerIndex].tempoCmd = cmd; break; case SEQCMD_OP_SETUP_CMD: // Queue a sub-command to execute once the sequence is finished playing subOp = (cmd & 0xF00000) >> 20; if (subOp != SEQCMD_SUB_OP_SETUP_RESET_SETUP_CMDS) { // Ensure the maximum number of setup commands is not exceeded if (gActiveSeqs[seqPlayerIndex].setupCmdNum < (ARRAY_COUNT(gActiveSeqs[seqPlayerIndex].setupCmd) - 1)) { found = gActiveSeqs[seqPlayerIndex].setupCmdNum++; if (found < ARRAY_COUNT(gActiveSeqs[seqPlayerIndex].setupCmd)) { gActiveSeqs[seqPlayerIndex].setupCmd[found] = cmd; // Adds a delay of 2 frames before executing any setup commands. // This allows setup commands to be requested along with a new sequence on a seqPlayerIndex. // This 2 frame delay ensures the player is enabled before its state is checked for // the purpose of deciding if the setup commands should be run. // Otherwise, the setup commands will be executed before the sequence starts, // when the player is still disabled, instead of when the newly played sequence ends. gActiveSeqs[seqPlayerIndex].setupCmdTimer = 2; } } } else { // `SEQCMD_SUB_OP_SETUP_RESET_SETUP_CMDS` // Discard all setup command requests on `seqPlayerIndex` gActiveSeqs[seqPlayerIndex].setupCmdNum = 0; } break; case SEQCMD_OP_GLOBAL_CMD: // Apply a command that applies to all sequence players subOp = (cmd & 0xF00) >> 8; val = cmd & 0xFF; switch (subOp) { case SEQCMD_SUB_OP_GLOBAL_SET_SOUND_MODE: // Set sound mode Audio_QueueCmdS32(0xF0000000, gSoundModeList[val]); break; case SEQCMD_SUB_OP_GLOBAL_DISABLE_NEW_SEQUENCES: // Disable the starting of new sequences (except for the sfx player) gStartSeqDisabled = val & 1; break; } break; case SEQCMD_OP_RESET_AUDIO_HEAP: // Resets the audio heap based on the audio specifications and sfx channel layout spec = cmd & 0xFF; gSfxChannelLayout = (cmd & 0xFF00) >> 8; oldSpec = gAudioSpecId; gAudioSpecId = spec; func_800E5F88(spec); func_800F71BC(oldSpec); Audio_QueueCmdS32(0xF8000000, 0); break; } } /** * Add the sequence cmd to the `sAudioSeqCmds` queue */ void Audio_QueueSeqCmd(u32 cmd) { sAudioSeqCmds[gSeqCmdWritePos++] = cmd; } void Audio_ProcessSeqCmds(void) { while (gSeqCmdWritePos != gSeqCmdReadPos) { Audio_ProcessSeqCmd(sAudioSeqCmds[gSeqCmdReadPos++]); } } u16 Audio_GetActiveSeqId(u8 seqPlayerIndex) { if (!gAudioCtx.seqPlayers[seqPlayerIndex].enabled) { return NA_BGM_DISABLED; } return gActiveSeqs[seqPlayerIndex].seqId; } s32 Audio_IsSeqCmdNotQueued(u32 cmdVal, u32 cmdMask) { u8 i; for (i = gSeqCmdReadPos; i != gSeqCmdWritePos; i++) { if ((sAudioSeqCmds[i] & cmdMask) == cmdVal) { return false; } } return true; } // Unused void Audio_ResetSequenceRequests(u8 seqPlayerIndex) { sNumSeqRequests[seqPlayerIndex] = 0; } /** * Check if the setup command is queued. If it is, then replace the command * with `SEQCMD_SUB_OP_SETUP_RESTORE_VOLUME`. * Unused */ void Audio_ReplaceSeqCmdSetupOpVolRestore(u8 seqPlayerIndex, u8 setupOpDisabled) { u8 i; for (i = 0; i < gActiveSeqs[seqPlayerIndex].setupCmdNum; i++) { u8 setupOp = (gActiveSeqs[seqPlayerIndex].setupCmd[i] & 0xF00000) >> 20; if (setupOp == setupOpDisabled) { gActiveSeqs[seqPlayerIndex].setupCmd[i] = 0xFF000000; } } } void Audio_SetVolumeScale(u8 seqPlayerIndex, u8 scaleIndex, u8 targetVol, u8 volFadeTimer) { f32 volScale; u8 i; gActiveSeqs[seqPlayerIndex].volScales[scaleIndex] = targetVol & 0x7F; if (volFadeTimer != 0) { gActiveSeqs[seqPlayerIndex].fadeVolUpdate = true; gActiveSeqs[seqPlayerIndex].volFadeTimer = volFadeTimer; } else { for (i = 0, volScale = 1.0f; i < VOL_SCALE_INDEX_MAX; i++) { volScale *= gActiveSeqs[seqPlayerIndex].volScales[i] / 127.0f; } SEQCMD_SET_PLAYER_VOLUME_NOW(seqPlayerIndex, volFadeTimer, volScale); } } /** * Update different commands and requests for active sequences */ void Audio_UpdateActiveSequences(void) { u32 tempoCmd; u8 tempoOp; u16 tempoPrev; u16 tempoTarget; u8 setupOp; u8 targetSeqPlayerIndex; u8 setupVal2; u8 setupVal1; u16 seqId; s32 pad[2]; u16 channelMask; u32 retMsg; f32 volume; u8 tempoTimer; u8 seqPlayerIndex; u8 j; u8 channelIndex; for (seqPlayerIndex = 0; seqPlayerIndex < 4; seqPlayerIndex++) { // The setup for this block of code was not fully implemented until Majora's Mask. // The intent was to load soundfonts asynchronously before playing a // sequence in `Audio_StartSequence` using `(seqArgs & 0x80)`. // Checks if the requested sequence is finished loading fonts if (gActiveSeqs[seqPlayerIndex].isWaitingForFonts) { switch (func_800E5E20(&retMsg)) { case SEQ_PLAYER_BGM_MAIN + 1: case SEQ_PLAYER_FANFARE + 1: case SEQ_PLAYER_SFX + 1: case SEQ_PLAYER_BGM_SUB + 1: // The fonts have been loaded successfully. gActiveSeqs[seqPlayerIndex].isWaitingForFonts = false; // Queue the same command that was stored previously // The code to store this command is missing in OoT, so no command is executed Audio_ProcessSeqCmd(gActiveSeqs[seqPlayerIndex].startSeqCmd); 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_PLAYER_VOLUME(seqPlayerIndex, 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; } Audio_QueueCmdF32(0x41000000 | _SHIFTL(seqPlayerIndex, 16, 8), gActiveSeqs[seqPlayerIndex].volCur); } // 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 (tempoTarget > 300) { tempoTarget = 300; } if (gActiveSeqs[seqPlayerIndex].tempoOriginal == 0) { gActiveSeqs[seqPlayerIndex].tempoOriginal = tempoPrev; } gActiveSeqs[seqPlayerIndex].tempoTarget = tempoTarget; gActiveSeqs[seqPlayerIndex].tempoCur = gAudioCtx.seqPlayers[seqPlayerIndex].tempo / TATUMS_PER_BEAT; 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; } // Set tempo Audio_QueueCmdS32(0x47000000 | _SHIFTL(seqPlayerIndex, 16, 8), 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); } // `CHAN_UPD_VOL_SCALE` Audio_QueueCmdF32(0x01000000 | _SHIFTL(seqPlayerIndex, 16, 8) | _SHIFTL(channelIndex, 8, 8), 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); } // `CHAN_UPD_FREQ_SCALE` Audio_QueueCmdF32(0x04000000 | _SHIFTL(seqPlayerIndex, 16, 8) | _SHIFTL(channelIndex, 8, 8), 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 (!Audio_IsSeqCmdNotQueued(SEQCMD_OP_RESET_AUDIO_HEAP << 28, SEQCMD_OP_MASK)) { gActiveSeqs[seqPlayerIndex].setupCmdNum = 0; return; } // 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] & 0x00F00000) >> 20; targetSeqPlayerIndex = (gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0x000F0000) >> 16; setupVal2 = (gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFF00) >> 8; setupVal1 = gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFF; switch (setupOp) { case SEQCMD_SUB_OP_SETUP_RESTORE_VOLUME: // Restore `targetSeqPlayerIndex` volume back to normal levels Audio_SetVolumeScale(targetSeqPlayerIndex, VOL_SCALE_INDEX_FANFARE, 0x7F, setupVal1); break; case SEQCMD_SUB_OP_SETUP_RESTORE_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]) { Audio_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(seqPlayerIndex, 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(targetSeqPlayerIndex, 1, 0, gActiveSeqs[targetSeqPlayerIndex].seqId); gActiveSeqs[targetSeqPlayerIndex].fadeVolUpdate = true; gActiveSeqs[targetSeqPlayerIndex].volScales[VOL_SCALE_INDEX_FANFARE] = 0x7F; break; case SEQCMD_SUB_OP_SETUP_TEMPO_SCALE: // Scale tempo by a multiplicative factor SEQCMD_SCALE_TEMPO(targetSeqPlayerIndex, setupVal2, setupVal1); break; case SEQCMD_SUB_OP_SETUP_TEMPO_RESET: // Reset tempo to previous tempo SEQCMD_RESET_TEMPO(targetSeqPlayerIndex, 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(targetSeqPlayerIndex, gActiveSeqs[targetSeqPlayerIndex].setupFadeTimer, 0, seqId); Audio_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_VOLUME_WITH_SCALE_INDEX: // Restore the volume back to default levels // Allows a `scaleIndex` to be specified. Audio_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)) { Audio_QueueCmdS32(0xE3000000, SEQUENCE_TABLE); } if (setupVal1 & (1 << FONT_TABLE)) { Audio_QueueCmdS32(0xE3000000, FONT_TABLE); } if (setupVal1 & (1 << SAMPLE_TABLE)) { Audio_QueueCmdS32(0xE3000000, 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(targetSeqPlayerIndex, channelMask); break; case SEQCMD_SUB_OP_SETUP_SET_PLAYER_FREQ: // Scale all channels of `targetSeqPlayerIndex` SEQCMD_SET_PLAYER_FREQ(targetSeqPlayerIndex, setupVal2, (setupVal1 * 10) & 0xFFFF); break; } } gActiveSeqs[seqPlayerIndex].setupCmdNum = 0; } } } u8 func_800FAD34(void) { if (D_80133418 != 0) { if (D_80133418 == 1) { if (func_800E5EDC() == 1) { D_80133418 = 0; Audio_QueueCmdS8(0x46020000, gSfxChannelLayout); func_800F7170(); } } else if (D_80133418 == 2) { while (func_800E5EDC() != 1) {} D_80133418 = 0; Audio_QueueCmdS8(0x46020000, gSfxChannelLayout); func_800F7170(); } } return D_80133418; } void Audio_ResetActiveSequences(void) { u8 seqPlayerIndex; u8 scaleIndex; for (seqPlayerIndex = 0; seqPlayerIndex < 4; seqPlayerIndex++) { sNumSeqRequests[seqPlayerIndex] = 0; gActiveSeqs[seqPlayerIndex].seqId = NA_BGM_DISABLED; gActiveSeqs[seqPlayerIndex].prevSeqId = NA_BGM_DISABLED; gActiveSeqs[seqPlayerIndex].tempoTimer = 0; gActiveSeqs[seqPlayerIndex].tempoOriginal = 0; gActiveSeqs[seqPlayerIndex].tempoCmd = 0; gActiveSeqs[seqPlayerIndex].channelPortMask = 0; gActiveSeqs[seqPlayerIndex].setupCmdNum = 0; gActiveSeqs[seqPlayerIndex].setupFadeTimer = 0; gActiveSeqs[seqPlayerIndex].freqScaleChannelFlags = 0; gActiveSeqs[seqPlayerIndex].volChannelFlags = 0; for (scaleIndex = 0; scaleIndex < VOL_SCALE_INDEX_MAX; scaleIndex++) { gActiveSeqs[seqPlayerIndex].volScales[scaleIndex] = 0x7F; } gActiveSeqs[seqPlayerIndex].volFadeTimer = 1; gActiveSeqs[seqPlayerIndex].fadeVolUpdate = true; } } void Audio_ResetActiveSequencesAndVolume(void) { u8 seqPlayerIndex; u8 scaleIndex; for (seqPlayerIndex = 0; seqPlayerIndex < 4; seqPlayerIndex++) { gActiveSeqs[seqPlayerIndex].volCur = 1.0f; gActiveSeqs[seqPlayerIndex].volTimer = 0; gActiveSeqs[seqPlayerIndex].fadeVolUpdate = false; for (scaleIndex = 0; scaleIndex < VOL_SCALE_INDEX_MAX; scaleIndex++) { gActiveSeqs[seqPlayerIndex].volScales[scaleIndex] = 0x7F; } } Audio_ResetActiveSequences(); }