/** * @file sequence.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 `AudioSeq_QueueSeqCmd`, and a user-friendly interface for this function * can be found in `seqcmd.h` * * These commands change sequences by generating internal audio commands `AudioThread_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 "global.h" // 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))); SeqRequest sSeqRequests[5][5]; u8 sNumSeqRequests[5]; u32 sAudioSeqCmds[0x100]; ActiveSequence gActiveSeqs[5]; u8 sResetAudioHeapTimer; u16 sResetAudioHeapFadeReverbVolume; u16 sResetAudioHeapFadeReverbVolumeStep; u8 sSeqCmdWritePos = 0; u8 sSeqCmdReadPos = 0; u8 sStartSeqDisabled = 0; u8 sSoundModeList[] = { SOUNDMODE_STEREO, SOUNDMODE_HEADSET, SOUNDMODE_SURROUND_EXTERNAL, SOUNDMODE_MONO, SOUNDMODE_SURROUND, }; u8 gAudioSpecId = 0; u8 gAudioHeapResetState = AUDIO_HEAP_RESET_STATE_NONE; u32 sResetAudioHeapSeqCmd = 0; void AudioSeq_StartSequence(u8 seqPlayerIndex, u8 seqId, u8 seqArgs, u16 fadeInDuration) { u8 channelIndex; u16 skipTicks; s32 pad; if (!sStartSeqDisabled || (seqPlayerIndex == SEQ_PLAYER_SFX)) { seqArgs &= 0x7F; if (seqArgs == 0x7F) { // `fadeInDuration` interpreted as seconds, 60 is refresh rate and does not account for PAL skipTicks = (fadeInDuration >> 3) * 60 * gAudioCtx.audioBufferParameters.updatesPerFrame; AUDIOCMD_GLOBAL_INIT_SEQPLAYER_SKIP_TICKS(seqPlayerIndex, seqId, skipTicks); } else { // `fadeInDuration` interpreted as 1/30th of a second, does not account for change in refresh rate for PAL AUDIOCMD_GLOBAL_INIT_SEQPLAYER(seqPlayerIndex, seqId, (fadeInDuration * (u16)gAudioCtx.audioBufferParameters.updatesPerFrame) / 4); } gActiveSeqs[seqPlayerIndex].seqId = seqId | (seqArgs << 8); gActiveSeqs[seqPlayerIndex].prevSeqId = seqId | (seqArgs << 8); gActiveSeqs[seqPlayerIndex].isSeqPlayerInit = true; if (gActiveSeqs[seqPlayerIndex].volCur != 1.0f) { AUDIOCMD_SEQPLAYER_FADE_VOLUME_SCALE(seqPlayerIndex, 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 AudioSeq_StopSequence(u8 seqPlayerIndex, u16 fadeOutDuration) { AUDIOCMD_GLOBAL_DISABLE_SEQPLAYER(seqPlayerIndex, (fadeOutDuration * (u16)gAudioCtx.audioBufferParameters.updatesPerFrame) / 4); gActiveSeqs[seqPlayerIndex].seqId = NA_BGM_DISABLED; } void AudioSeq_ProcessSeqCmd(u32 cmd) { s32 priority; s32 channelMaskEnable; u16 channelMaskDisable; u16 fadeTimer; u16 val; u8 oldSpecId; u8 specId; u8 fadeReverb; u8 op; u8 subOp; u8 seqPlayerIndex; u8 seqId; u8 seqArgs; u8 found; u8 ioPort; u8 duration; u8 channelIndex; u8 i; f32 freqScaleTarget; u32 outNumFonts; op = cmd >> 28; seqPlayerIndex = (cmd & SEQCMD_SEQPLAYER_MASK) >> 24; switch (op) { case SEQCMD_OP_PLAY_SEQUENCE: // Play a new sequence seqId = cmd & SEQCMD_SEQID_MASK; seqArgs = (cmd & 0xFF00) >> 8; // `fadeTimer` is only shifted 13 bits instead of 16 bits. // `fadeTimer` continues to be scaled in `AudioSeq_StartSequence` fadeTimer = (cmd & 0xFF0000) >> 13; if (!gActiveSeqs[seqPlayerIndex].isWaitingForFonts && !sStartSeqDisabled) { if (seqArgs < 0x80) { AudioSeq_StartSequence(seqPlayerIndex, seqId, seqArgs, fadeTimer); } else { // Store the cmd to be called again once the fonts are loaded // but changes the command so that next time, the (seqArgs < 0x80) case is taken gActiveSeqs[seqPlayerIndex].startAsyncSeqCmd = (cmd & ~(SEQ_FLAG_ASYNC | SEQCMD_ASYNC_ACTIVE)) + SEQCMD_ASYNC_ACTIVE; gActiveSeqs[seqPlayerIndex].isWaitingForFonts = true; gActiveSeqs[seqPlayerIndex].fontId = *AudioThread_GetFontsForSequence(seqId, &outNumFonts); AudioSeq_StopSequence(seqPlayerIndex, 1); if (gActiveSeqs[seqPlayerIndex].prevSeqId != NA_BGM_DISABLED) { if (*AudioThread_GetFontsForSequence(seqId, &outNumFonts) != *AudioThread_GetFontsForSequence(gActiveSeqs[seqPlayerIndex].prevSeqId & 0xFF, &outNumFonts)) { // Discard Seq Fonts AUDIOCMD_GLOBAL_DISCARD_SEQ_FONTS((s32)seqId); } } AUDIOCMD_GLOBAL_ASYNC_LOAD_FONT(*AudioThread_GetFontsForSequence(seqId, &outNumFonts), (u8)((seqPlayerIndex + 1) & 0xFF)); } } break; case SEQCMD_OP_STOP_SEQUENCE: // Stop a sequence and disable the sequence player fadeTimer = (cmd & 0xFF0000) >> 13; AudioSeq_StopSequence(seqPlayerIndex, fadeTimer); break; case SEQCMD_OP_QUEUE_SEQUENCE: // Queue a sequence into `sSeqRequests` seqId = cmd & SEQCMD_SEQID_MASK; 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) { AudioSeq_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) { AudioSeq_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 & SEQCMD_SEQID_MASK; 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) { AudioSeq_StopSequence(seqPlayerIndex, fadeTimer); if (sNumSeqRequests[seqPlayerIndex] != 0) { AudioSeq_StartSequence(seqPlayerIndex, sSeqRequests[seqPlayerIndex][0].seqId, sSeqRequests[seqPlayerIndex][0].priority, fadeTimer); } } break; case SEQCMD_OP_SET_SEQPLAYER_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_SEQPLAYER_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_SEQPLAYER_IO: // Set global io port ioPort = (cmd & 0xFF0000) >> 16; val = cmd & 0xFF; AUDIOCMD_SEQPLAYER_SET_IO(seqPlayerIndex, ioPort, val); break; case SEQCMD_OP_SET_CHANNEL_IO: // Set io port if channel masked channelIndex = (cmd & 0xF00) >> 8; ioPort = (cmd & 0xFF0000) >> 16; val = cmd & 0xFF; if (!(gActiveSeqs[seqPlayerIndex].channelPortMask & (1 << channelIndex))) { AUDIOCMD_CHANNEL_SET_IO(seqPlayerIndex, channelIndex, ioPort, 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 using `AUDIOCMD_CHANNEL_SET_IO` 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` AUDIOCMD_GLOBAL_SET_CHANNEL_MASK(seqPlayerIndex, channelMaskDisable); // Disable channels AUDIOCMD_CHANNEL_SET_MUTE(seqPlayerIndex, AUDIOCMD_ALL_CHANNELS, true); } // Reenable channels channelMaskEnable = channelMaskDisable ^ 0xFFFF; if (channelMaskEnable != 0) { // Apply channel mask `channelMaskEnable` AUDIOCMD_GLOBAL_SET_CHANNEL_MASK(seqPlayerIndex, channelMaskEnable); // Enable channels AUDIOCMD_CHANNEL_SET_MUTE(seqPlayerIndex, AUDIOCMD_ALL_CHANNELS, false); } 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 AUDIOCMD_GLOBAL_SET_SOUND_MODE(sSoundModeList[val]); break; case SEQCMD_SUB_OP_GLOBAL_DISABLE_NEW_SEQUENCES: // Set sequence disabled in (sStartSeqDisabled & 1) bit sStartSeqDisabled = (sStartSeqDisabled & (u8)~1) | (u8)(val & 1); break; case SEQCMD_SUB_OP_GLOBAL_DISABLE_NEW_SEQUENCES_2: // Set sequence disabled in (sStartSeqDisabled & 2) bit sStartSeqDisabled = (sStartSeqDisabled & (u8)~2) | (u8)((val & 1) << 1); break; } break; case SEQCMD_OP_RESET_AUDIO_HEAP: // Resets the audio heap based on the audio specifications, audio mode, and sfx channel layout specId = cmd & 0xFF; fadeReverb = (cmd & 0xFF0000) >> 16; if (fadeReverb == 0) { gSfxChannelLayout = (cmd & 0xFF00) >> 8; oldSpecId = gAudioSpecId; gAudioSpecId = specId; AudioThread_ResetAudioHeap(specId); Audio_ResetForAudioHeapStep1(oldSpecId); AUDIOCMD_GLOBAL_STOP_AUDIOCMDS(); } else { sResetAudioHeapSeqCmd = cmd; sResetAudioHeapFadeReverbVolume = 0x7FFF; sResetAudioHeapTimer = 20; sResetAudioHeapFadeReverbVolumeStep = 0x666; } break; } } /** * Add the sequence cmd to the `sAudioSeqCmds` queue */ void AudioSeq_QueueSeqCmd(u32 cmd) { sAudioSeqCmds[sSeqCmdWritePos++] = cmd; } void AudioSeq_ProcessSeqCmds(void) { while (sSeqCmdWritePos != sSeqCmdReadPos) { AudioSeq_ProcessSeqCmd(sAudioSeqCmds[sSeqCmdReadPos++]); } } u16 AudioSeq_GetActiveSeqId(u8 seqPlayerIndex) { if (gActiveSeqs[seqPlayerIndex].isWaitingForFonts == true) { return gActiveSeqs[seqPlayerIndex].startAsyncSeqCmd & SEQCMD_SEQID_MASK; } if (gActiveSeqs[seqPlayerIndex].seqId != NA_BGM_DISABLED) { return gActiveSeqs[seqPlayerIndex].seqId; } return NA_BGM_DISABLED; } s32 AudioSeq_IsSeqCmdNotQueued(u32 cmdVal, u32 cmdMask) { u8 i; for (i = sSeqCmdReadPos; i != sSeqCmdWritePos; i++) { if ((sAudioSeqCmds[i] & cmdMask) == cmdVal) { return false; } } return true; } // Unused void AudioSeq_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_SEQPLAYER_VOLUME`. * Unused */ void AudioSeq_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 AudioSeq_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_SEQPLAYER_VOLUME_NOW(seqPlayerIndex, volFadeTimer, volScale); } } /** * 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; } AUDIOCMD_SEQPLAYER_FADE_VOLUME_SCALE(seqPlayerIndex, 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 (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; } } } u8 AudioSeq_UpdateAudioHeapReset(void) { if (gAudioHeapResetState != AUDIO_HEAP_RESET_STATE_NONE) { if (gAudioHeapResetState == AUDIO_HEAP_RESET_STATE_RESETTING) { if (func_80193C5C() == 1) { gAudioHeapResetState = AUDIO_HEAP_RESET_STATE_NONE; AUDIOCMD_SEQPLAYER_SET_IO(SEQ_PLAYER_SFX, 0, gSfxChannelLayout); Audio_ResetForAudioHeapStep2(); } } else if (gAudioHeapResetState == AUDIO_HEAP_RESET_STATE_RESETTING_ALT) { while (func_80193C5C() != 1) {} gAudioHeapResetState = AUDIO_HEAP_RESET_STATE_NONE; AUDIOCMD_SEQPLAYER_SET_IO(SEQ_PLAYER_SFX, 0, gSfxChannelLayout); Audio_ResetForAudioHeapStep2(); } } return gAudioHeapResetState; } u8 AudioSeq_ResetReverb(void) { u8 isReverbFading = false; u8 fadeReverb = ((sResetAudioHeapSeqCmd & 0xFF0000) >> 16); u8 reverbIndex = 0; u8 specId; if (sResetAudioHeapSeqCmd != 0) { if (sResetAudioHeapTimer--) { while (fadeReverb != 0) { if (fadeReverb & 1) { AUDIOCMD_GLOBAL_SET_REVERB_DATA(reverbIndex, REVERB_DATA_TYPE_VOLUME, sResetAudioHeapFadeReverbVolume); AudioThread_ScheduleProcessCmds(); } reverbIndex++; fadeReverb = fadeReverb >> 1; } sResetAudioHeapFadeReverbVolume -= sResetAudioHeapFadeReverbVolumeStep; isReverbFading = true; } else { while (fadeReverb != 0) { if (fadeReverb & 1) { AUDIOCMD_GLOBAL_SET_REVERB_DATA( reverbIndex, REVERB_DATA_TYPE_SETTINGS, (s32)(gReverbSettingsTable[(u8)(sResetAudioHeapSeqCmd & 0xFF)] + reverbIndex)); AudioThread_ScheduleProcessCmds(); } reverbIndex++; fadeReverb = fadeReverb >> 1; } sResetAudioHeapSeqCmd = 0; AUDIOCMD_SEQPLAYER_SET_IO(SEQ_PLAYER_SFX, 0, gSfxChannelLayout); Audio_ResetForAudioHeapStep3(); } } return isReverbFading; } void AudioSeq_ResetActiveSequences(void) { u8 seqPlayerIndex; u8 scaleIndex; for (seqPlayerIndex = 0; seqPlayerIndex < SEQ_PLAYER_MAX; 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; gActiveSeqs[seqPlayerIndex].isWaitingForFonts = false; gActiveSeqs[seqPlayerIndex].isSeqPlayerInit = false; for (scaleIndex = 0; scaleIndex < VOL_SCALE_INDEX_MAX; scaleIndex++) { gActiveSeqs[seqPlayerIndex].volScales[scaleIndex] = 0x7F; } gActiveSeqs[seqPlayerIndex].volFadeTimer = 1; gActiveSeqs[seqPlayerIndex].fadeVolUpdate = true; } } void AudioSeq_ResetActiveSequencesAndVolume(void) { u8 seqPlayerIndex; u8 scaleIndex; for (seqPlayerIndex = 0; seqPlayerIndex < SEQ_PLAYER_MAX; 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; } } AudioSeq_ResetActiveSequences(); }