diff --git a/soh/include/seqcmd.h b/soh/include/seqcmd.h new file mode 100644 index 0000000000..53fce27bfb --- /dev/null +++ b/soh/include/seqcmd.h @@ -0,0 +1,512 @@ +#ifndef SEQCMD_H +#define SEQCMD_H + +#include "sequence.h" + +// ==== Primary commands ==== + +#define SEQCMD_OP_MASK 0xF0000000 + +typedef enum SeqCmdOp { + /* 0x0 */ SEQCMD_OP_PLAY_SEQUENCE, + /* 0x1 */ SEQCMD_OP_STOP_SEQUENCE, + /* 0x2 */ SEQCMD_OP_QUEUE_SEQUENCE, + /* 0x3 */ SEQCMD_OP_UNQUEUE_SEQUENCE, + /* 0x4 */ SEQCMD_OP_SET_SEQPLAYER_VOLUME, + /* 0x5 */ SEQCMD_OP_SET_SEQPLAYER_FREQ, + /* 0x6 */ SEQCMD_OP_SET_CHANNEL_VOLUME, + /* 0x7 */ SEQCMD_OP_SET_SEQPLAYER_IO, + /* 0x8 */ SEQCMD_OP_SET_CHANNEL_IO, + /* 0x9 */ SEQCMD_OP_SET_CHANNEL_IO_DISABLE_MASK, + /* 0xA */ SEQCMD_OP_SET_CHANNEL_DISABLE_MASK, + /* 0xB */ SEQCMD_OP_TEMPO_CMD, + /* 0xC */ SEQCMD_OP_SETUP_CMD, + /* 0xD */ SEQCMD_OP_SET_CHANNEL_FREQ, + /* 0xE */ SEQCMD_OP_GLOBAL_CMD, + /* 0xF */ SEQCMD_OP_RESET_AUDIO_HEAP +} SeqCmdOp; + +// ==== Secondary commands ==== + +// Subset of `SEQCMD_OP_TEMPO_CMD` +typedef enum SeqCmdTempoCmdOp { + /* 0x0 */ SEQCMD_SUB_OP_TEMPO_SET, + /* 0x1 */ SEQCMD_SUB_OP_TEMPO_SPEED_UP, + /* 0x2 */ SEQCMD_SUB_OP_TEMPO_SLOW_DOWN, + /* 0x3 */ SEQCMD_SUB_OP_TEMPO_SCALE, + /* 0x4 */ SEQCMD_SUB_OP_TEMPO_RESET +} SeqCmdTempoCmdOp; + +// Subset of `SEQCMD_OP_SETUP_CMD` +typedef enum SeqCmdSetupCmdOp { + /* 0x0 */ SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME, + /* 0x1 */ SEQCMD_SUB_OP_SETUP_SEQ_UNQUEUE, + /* 0x2 */ SEQCMD_SUB_OP_SETUP_RESTART_SEQ, + /* 0x3 */ SEQCMD_SUB_OP_SETUP_TEMPO_SCALE, + /* 0x4 */ SEQCMD_SUB_OP_SETUP_TEMPO_RESET, + /* 0x5 */ SEQCMD_SUB_OP_SETUP_PLAY_SEQ, + /* 0x6 */ SEQCMD_SUB_OP_SETUP_SET_FADE_TIMER, + /* 0x7 */ SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME_IF_QUEUED, + /* 0x8 */ SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME_WITH_SCALE_INDEX, + /* 0x9 */ SEQCMD_SUB_OP_SETUP_SET_CHANNEL_DISABLE_MASK, + /* 0xA */ SEQCMD_SUB_OP_SETUP_SET_SEQPLAYER_FREQ, + /* 0xE */ SEQCMD_SUB_OP_SETUP_POP_PERSISTENT_CACHE = 0xE, + /* 0xF */ SEQCMD_SUB_OP_SETUP_RESET_SETUP_CMDS +} SeqCmdSetupCmdOp; + +// Subset of `SEQCMD_OP_GLOBAL_CMD` +typedef enum SeqCmdSubCmdOp { + /* 0x0 */ SEQCMD_SUB_OP_GLOBAL_SET_SOUND_OUTPUT_MODE, + /* 0x1 */ SEQCMD_SUB_OP_GLOBAL_DISABLE_NEW_SEQUENCES +} SeqCmdSubCmdOp; + +// ==== Audio Sequence Primary Commands ==== + +/** + * Play a sequence on a given seqPlayer + * + * @param seqPlayerIndex the index of the seqPlayer to play the sequence + * @param fadeInDuration effect will depend on seqArg. See below + * @param seqArg no effect: < 0x7F, skip ahead: = 0x7F, will not play: >= 0x80 (see note) + * @param seqId the id of the sequence to play, see `SeqId` + * + * @note seqArg will also be stored in gActiveSeqs.seqId, any check against that seqId must also include seqArg. + * seqArg < 0x7F: fade in the sequence over `fadeInDuration` in units of (1/30th) seconds + * seqArg = 0x7F: start the sequence immediately, but begins `fadeInDuration` number of second into the sequence. + * seqArg >= 0x80: no sequence will play. Intended to load a soundFont asynchronously but was only half implemented + * (inferred from MM). + */ +#define SEQCMD_PLAY_SEQUENCE(seqPlayerIndex, fadeInDuration, seqArg, seqId) \ + Audio_QueueSeqCmd((SEQCMD_OP_PLAY_SEQUENCE << 28) | ((u8)(seqPlayerIndex) << 24) | ((u8)(fadeInDuration) << 16) | \ + ((u8)(seqArg) << 8) | (u16)(seqId)) + +/** + * Stop a sequence on a given seqPlayer + * + * @param seqPlayerIndex the index of the seqPlayer to stop the sequence + * @param fadeOutDuration duration the sequence will fade out over + * + * @note the 0xFF in the command is not read from at all, but is common in all Stop SeqPlayer Commands + */ +#define SEQCMD_STOP_SEQUENCE(seqPlayerIndex, fadeOutDuration) \ + Audio_QueueSeqCmd((SEQCMD_OP_STOP_SEQUENCE << 28) | 0xFF | ((u8)(seqPlayerIndex) << 24) | \ + ((u8)(fadeOutDuration) << 16)) + +/** + * Add a sequence to a queue of sequences associated with a given seqPlayer. + * If the sequence is first in queue, play the sequence + * + * @param seqPlayerIndex the index of the seqPlayer to queue the sequence + * @param fadeInDuration if the sequence is played, duration the sequence will fade in over + * @param priority priority in the queue. The highest valued priority will be queued to play first. Also used as `seqArg`. + * @param seqId the id of the sequence to play, see `SeqId` + * + * @note for the next sequence in the queue to play, the current sequence must be unqueued with the unqueue command + * @note for a priority >= 0x7F, similar effects happen as `seqArg` in the play sequence command + */ +#define SEQCMD_QUEUE_SEQUENCE(seqPlayerIndex, fadeInDuration, priority, seqId) \ + Audio_QueueSeqCmd((SEQCMD_OP_QUEUE_SEQUENCE << 28) | ((u8)(seqPlayerIndex) << 24) | ((u8)(fadeInDuration) << 16) | \ + ((u8)(priority) << 8) | (u8)(seqId)) + +/** + * Remove a sequence from a queue of sequences associated with a given seqPlayer. + * If the sequence is first in queue, stop the sequence, and play the next one in queue if any + * + * @param seqPlayerIndex the index of the seqPlayer to queue the sequence + * @param fadeInDuration if the sequence is stopped, duration the sequence will fade out over, and duration the next seq will fade in over + * @param seqId the id of the sequence to remove, see `SeqId` + * + * @note for the next sequence in the queue to play, the current sequence must be unqueued with this command + */ +#define SEQCMD_UNQUEUE_SEQUENCE(seqPlayerIndex, fadeOutInDuration, seqId) \ + Audio_QueueSeqCmd((SEQCMD_OP_UNQUEUE_SEQUENCE << 28) | ((u8)(seqPlayerIndex) << 24) | \ + ((u8)(fadeOutInDuration) << 16) | (seqId)) + +/** + * Set the volume of an entire sequence on a given seqPlayer. Fade to the volume over a specified duration + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the volume + * @param volume the target volume for the sequence. Ranged from 0-0xFF, with 0x7F mapping to 1.0f + */ +#define SEQCMD_SET_SEQPLAYER_VOLUME(seqPlayerIndex, duration, volume) \ + Audio_QueueSeqCmd((SEQCMD_OP_SET_SEQPLAYER_VOLUME << 28) | ((u8)(seqPlayerIndex) << 24) | ((duration) << 16) | \ + (volume)) + +/** + * Scale the frequency of every channel on a given seqPlayer over a specified duration. + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the frequency + * @param freqScale the scaling factor to shift the pitch. Ranged from 0-0xFFFF, with 1000 mapping to 1.0f + * + * @note 2000 will double the frequency (raise an octave), 500 will halve the frequency (lower an octave). + * Cannot be used with `SEQCMD_SET_CHANNEL_FREQ` as they will overwrite one another. + */ +#define SEQCMD_SET_SEQPLAYER_FREQ(seqPlayerIndex, duration, freqScale) \ + Audio_QueueSeqCmd((SEQCMD_OP_SET_SEQPLAYER_FREQ << 28) | ((u8)(seqPlayerIndex) << 24) | ((duration) << 16) | \ + (freqScale)) + +/** + * Scale the frequency of a specific channel on a given seqPlayer over a specified duration + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param channelIndex the index of the channel to modify + * @param duration duration to transition to the frequency + * @param freqScale the scaling factor to shift the pitch. Ranged from 0-0xFFF, with 1000 mapping to 1.0f + * + * @note a frequency of 2000 will double the frequency (raise an octave), 500 will halve the frequency (lower an octave). + * Cannot be used with `SEQCMD_SET_SEQPLAYER_FREQ` as they will overwrite one another. + */ +#define SEQCMD_SET_CHANNEL_FREQ(seqPlayerIndex, channelIndex, duration, freqScale) \ + Audio_QueueSeqCmd((SEQCMD_OP_SET_CHANNEL_FREQ << 28) | ((u8)(seqPlayerIndex) << 24) | ((duration) << 16) | \ + ((channelIndex) << 12) | (freqScale)) + +/** + * Set the volume of a specific channel on a given seqPlayer. Fade to the volume over a specified duration + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param channelIndex the index of the channel to modify + * @param duration duration to transition to the volume + * @param volume the target volume for the sequence. Ranged from 0-0xFF, with 0x7F mapping to 1.0f + */ +#define SEQCMD_SET_CHANNEL_VOLUME(seqPlayerIndex, channelIndex, duration, volume) \ + Audio_QueueSeqCmd((SEQCMD_OP_SET_CHANNEL_VOLUME << 28) | ((u8)(seqPlayerIndex) << 24) | ((u8)(duration) << 16) | \ + ((u8)(channelIndex) << 8) | ((u8)volume)) + +/** + * Write a value that can be read as input directly by the sequence itself. This will be set to the global + * ioPort, which can affect the entire sequence. + * + * @param seqPlayerIndex the index of the seqPlayer to write the input to + * @param ioPort the index of the array to store the input-output value + * @param ioData the value s8 that's written to the input-output array + * + * @note Each seqPlayer has 8 global ioPorts indexed 0-7. + * ioPort 0 and 1 are read-only-once, and will reset after being read by the sequence. + * ioPort 2-7 can be read multiple times. + */ +#define SEQCMD_SET_SEQPLAYER_IO(seqPlayerIndex, ioPort, ioData) \ + Audio_QueueSeqCmd((SEQCMD_OP_SET_SEQPLAYER_IO << 28) | ((u8)(seqPlayerIndex) << 24) | ((u8)(ioPort) << 16) | \ + (u8)(ioData)) + +/** + * Write a value that can be read as input directly by the sequence itself. This will be set to the channel + * ioPort, which will only affect a single channel. + * + * @param seqPlayerIndex the index of the seqPlayer to write the input to + * @param channelIndex the index of the channel to write the input to + * @param ioPort the index of the array to store the input-output value + * @param ioData the value s8 that's written to the input-output array + * + * @note Each channel has 8 channel ioPorts indexed 0-7. + * ioPort 0 and 1 are read-only-once, and will reset after being read by the sequence. + * ioPort 2-7 can be read multiple times. + */ +#define SEQCMD_SET_CHANNEL_IO(seqPlayerIndex, channelIndex, ioPort, ioData) \ + Audio_QueueSeqCmd((SEQCMD_OP_SET_CHANNEL_IO << 28) | ((u8)(seqPlayerIndex) << 24) | ((u8)(ioPort) << 16) | \ + ((u8)(channelIndex) << 8) | (u8)(ioData)) + +/** + * Disable (or reenable) specific channels from receiving input set by `SEQCMD_SET_CHANNEL_IO` + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param channelMask a 16 bit mask where each bit maps to a channel. Bitflag on to disable + * + * @note using AUDIOCMD_CHANNEL_SET_IO will bypass this channelMask + */ +#define SEQCMD_SET_CHANNEL_IO_DISABLE_MASK(seqPlayerIndex, channelMask) \ + Audio_QueueSeqCmd((SEQCMD_OP_SET_CHANNEL_IO_DISABLE_MASK << 28) | ((u8)(seqPlayerIndex) << 24) | (u16)(channelMask)) + +/** + * Disable (or reenable) specific channels + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param channelMask a 16 bit mask where each bit maps to a channel. Bitflag on to disable + */ +#define SEQCMD_SET_CHANNEL_DISABLE_MASK(seqPlayerIndex, channelMask) \ + Audio_QueueSeqCmd((SEQCMD_OP_SET_CHANNEL_DISABLE_MASK << 28) | ((u8)(seqPlayerIndex) << 24) | (u16)(channelMask)) + +// ==== Audio Sequence Tempo Commands ==== + +/** + * Set the absolute tempo of a sequence on a given seqPlayer, fading over a specified duration + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the tempo + * @param tempoTarget the target tempo for the sequence + * + * @note the absolute tempo is constrained to a maximum of 300 + */ +#define SEQCMD_SET_TEMPO(seqPlayerIndex, duration, tempoTarget) \ + Audio_QueueSeqCmd((SEQCMD_OP_TEMPO_CMD << 28) | (SEQCMD_SUB_OP_TEMPO_SET << 12) | ((u8)(seqPlayerIndex) << 24) | \ + ((u8)(duration) << 16) | (u16)(tempoTarget)) + +/** + * Increase the tempo of a sequence by a relative value on a given seqPlayer over a specified duration + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the tempo + * @param tempoIncrease the change to add to the current tempo + * + * @note the absolute tempo is constrained to a maximum of 300 + */ +#define SEQCMD_SPEED_UP_TEMPO(seqPlayerIndex, duration, tempoIncrease) \ + Audio_QueueSeqCmd((SEQCMD_OP_TEMPO_CMD << 28) | (SEQCMD_SUB_OP_TEMPO_SPEED_UP << 12) | \ + ((u8)(seqPlayerIndex) << 24) | ((u8)(duration) << 16) | (u16)(tempoIncrease)) + +/** + * Decrease the tempo of a sequence by a relative value on a given seqPlayer over a specified duration + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the tempo + * @param tempoDecrease the change to subtract to the current tempo + * + * @note the absolute tempo is constrained to a maximum of 300 + */ +#define SEQCMD_SLOW_DOWN_TEMPO(seqPlayerIndex, duration, tempoDecrease) \ + Audio_QueueSeqCmd((SEQCMD_OP_TEMPO_CMD << 28) | (SEQCMD_SUB_OP_TEMPO_SLOW_DOWN << 12) | \ + ((u8)(seqPlayerIndex) << 24) | ((u8)(duration) << 16) | (u16)(tempoDecrease)) + +/** + * Scale the tempo of a sequence by a multiplicative value on a given seqPlayer over a specified duration + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the tempo + * @param tempoScale the scaling factor of the tempo, relative to 100 + * + * @note a tempoScale of 200 will go double-time, 50 will go half-time. + * the absolute tempo is constrained to a maximum of 300 + */ +#define SEQCMD_SCALE_TEMPO(seqPlayerIndex, duration, tempoScale) \ + Audio_QueueSeqCmd((SEQCMD_OP_TEMPO_CMD << 28) | (SEQCMD_SUB_OP_TEMPO_SCALE << 12) | ((u8)(seqPlayerIndex) << 24) | \ + ((u8)(duration) << 16) | (u16)(tempoScale)) + +/** + * Reset the tempo of a sequence to the original tempo on a given seqPlayer over a specified duration + * + * @param seqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the tempo + */ +#define SEQCMD_RESET_TEMPO(seqPlayerIndex, duration) \ + Audio_QueueSeqCmd((SEQCMD_OP_TEMPO_CMD << 28) | (SEQCMD_SUB_OP_TEMPO_RESET << 12) | ((u8)(seqPlayerIndex) << 24) | \ + ((u8)(duration) << 16)) + +/** + * ==== Audio Sequence Setup Commands ==== + * + * The setup commands are designed to wait to be executed. + * Up to 8 commands can be queued per `seqPlayerIndex`. + * These commands will only execute once that `seqPlayerIndex` is finished playing and is no longer enabled. + * They will often target a different player (`targetSeqPlayerIndex`) but not always. + */ + +/** + * Setup a request to restore a volume on target seqPlayer once a setup seqPlayer is finished playing and disabled + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param targetSeqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the volume + */ +#define SEQCMD_SETUP_RESTORE_SEQPLAYER_VOLUME(setupSeqPlayerIndex, targetSeqPlayerIndex, duration) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)(targetSeqPlayerIndex) << 16) | (u8)(duration)) + +/** + * Setup a request to unqueue a seqPlayer once that same setup seqPlayer is finished playing and disabled + * + * @param setupSeqPlayerIndex the index of the seqPlayer to unqueue once the same seqPlayer is disabled + * + * @bug this command was misimplemented and fails to unqueue. The command relies on `gActiveSeqs.seqId` for + * unqueueing, but seqId is reset before being used to unqueue. A simple fix is to unqueue based on + * `gActiveSeqs.prevSeqId` instead. + */ +#define SEQCMD_SETUP_UNQUEUE_SEQUENCE(setupSeqPlayerIndex) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_SEQ_UNQUEUE << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24)) + +/** + * Setup a request to restart and play an active sequence currently playing on a target seqPlayer + * once a setup seqPlayer is finished playing and disabled + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param targetSeqPlayerIndex the index of the seqPlayer to restart its active sequence + */ +#define SEQCMD_SETUP_RESTART_SEQUENCE(setupSeqPlayerIndex, targetSeqPlayerIndex) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_RESTART_SEQ << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)(targetSeqPlayerIndex) << 16)) + +/** + * Setup a request to scale the tempo of a sequence by a multiplicative value on a target seqPlayer + * once a setup seqPlayer is finished playing and disabled + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param targetSeqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the tempo + * @param tempoScale the scaling factor of the tempo, relative to 100 + * + * @note a tempoScale of 200 will go double-time, 50 will go half-time. + * the absolute tempo is constrained to a maximum of 300 + */ +#define SEQCMD_SETUP_SCALE_TEMPO(setupSeqPlayerIndex, targetSeqPlayerIndex, duration, tempoScale) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_TEMPO_SCALE << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)(targetSeqPlayerIndex) << 16) | ((u8)(duration) << 8) | \ + (u8)(tempoScale)) + +/** + * Setup a request to reset the tempo of a sequence to the original tempo on a target seqPlayer + * once a setup seqPlayer is finished playing and disabled + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param targetSeqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the tempo + */ +#define SEQCMD_SETUP_RESET_TEMPO(setupSeqPlayerIndex, targetSeqPlayerIndex, duration) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_TEMPO_RESET << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)(targetSeqPlayerIndex) << 16) | (u8)(duration)) + +/** + * Setup a request to play a sequence on a target seqPlayer once a setup seqPlayer is finished playing and disabled. + * This command is optionally paired with `SEQCMD_SETUP_SET_FADE_IN_TIMER` to set the fade in duration + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param targetSeqPlayerIndex the index of the seqPlayer to play the sequence + * @param seqId the id of the sequence to play, see `SeqId` + */ +#define SEQCMD_SETUP_PLAY_SEQUENCE(setupSeqPlayerIndex, targetSeqPlayerIndex, seqId) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_PLAY_SEQ << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)(targetSeqPlayerIndex) << 16) | (u16)(seqId)) + +/** + * This command is an optional command before `SEQCMD_SETUP_PLAY_SEQUENCE` to set the fade in duration + * (see cmd above) + * + * @param targetSeqPlayerIndex the index of the seqPlayer to play the sequence + * @param fadeInDuration duration the sequence will fade in over + */ +#define SEQCMD_SETUP_SET_FADE_IN_TIMER(targetSeqPlayerIndex, fadeInDuration) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_SET_FADE_TIMER << 20) | \ + ((u8)(targetSeqPlayerIndex) << 24) | ((u8)(fadeInDuration) << 8)) + +/** + * Setup a request to restore a volume on target seqPlayer once a setup seqPlayer is finished playing and disabled. + * Specifically, it will only restore volume if the number of queued requests on the setup seqPlayer matches + * the number of sequences queued + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param targetSeqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the volume + * @param numSeqRequests the number of sequence requests queued that must match the actual number of sequence requests + */ +#define SEQCMD_SETUP_RESTORE_SEQPLAYER_VOLUME_IF_QUEUED(setupSeqPlayerIndex, targetSeqPlayerIndex, duration, \ + numSeqRequests) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME_IF_QUEUED << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)(targetSeqPlayerIndex) << 16) | ((u8)(duration) << 8) | \ + (u8)(numSeqRequests)) + +/** + * Setup a request to restore a volume on target seqPlayer once a setup seqPlayer is finished playing and disabled. + * Allows `scaleIndex` to be specified. + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param targetSeqPlayerIndex the index of the seqPlayer to modify + * @param scaleIndex the scale index of a seqPlayer + * @param duration duration to transition to the volume + */ +#define SEQCMD_SETUP_RESTORE_SEQPLAYER_VOLUME_WITH_SCALE_INDEX(setupSeqPlayerIndex, targetSeqPlayerIndex, scaleIndex, \ + duration) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | \ + (SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME_WITH_SCALE_INDEX << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)(targetSeqPlayerIndex) << 16) | \ + ((u8)(scaleIndex) << 8) | (u8)(duration)) + +/** + * Setup a request to disable (or reenable) specific channels on a target seqPlayer + * once a setup seqPlayer is finished playing and disabled. + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param targetSeqPlayerIndex the index of the seqPlayer to modify + * @param channelMask a 16 bit mask where each bit maps to a channel. Bitflag on to disable + */ +#define SEQCMD_SETUP_SET_CHANNEL_DISABLE_MASK(setupSeqPlayerIndex, targetSeqPlayerIndex, channelMask) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_SET_CHANNEL_DISABLE_MASK << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)(targetSeqPlayerIndex) << 16) | (u16)(channelMask)) + +/** + * Queue a request to scale the frequency of an entire sequence on a target seqPlayer + * once a setup seqPlayer is finished playing and disabled. + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param targetSeqPlayerIndex the index of the seqPlayer to modify + * @param duration duration to transition to the frequency + * @param freqScale the scaling factor to shift the pitch. Ranged from 0-0xFF, with 100 mapping to 1.0f + * + * @note The base value for frequency, 100, is 10 times smaller than other frequency commands. + * 200 will double the frequency (raise an octave), 50 will halve the frequency (lower an octave). + */ +#define SEQCMD_SETUP_SET_SEQPLAYER_FREQ(setupSeqPlayerIndex, targetSeqPlayerIndex, duration, freqScale) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_SET_SEQPLAYER_FREQ << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)(targetSeqPlayerIndex) << 16) | ((u8)(duration) << 8) | \ + (u8)(freqScale)) + +/** + * Queue a request to discard audio data by popping an entry from the persistent caches on the audio heap, + * once a setup seqPlayer is finished playing and disabled. + * + * @param setupSeqPlayerIndex the index of the seqPlayer to wait for to be disabled + * @param tableTypeFlag All tables with the flag `(tableTypeFlag & (1 << tableType))` will be discarded. Specifically: + * `(tableTypeFlag & 1)` will discard the `SEQUENCE_TABLE` + * `(tableTypeFlag & 2)` will discard the `FONT_TABLE` + * `(tableTypeFlag & 4)` will discard the `SAMPLE_TABLE` + */ +#define SEQCMD_SETUP_POP_PERSISTENT_CACHE(setupSeqPlayerIndex, tableTypeFlag) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_POP_PERSISTENT_CACHE << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24) | ((u8)tableTypeFlag)) + +/** + * Discard all setup command requests on a seqPlayerIndex by resetting the setup command queue + * + * @param setupSeqPlayerIndex the index of the seqPlayer to disable all setup commands + */ +#define SEQCMD_RESET_SETUP_CMDS(setupSeqPlayerIndex) \ + Audio_QueueSeqCmd((SEQCMD_OP_SETUP_CMD << 28) | (SEQCMD_SUB_OP_SETUP_RESET_SETUP_CMDS << 20) | \ + ((u8)(setupSeqPlayerIndex) << 24)) + +/** + * ==== Audio Sequence Global Commands ==== + * + * The global commands will apply to the entire audio system and all 4 sequence players + */ + +/** + * Change the sound output mode + * + * @param soundOutputMode see the `SoundOutputMode` enum + */ +#define SEQCMD_SET_SOUND_OUTPUT_MODE(soundOutputMode) \ + Audio_QueueSeqCmd((SEQCMD_OP_GLOBAL_CMD << 28) | (SEQCMD_SUB_OP_GLOBAL_SET_SOUND_OUTPUT_MODE << 8) | (u8)(soundOutputMode)) + +/** + * Disable (or reenable) new sequences from starting + * + * @param isDisabled true to disable, false to enable + * + * @note this does not disable the sfx player + */ +#define SEQCMD_DISABLE_PLAY_SEQUENCES(isDisabled) \ + Audio_QueueSeqCmd((SEQCMD_OP_GLOBAL_CMD << 28) | (SEQCMD_SUB_OP_GLOBAL_DISABLE_NEW_SEQUENCES << 8) | \ + (u16)(isDisabled)) + +/** + * Restart the audio heap with the specified settings + * + * @param sfxChannelLayout index for different mappings between the 7 banks and the 16 channels + * @param specId index for the audio specifications to set high-level audio parameters + * + * @note: For sfxChannelLayout, There are 4 possible layouts indexed by 0-3. + * However, only index 0 is properly implemented. Other indices lead to bugs and softlocks. + */ +#define SEQCMD_RESET_AUDIO_HEAP(sfxChannelLayout, specId) \ + Audio_QueueSeqCmd((SEQCMD_OP_RESET_AUDIO_HEAP << 28) | ((u8)(sfxChannelLayout) << 8) | (u8)(specId)) + +#endif diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/Twinrova.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/Twinrova.cpp new file mode 100644 index 0000000000..3e800fb681 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/Twinrova.cpp @@ -0,0 +1,122 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "seqcmd.h" +#include "src/overlays/actors/ovl_Boss_Tw/z_boss_tw.h" +#include "assets/objects/object_tw/object_tw.h" +extern BossTw* sTwinrovaPtr; +extern void BossTw_SetupFlyTo(BossTw*, PlayState*); +extern void BossTw_SetupWait(BossTw*, PlayState*); +} + +typedef enum { + /* 0x00 */ TW_KOTAKE, + /* 0x01 */ TW_KOUME, +} TwinrovaType; + +// Adjust alpha for spawning portals on the platforms +extern "C" void BossTwShip_TwinrovaHandlePortal(BossTw* bossTw, PlayState* play) { + if (bossTw->timers[0]-- > 0) { + Math_StepToF(&bossTw->spawnPortalAlpha, 255.0f, 10.0f); + } else if (Math_StepToF(&bossTw->spawnPortalAlpha, 0.0f, 10.0f)) { + BossTw_SetupWait(bossTw, play); + } +} + +extern "C" void BossTwShip_TwinrovaSetupHandlePortal(BossTw* bossTw, PlayState* play) { + bossTw->timers[0] = 80; + bossTw->spawnPortalAlpha = 1.0f; + bossTw->spawnPortalScale = 0.05f; + bossTw->actionFunc = BossTwShip_TwinrovaHandlePortal; +} + +// Rise from below platform, then enter normal gameplay +extern "C" void BossTwShip_Rise(BossTw* bossTw, PlayState* play) { + Player* player = GET_PLAYER(play); + + Math_SmoothStepToF(&bossTw->actor.world.pos.y, 400.0f, 300.0f, 6.0f, 4.0f); + bossTw->actor.world.rot.y = bossTw->actor.shape.rot.y = Actor_WorldYawTowardActor(&bossTw->actor, &player->actor); + SkelAnime_Update(&bossTw->skelAnime); + Audio_PlayActorSound2(&bossTw->actor, NA_SE_EN_TWINROBA_FLY - SFX_FLAG); + Math_ApproachF(&bossTw->scepterAlpha, 0.0f, 1.0f, 10.0f); + if (bossTw->actor.world.pos.y >= 400.0f) { + BossTw_SetupFlyTo(bossTw, play); + if (bossTw->actor.params == TW_KOUME) { + Audio_PlayActorSound2(&bossTw->actor, NA_SE_EN_TWINROBA_LAUGH2); + } + } +} + +extern "C" void BossTwShip_SetupRise(BossTw* bossTw, PlayState* play) { + Player* player = GET_PLAYER(play); + + bossTw->actor.flags |= ACTOR_FLAG_ATTENTION_ENABLED; + bossTw->actionFunc = BossTwShip_Rise; + if (bossTw->actor.params == TW_KOTAKE) { + Audio_PlayActorSound2(&bossTw->actor, NA_SE_EN_TWINROBA_LAUGH); + } +} + +// Wait for player to climb up to start fight +extern "C" void BossTwShip_WaitHeight(BossTw* bossTw, PlayState* play) { + Player* player = GET_PLAYER(play); + + if (player->actor.world.pos.y >= 150.0f) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_TWINROVA_BATTLE); + SEQCMD_PLAY_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 0, 0, NA_BGM_BOSS); + BossTwShip_SetupRise(bossTw, play); + // The spawn portal is drawn by the Twinrova type BossTw actor, not Kotake/Koume + BossTwShip_TwinrovaSetupHandlePortal(sTwinrovaPtr, play); + } +} + +extern "C" void BossTwShip_SetupWaitHeight(BossTw* bossTw, PlayState* play) { + Player* player = GET_PLAYER(play); + + // X and Z are swapped + bossTw->actor.world.pos.x = (bossTw->actor.params == TW_KOTAKE) ? -600.0f : 600.0f; + bossTw->actor.world.pos.y = 60.0f; + bossTw->actor.world.rot.y = bossTw->actor.shape.rot.y = Actor_WorldYawTowardActor(&bossTw->actor, &player->actor); + bossTw->unk_5F8 = true; // Hair activated + bossTw->actionFunc = BossTwShip_WaitHeight; +} + +void RegisterTwinrovaSkipIntroCS() { + COND_VB_SHOULD(VB_PLAY_TWINROVA_INTRO_CS, + (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)), { + BossTw* bossTw = va_arg(args, BossTw*); + PlayState* play = va_arg(args, PlayState*); + + if (bossTw->actor.params == TW_KOTAKE || bossTw->actor.params == TW_KOUME) { + BossTwShip_SetupWaitHeight(bossTw, play); + } else if (!Flags_GetClear(play, play->roomCtx.curRoom.num)) { + // Twinrova + SEQCMD_PLAY_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 0, 0, NA_BGM_KOTAKE_KOUME); + BossTw_SetupWait(bossTw, play); + bossTw->visible = false; + bossTw->actor.world.pos.y = -2000.0f; + bossTw->actor.flags &= ~ACTOR_FLAG_ATTENTION_ENABLED; + } + + *should = false; + }); + + // Swap portal XZ to match new Kotake/Koume spawn position + COND_VB_SHOULD(VB_TWINROVA_SPAWN_PORTAL_TRANSLATION_KOTAKE, + (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)), { + Matrix_Translate(-600.0f, 232.0f, 0.0f, MTXMODE_NEW); // Ice/Kotake + *should = false; + }); + COND_VB_SHOULD(VB_TWINROVA_SPAWN_PORTAL_TRANSLATION_KOUME, + (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)), { + Matrix_Translate(600.0f, 232.0f, 0.0f, MTXMODE_NEW); // Fire/Koume + *should = false; + }); +} + +static RegisterShipInitFunc initTwinrovaSkipIntroCS(RegisterTwinrovaSkipIntroCS, + { CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), + "IS_RANDO" }); diff --git a/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp index f8e67442cd..23aba6f03e 100644 --- a/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp @@ -1266,9 +1266,9 @@ void TimeSaverOnSceneInitHandler(int16_t sceneNum) { } break; case SCENE_SPIRIT_TEMPLE_BOSS: + // For Twinrova see Twinrova.cpp if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_TWINROVA_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_TWINROVA_BATTLE); + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_NABOORU_BATTLE)) { Flags_SetEventChkInf(EVENTCHKINF_BEGAN_NABOORU_BATTLE); Flags_SetEventChkInf(EVENTCHKINF_NABOORU_ORDERED_TO_FIGHT_BY_TWINROVA); } diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 8689bc41a2..1831afe1e4 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2005,6 +2005,31 @@ typedef enum { // - None VB_PLAY_NABOORU_CAPTURED_CS, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*BossTw` + // - `*PlayState` + VB_PLAY_TWINROVA_INTRO_CS, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_TWINROVA_SPAWN_PORTAL_TRANSLATION_KOTAKE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_TWINROVA_SPAWN_PORTAL_TRANSLATION_KOUME, + // #### `result` // ```c // true diff --git a/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c b/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c index b0b79eaca9..b3adbd4e2d 100644 --- a/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c +++ b/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c @@ -178,7 +178,7 @@ static s8 sEnvType; static u8 sGroundBlastType; static BossTw* sKotakePtr; static BossTw* sKoumePtr; -static BossTw* sTwinrovaPtr; +BossTw* sTwinrovaPtr; // global for CS skip hook static u8 sShieldFireCharge; static u8 sShieldIceCharge; static f32 D_8094C854; @@ -495,7 +495,7 @@ void BossTw_Init(Actor* thisx, PlayState* play2) { this->actor.world.pos.y = 400.0f; this->actor.world.pos.z = 0.0f; Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_BOSS); - } else { + } else if (GameInteractor_Should(VB_PLAY_TWINROVA_INTRO_CS, true, this, play)) { BossTw_SetupCSWait(this, play); } @@ -512,7 +512,7 @@ void BossTw_Init(Actor* thisx, PlayState* play2) { this->actor.world.pos.x = 600.0f; this->actor.world.pos.y = 400.0f; this->actor.world.pos.z = 0.0f; - } else { + } else if (GameInteractor_Should(VB_PLAY_TWINROVA_INTRO_CS, true, this, play)) { BossTw_SetupCSWait(this, play); } @@ -531,7 +531,7 @@ void BossTw_Init(Actor* thisx, PlayState* play2) { if (Flags_GetEventChkInf(EVENTCHKINF_BEGAN_TWINROVA_BATTLE)) { // began twinrova battle BossTw_SetupWait(this, play); - } else { + } else if (GameInteractor_Should(VB_PLAY_TWINROVA_INTRO_CS, true, this, play)) { BossTw_TwinrovaSetupIntroCS(this, play); this->actor.world.pos.x = 0.0f; this->actor.world.pos.y = 1000.0f; @@ -3782,7 +3782,9 @@ void BossTw_SpawnPortalDraw(BossTw* this, PlayState* play) { Matrix_Push(); - Matrix_Translate(0.0f, 232.0f, -600.0f, MTXMODE_NEW); + if (GameInteractor_Should(VB_TWINROVA_SPAWN_PORTAL_TRANSLATION_KOTAKE, true)) { + Matrix_Translate(0.0f, 232.0f, -600.0f, MTXMODE_NEW); + } Matrix_Scale(this->spawnPortalScale, this->spawnPortalScale, this->spawnPortalScale, MTXMODE_APPLY); gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 0, 0, 0, (s16)this->spawnPortalAlpha); gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH); @@ -3794,7 +3796,9 @@ void BossTw_SpawnPortalDraw(BossTw* this, PlayState* play) { gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH); gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(gTwinrovaKotakeMagicSigilDL)); - Matrix_Translate(0.0f, 232.0f, 600.0f, MTXMODE_NEW); + if (GameInteractor_Should(VB_TWINROVA_SPAWN_PORTAL_TRANSLATION_KOUME, true)) { + Matrix_Translate(0.0f, 232.0f, 600.0f, MTXMODE_NEW); + } Matrix_Scale(this->spawnPortalScale, this->spawnPortalScale, this->spawnPortalScale, MTXMODE_APPLY); gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 0, 0, 0, (s16)this->spawnPortalAlpha); gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);