2019-08-25 00:46:40 -04:00
|
|
|
#include <ultra64.h>
|
|
|
|
#include <macros.h>
|
|
|
|
|
|
|
|
#include "memory.h"
|
|
|
|
#include "data.h"
|
|
|
|
#include "load.h"
|
|
|
|
#include "seqplayer.h"
|
|
|
|
#include "external.h"
|
|
|
|
#include "effects.h"
|
|
|
|
|
|
|
|
void seq_channel_layer_process_script(struct SequenceChannelLayer *layer);
|
|
|
|
void sequence_channel_process_script(struct SequenceChannel *seqChannel);
|
|
|
|
|
|
|
|
void sequence_channel_init(struct SequenceChannel *seqChannel) {
|
|
|
|
s32 i;
|
|
|
|
|
|
|
|
seqChannel->enabled = FALSE;
|
|
|
|
seqChannel->finished = FALSE;
|
|
|
|
seqChannel->stopScript = FALSE;
|
|
|
|
seqChannel->unk0b10 = FALSE;
|
|
|
|
seqChannel->hasInstrument = FALSE;
|
|
|
|
seqChannel->stereoHeadsetEffects = FALSE;
|
|
|
|
seqChannel->transposition = 0;
|
|
|
|
seqChannel->largeNotes = FALSE;
|
|
|
|
seqChannel->scriptState.depth = 0;
|
|
|
|
seqChannel->volume = 1.0f;
|
|
|
|
seqChannel->volumeScale = 1.0f;
|
|
|
|
seqChannel->freqScale = 1.0f;
|
|
|
|
seqChannel->pan = 0.5f;
|
|
|
|
seqChannel->panChannelWeight = 1.0f;
|
|
|
|
seqChannel->noteUnused = NULL;
|
|
|
|
seqChannel->reverb = 0;
|
|
|
|
seqChannel->notePriority = NOTE_PRIORITY_DEFAULT;
|
|
|
|
seqChannel->delay = 0;
|
|
|
|
seqChannel->adsr.envelope = gDefaultEnvelope;
|
|
|
|
seqChannel->adsr.releaseRate = 0x20;
|
|
|
|
seqChannel->adsr.sustain = 0;
|
|
|
|
seqChannel->updatesPerFrameUnused = gAudioUpdatesPerFrame;
|
|
|
|
seqChannel->vibratoRateTarget = 0x800;
|
|
|
|
seqChannel->vibratoRateStart = 0x800;
|
|
|
|
seqChannel->vibratoExtentTarget = 0;
|
|
|
|
seqChannel->vibratoExtentStart = 0;
|
|
|
|
seqChannel->vibratoRateChangeDelay = 0;
|
|
|
|
seqChannel->vibratoExtentChangeDelay = 0;
|
|
|
|
seqChannel->vibratoDelay = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
|
|
seqChannel->soundScriptIO[i] = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
seqChannel->unused = FALSE;
|
|
|
|
init_note_lists(&seqChannel->notePool);
|
|
|
|
}
|
|
|
|
|
|
|
|
s32 seq_channel_set_layer(struct SequenceChannel *seqChannel, s32 layerIndex) {
|
|
|
|
struct SequenceChannelLayer *layer;
|
|
|
|
|
|
|
|
if (seqChannel->layers[layerIndex] == NULL) {
|
|
|
|
layer = audio_list_pop_back(&gLayerFreeList);
|
|
|
|
seqChannel->layers[layerIndex] = layer;
|
|
|
|
if (layer == NULL) {
|
|
|
|
seqChannel->layers[layerIndex] = NULL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
seq_channel_layer_note_decay(seqChannel->layers[layerIndex]);
|
|
|
|
}
|
|
|
|
|
|
|
|
layer = seqChannel->layers[layerIndex];
|
|
|
|
layer->seqChannel = seqChannel;
|
|
|
|
layer->adsr = seqChannel->adsr;
|
|
|
|
layer->adsr.releaseRate = 0;
|
|
|
|
layer->enabled = TRUE;
|
|
|
|
layer->unk0b20 = FALSE;
|
|
|
|
layer->unk0b10 = FALSE;
|
|
|
|
layer->finished = FALSE;
|
|
|
|
layer->portamento.mode = 0;
|
|
|
|
layer->scriptState.depth = 0;
|
|
|
|
layer->unk1 = 0;
|
|
|
|
layer->noteDuration = 0x80;
|
|
|
|
layer->transposition = 0;
|
|
|
|
layer->delay = 0;
|
|
|
|
layer->duration = 0;
|
|
|
|
layer->delayUnused = 0;
|
|
|
|
layer->note = NULL;
|
|
|
|
layer->instrument = NULL;
|
|
|
|
layer->velocitySquare = 0.0f;
|
|
|
|
layer->pan = 0.5f;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void seq_channel_layer_disable(struct SequenceChannelLayer *layer) {
|
|
|
|
if (layer != NULL) {
|
|
|
|
seq_channel_layer_note_decay(layer);
|
|
|
|
layer->enabled = FALSE;
|
|
|
|
layer->finished = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void seq_channel_layer_free(struct SequenceChannel *seqChannel, s32 layerIndex) {
|
|
|
|
struct SequenceChannelLayer *layer = seqChannel->layers[layerIndex];
|
|
|
|
struct AudioListItem *item;
|
|
|
|
|
|
|
|
if (layer != NULL) {
|
|
|
|
// push to end of list
|
|
|
|
item = &layer->listItem;
|
|
|
|
if (item->prev == NULL) {
|
|
|
|
// TODO: probably a macro?
|
|
|
|
gLayerFreeList.prev->next = item;
|
|
|
|
item->prev = gLayerFreeList.prev;
|
|
|
|
item->next = &gLayerFreeList;
|
|
|
|
gLayerFreeList.prev = item;
|
|
|
|
gLayerFreeList.u.count++;
|
|
|
|
item->pool = gLayerFreeList.pool;
|
|
|
|
}
|
|
|
|
seq_channel_layer_disable(layer);
|
|
|
|
seqChannel->layers[layerIndex] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sequence_channel_disable(struct SequenceChannel *seqChannel) {
|
|
|
|
s32 i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
seq_channel_layer_free(seqChannel, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
note_pool_clear(&seqChannel->notePool);
|
|
|
|
seqChannel->enabled = FALSE;
|
|
|
|
seqChannel->finished = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct SequenceChannel *allocate_sequence_channel(void) {
|
|
|
|
s32 i;
|
|
|
|
for (i = 0; i < ARRAY_COUNT(gSequenceChannels); i++) {
|
|
|
|
if (gSequenceChannels[i].seqPlayer == NULL) {
|
|
|
|
return gSequenceChannels + i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &gSequenceChannelNone;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sequence_player_init_channels(struct SequencePlayer *seqPlayer, u16 channelBits) {
|
|
|
|
struct SequenceChannel *seqChannel;
|
|
|
|
s32 i;
|
|
|
|
|
|
|
|
for (i = 0; i < CHANNELS_MAX; i++) {
|
|
|
|
if (channelBits & 1) {
|
|
|
|
seqChannel = seqPlayer->channels[i];
|
|
|
|
if (IS_SEQUENCE_CHANNEL_VALID(seqChannel) == TRUE && seqChannel->seqPlayer == seqPlayer) {
|
|
|
|
sequence_channel_disable(seqChannel);
|
|
|
|
seqChannel->seqPlayer = NULL;
|
|
|
|
}
|
|
|
|
seqChannel = allocate_sequence_channel();
|
|
|
|
if (IS_SEQUENCE_CHANNEL_VALID(seqChannel) == FALSE) {
|
|
|
|
gAudioErrorFlags = i + 0x10000;
|
|
|
|
seqPlayer->channels[i] = seqChannel;
|
|
|
|
} else {
|
|
|
|
sequence_channel_init(seqChannel);
|
|
|
|
seqPlayer->channels[i] = seqChannel;
|
|
|
|
seqChannel->seqPlayer = seqPlayer;
|
|
|
|
seqChannel->bankId = seqPlayer->anyBank[0];
|
|
|
|
seqChannel->muteBehavior = seqPlayer->muteBehavior;
|
|
|
|
seqChannel->noteAllocPolicy = seqPlayer->noteAllocPolicy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
channelBits >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sequence_player_disable_channels(struct SequencePlayer *seqPlayer, u16 channelBits) {
|
|
|
|
struct SequenceChannel *seqChannel;
|
|
|
|
s32 i;
|
|
|
|
|
|
|
|
for (i = 0; i < CHANNELS_MAX; i++) {
|
|
|
|
if (channelBits & 1) {
|
|
|
|
seqChannel = seqPlayer->channels[i];
|
|
|
|
if (IS_SEQUENCE_CHANNEL_VALID(seqChannel) == TRUE) {
|
|
|
|
if (seqChannel->seqPlayer == seqPlayer) {
|
|
|
|
sequence_channel_disable(seqChannel);
|
|
|
|
seqChannel->seqPlayer = NULL;
|
|
|
|
}
|
|
|
|
seqPlayer->channels[i] = &gSequenceChannelNone;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
channelBits >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sequence_channel_enable(struct SequencePlayer *seqPlayer, u8 channelIndex, void *arg2) {
|
|
|
|
struct SequenceChannel *seqChannel = seqPlayer->channels[channelIndex];
|
|
|
|
s32 i;
|
|
|
|
|
|
|
|
if (IS_SEQUENCE_CHANNEL_VALID(seqChannel) != FALSE) {
|
|
|
|
seqChannel->enabled = TRUE;
|
|
|
|
seqChannel->finished = FALSE;
|
|
|
|
seqChannel->scriptState.depth = 0;
|
|
|
|
seqChannel->scriptState.pc = arg2;
|
|
|
|
seqChannel->delay = 0;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
if (seqChannel->layers[i] != NULL) {
|
|
|
|
seq_channel_layer_free(seqChannel, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sequence_player_disable(struct SequencePlayer *seqPlayer) {
|
|
|
|
sequence_player_disable_channels(seqPlayer, 0xffff);
|
|
|
|
note_pool_clear(&seqPlayer->notePool);
|
|
|
|
seqPlayer->finished = TRUE;
|
|
|
|
seqPlayer->enabled = FALSE;
|
|
|
|
|
|
|
|
if (IS_SEQ_LOAD_COMPLETE(seqPlayer->seqId)) {
|
|
|
|
gSeqLoadStatus[seqPlayer->seqId] = SOUND_LOAD_STATUS_DISCARDABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IS_BANK_LOAD_COMPLETE(seqPlayer->anyBank[0])) {
|
|
|
|
gBankLoadStatus[seqPlayer->anyBank[0]] = SOUND_LOAD_STATUS_DISCARDABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// (Note that if this is called from alloc_bank_or_seq, the side will get swapped
|
|
|
|
// later in that function. Thus, we signal that we want to load into the slot
|
|
|
|
// of the bank that we no longer need.)
|
|
|
|
if (gBankLoadedPool.temporary.entries[0].id == seqPlayer->anyBank[0]) {
|
|
|
|
gBankLoadedPool.temporary.nextSide = 1;
|
|
|
|
} else if (gBankLoadedPool.temporary.entries[1].id == seqPlayer->anyBank[0]) {
|
|
|
|
gBankLoadedPool.temporary.nextSide = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add an item to the end of a list, if it's not already in any list.
|
|
|
|
*/
|
|
|
|
void audio_list_push_back(struct AudioListItem *list, struct AudioListItem *item) {
|
|
|
|
if (item->prev == NULL) {
|
|
|
|
list->prev->next = item;
|
|
|
|
item->prev = list->prev;
|
|
|
|
item->next = list;
|
|
|
|
list->prev = item;
|
|
|
|
list->u.count++;
|
|
|
|
item->pool = list->pool;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the last item from a list, and return it (or NULL if empty).
|
|
|
|
*/
|
|
|
|
void *audio_list_pop_back(struct AudioListItem *list) {
|
|
|
|
struct AudioListItem *item = list->prev;
|
|
|
|
if (item == list) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
item->prev->next = list;
|
|
|
|
list->prev = item->prev;
|
|
|
|
item->prev = NULL;
|
|
|
|
list->u.count--;
|
|
|
|
return item->u.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void func_8031AF74(void) {
|
|
|
|
s32 i;
|
|
|
|
|
|
|
|
gLayerFreeList.prev = &gLayerFreeList;
|
|
|
|
gLayerFreeList.next = &gLayerFreeList;
|
|
|
|
gLayerFreeList.u.count = 0;
|
|
|
|
gLayerFreeList.pool = NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_COUNT(D_802245D8); i++) {
|
|
|
|
D_802245D8[i].listItem.u.value = D_802245D8 + i;
|
|
|
|
D_802245D8[i].listItem.prev = NULL;
|
|
|
|
audio_list_push_back(&gLayerFreeList, &D_802245D8[i].listItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
u8 m64_read_u8(struct M64ScriptState *state) {
|
|
|
|
u8 *midiArg = state->pc++;
|
|
|
|
return *midiArg;
|
|
|
|
}
|
|
|
|
|
|
|
|
s16 m64_read_s16(struct M64ScriptState *state) {
|
|
|
|
s16 ret = *(state->pc++) << 8;
|
|
|
|
ret = *(state->pc++) | ret;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
u16 m64_read_compressed_u16(struct M64ScriptState *state) {
|
|
|
|
u16 ret = *(state->pc++);
|
|
|
|
if (ret & 0x80) {
|
|
|
|
ret = (ret << 8) & 0x7f00;
|
|
|
|
ret = *(state->pc++) | ret;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef NON_MATCHING
|
|
|
|
void seq_channel_layer_process_script(struct SequenceChannelLayer *layer) {
|
|
|
|
struct SequencePlayer *seqPlayer; // sp5C, t4
|
|
|
|
struct SequenceChannel *seqChannel; // sp58, t5
|
|
|
|
struct M64ScriptState *state; // v0
|
|
|
|
struct AdsrSettings *temp_v0_11;
|
|
|
|
struct Portamento *portamento; // v0
|
|
|
|
struct Instrument **instOut; // a1
|
|
|
|
struct Instrument *inst; // a0
|
|
|
|
struct AudioBankSound *phi_v0_5;
|
|
|
|
struct AudioBankSound *phi_v0_6;
|
|
|
|
struct Instrument *phi_v1_2;
|
|
|
|
struct Drum *drum;
|
|
|
|
u8 sameSound; // sp3F
|
|
|
|
u8 cmd; // a0
|
|
|
|
u8 sp3D; // t0
|
|
|
|
u8 loBits;
|
|
|
|
u16 sp3A; // t2, a0, a1
|
|
|
|
s32 sp30; // t3
|
|
|
|
f32 sp28; // f0
|
|
|
|
f32 sp24;
|
|
|
|
u8 temp8;
|
|
|
|
u8 *old;
|
|
|
|
u8 *old2;
|
|
|
|
u8 temp_v0_20;
|
|
|
|
u8 phi_a1_3;
|
|
|
|
f32 temp_f12;
|
|
|
|
f32 temp_f2;
|
|
|
|
s32 temp_a0_5;
|
|
|
|
u8 temp_t0;
|
|
|
|
s32 temp_t1;
|
|
|
|
s32 temp_t1_2;
|
|
|
|
u8 temp_a0_6;
|
|
|
|
u8 temp_t7_4;
|
|
|
|
s32 temp_a3;
|
|
|
|
u8 instIndex; // v0
|
|
|
|
s32 phi_v1;
|
|
|
|
f32 phi_f0;
|
|
|
|
|
|
|
|
sameSound = TRUE;
|
|
|
|
if (layer->enabled == FALSE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (layer->delay > 1) {
|
|
|
|
layer->delay--;
|
|
|
|
if (!layer->unk0b20 && layer->delay <= layer->duration) {
|
|
|
|
seq_channel_layer_note_decay(layer);
|
|
|
|
layer->unk0b20 = TRUE;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!layer->unk0b10) {
|
|
|
|
seq_channel_layer_note_decay(layer);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((layer->portamento.mode & ~0x80) == 1 || (layer->portamento.mode & ~0x80) == 2) {
|
|
|
|
layer->portamento.mode = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
seqChannel = layer->seqChannel;
|
|
|
|
seqPlayer = seqChannel->seqPlayer;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
// (Moving state outside the loop improves initial regalloc, but is wrong)
|
|
|
|
state = &layer->scriptState;
|
|
|
|
old2 = state->pc++;
|
|
|
|
cmd = *old2;
|
|
|
|
if (cmd <= 0xc0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case 0xff: // function return or end of script
|
|
|
|
if (state->depth == 0) {
|
|
|
|
// N.B. this function call is *not* inlined even though it's
|
|
|
|
// within the same file, unlike in the rest of this function.
|
|
|
|
seq_channel_layer_disable(layer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
state->depth--, state->pc = state->stack[state->depth];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xfc: // function call
|
|
|
|
// Something is wrong with the codegen here... It almost looks like
|
|
|
|
// it's inlining m64_read_s16, but it lacks a s16 cast.
|
|
|
|
// Maybe they did macro-based inlining since there are more layers
|
|
|
|
// than channels or sequences, making the code hotter.
|
|
|
|
sp3A = *(state->pc++) << 8;
|
|
|
|
sp3A = *(state->pc++) | sp3A;
|
|
|
|
state->depth++;
|
|
|
|
state->stack[state->depth - 1] = state->pc;
|
|
|
|
state->pc = seqPlayer->seqData + sp3A;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf8: // loop start, N iterations (or 256 if N = 0)
|
|
|
|
old = state->pc++;
|
|
|
|
state->remLoopIters[state->depth] = *old;
|
|
|
|
state->depth++;
|
|
|
|
state->stack[state->depth - 1] = state->pc;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf7: // loop end
|
|
|
|
state->remLoopIters[state->depth - 1]--;
|
|
|
|
if (state->remLoopIters[state->depth - 1] != 0) {
|
|
|
|
state->pc = state->stack[state->depth - 1];
|
|
|
|
} else {
|
|
|
|
state->depth--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xfb: // unconditional jump
|
|
|
|
sp3A = *(state->pc++) << 8;
|
|
|
|
sp3A = *(state->pc++) | sp3A;
|
|
|
|
state->pc = seqPlayer->seqData + sp3A;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc1: // set short note velocity
|
|
|
|
case 0xca: // set pan (0..128)
|
|
|
|
temp_a0_5 = *(state->pc++);
|
|
|
|
if (cmd == 0xc1) {
|
|
|
|
layer->velocitySquare = (f32)(temp_a0_5 * temp_a0_5);
|
|
|
|
} else {
|
|
|
|
layer->pan = (f32) temp_a0_5 / US_FLOAT(128.0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc2: // set transposition in semitones
|
|
|
|
case 0xc9: // set short note duration
|
|
|
|
temp_a0_6 = *(state->pc++);
|
|
|
|
if (cmd == 0xc9) {
|
|
|
|
layer->noteDuration = temp_a0_6;
|
|
|
|
} else {
|
|
|
|
layer->transposition = temp_a0_6;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc4:
|
|
|
|
case 0xc5:
|
|
|
|
if (cmd == 0xc4) {
|
|
|
|
temp8 = TRUE;
|
|
|
|
} else {
|
|
|
|
temp8 = FALSE;
|
|
|
|
}
|
|
|
|
layer->unk0b10 = temp8;
|
|
|
|
seq_channel_layer_note_decay(layer);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc3: // set short note default play percentage
|
|
|
|
// This doesn't match very well... sp3A is definitely set here
|
|
|
|
// (it's falsely preserved until after the loop), but maybe there's
|
|
|
|
// also inlining going on, with sp3A as a temp variable being used
|
|
|
|
// for no good reason? Or it could just be a macro.
|
|
|
|
sp3A = *(state->pc++);
|
|
|
|
if (sp3A & 0x80) {
|
|
|
|
sp3A = (sp3A << 8) & 0x7f00;
|
|
|
|
sp3A = *(state->pc++) | sp3A;
|
|
|
|
}
|
|
|
|
layer->shortNoteDefaultPlayPercentage = sp3A;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc6: // set instrument
|
|
|
|
old = state->pc++;
|
|
|
|
instIndex = *old;
|
|
|
|
// The rest of this case is identical to
|
|
|
|
// if (instIndex < 0x7f) {
|
|
|
|
// get_instrument(seqChannel, instIndex, &layer->instrument, &layer->adsr);
|
|
|
|
// }
|
|
|
|
// except without seqChannelCpy...
|
|
|
|
// interestingly, get_instrument comes just *after* this function,
|
|
|
|
// which I think wouldn't be the case with __inline (maybe if they
|
|
|
|
// both inline a common helper?)
|
|
|
|
if (instIndex >= 0x7f) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
temp_a3 = seqChannel->bankId;
|
|
|
|
if (instIndex >= gCtlEntries[temp_a3].numInstruments) {
|
|
|
|
instIndex = gCtlEntries[temp_a3].numInstruments;
|
|
|
|
if (instIndex == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
instIndex--;
|
|
|
|
}
|
|
|
|
|
|
|
|
instOut = &layer->instrument;
|
|
|
|
inst = gCtlEntries[temp_a3].instruments[instIndex];
|
|
|
|
if (inst == NULL) {
|
|
|
|
while (instIndex != 0xff) {
|
|
|
|
inst = gCtlEntries[temp_a3].instruments[instIndex];
|
|
|
|
if (inst != NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
instIndex--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
temp_v0_11 = &layer->adsr;
|
2019-10-05 15:08:05 -04:00
|
|
|
if (((uintptr_t) gBankLoadedPool.persistent.pool.start <= (uintptr_t) inst
|
|
|
|
&& (uintptr_t) inst <= (uintptr_t)(gBankLoadedPool.persistent.pool.start
|
2019-08-25 00:46:40 -04:00
|
|
|
+ gBankLoadedPool.persistent.pool.size))
|
2019-10-05 15:08:05 -04:00
|
|
|
|| ((uintptr_t) gBankLoadedPool.temporary.pool.start <= (uintptr_t) inst
|
|
|
|
&& (uintptr_t) inst <= (uintptr_t)(gBankLoadedPool.temporary.pool.start
|
2019-08-25 00:46:40 -04:00
|
|
|
+ gBankLoadedPool.temporary.pool.size))) {
|
|
|
|
temp_v0_11->envelope = inst->envelope;
|
|
|
|
temp_v0_11->releaseRate = inst->releaseRate;
|
|
|
|
*instOut = inst;
|
|
|
|
} else {
|
|
|
|
gAudioErrorFlags = instIndex + 0x20000;
|
|
|
|
*instOut = NULL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc7: // enable portamento
|
|
|
|
old = state->pc++;
|
|
|
|
layer->portamento.mode = *old;
|
|
|
|
old = state->pc++;
|
|
|
|
temp_t7_4 =
|
|
|
|
seqChannel->transposition + *old + layer->transposition + seqPlayer->transposition;
|
|
|
|
if (temp_t7_4 >= 0x80) {
|
|
|
|
temp_t7_4 = 0;
|
|
|
|
}
|
|
|
|
layer->portamentoTargetNote = temp_t7_4;
|
|
|
|
|
|
|
|
if (layer->portamento.mode & 0x80) {
|
|
|
|
layer->portamentoTime = *(state->pc++);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
sp3A = *(state->pc++);
|
|
|
|
if (sp3A & 0x80) {
|
|
|
|
sp3A = (sp3A << 8) & 0x7f00;
|
|
|
|
sp3A = *(state->pc++) | sp3A;
|
|
|
|
}
|
|
|
|
layer->portamentoTime = sp3A;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc8: // disable portamento
|
|
|
|
layer->portamento.mode = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
loBits = cmd & 0xf;
|
|
|
|
switch (cmd & 0xf0) {
|
|
|
|
case 0xd0: // set short note velocity via lookup table
|
|
|
|
sp3A = seqPlayer->shortNoteVelocityTable[loBits];
|
|
|
|
layer->velocitySquare = (f32)(sp3A * sp3A);
|
|
|
|
break;
|
|
|
|
case 0xe0: // set short note duration via lookup table
|
|
|
|
layer->noteDuration = seqPlayer->shortNoteDurationTable[loBits];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state = &layer->scriptState;
|
|
|
|
if (cmd == 0xc0) // delay for N frames
|
|
|
|
{
|
|
|
|
sp3A = *(state->pc++);
|
|
|
|
if (sp3A & 0x80) {
|
|
|
|
sp3A = (sp3A << 8) & 0x7f00;
|
|
|
|
sp3A = *(state->pc++) | sp3A;
|
|
|
|
}
|
|
|
|
layer->delay = sp3A;
|
|
|
|
layer->unk0b20 = TRUE;
|
|
|
|
} else {
|
|
|
|
layer->unk0b20 = FALSE;
|
|
|
|
|
|
|
|
if (seqChannel->largeNotes == TRUE) {
|
|
|
|
temp_t1_2 = cmd & 0xc0;
|
|
|
|
|
|
|
|
// phi_a0_3 = sp3A; // real assignment, or same vars?
|
|
|
|
state = &layer->scriptState;
|
|
|
|
switch (temp_t1_2) {
|
|
|
|
case 0x00: // play note, type 0 (play percentage, velocity, duration)
|
|
|
|
sp3A = *(state->pc++);
|
|
|
|
if (sp3A & 0x80) {
|
|
|
|
sp3A = (sp3A << 8) & 0x7f00;
|
|
|
|
sp3A = *(state->pc++) | sp3A;
|
|
|
|
}
|
|
|
|
sp30 = *(state->pc++);
|
|
|
|
layer->noteDuration = *(state->pc++);
|
|
|
|
layer->playPercentage = sp3A;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x40: // play note, type 1 (play percentage, velocity)
|
|
|
|
sp3A = *(state->pc++);
|
|
|
|
if (sp3A & 0x80) {
|
|
|
|
sp3A = (sp3A << 8) & 0x7f00;
|
|
|
|
sp3A = *(state->pc++) | sp3A;
|
|
|
|
}
|
|
|
|
sp30 = *(state->pc++);
|
|
|
|
layer->noteDuration = 0;
|
|
|
|
layer->playPercentage = sp3A;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x80: // play note, type 2 (velocity, duration; uses last play percentage)
|
|
|
|
sp30 = *(state->pc++);
|
|
|
|
layer->noteDuration = *(state->pc++);
|
|
|
|
sp3A = layer->playPercentage;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
layer->velocitySquare = sp30 * sp30;
|
|
|
|
phi_v1 = cmd - temp_t1_2;
|
|
|
|
} else {
|
|
|
|
temp_t1 = cmd & 0xc0;
|
|
|
|
|
|
|
|
state = &layer->scriptState;
|
|
|
|
|
|
|
|
// phi_a0_3 = sp3A;
|
|
|
|
switch (temp_t1) {
|
|
|
|
case 0x00: // play note, type 0 (play percentage)
|
|
|
|
sp3A = *(state->pc++);
|
|
|
|
if (sp3A & 0x80) {
|
|
|
|
sp3A = (sp3A << 8) & 0x7f00;
|
|
|
|
sp3A = *(state->pc++) | sp3A;
|
|
|
|
}
|
|
|
|
layer->playPercentage = sp3A;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x40: // play note, type 1 (uses default play percentage)
|
|
|
|
sp3A = layer->shortNoteDefaultPlayPercentage;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x80: // play note, type 2 (uses last play percentage)
|
|
|
|
sp3A = layer->playPercentage;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
phi_v1 = cmd - temp_t1;
|
|
|
|
}
|
|
|
|
|
|
|
|
layer->delay = sp3A;
|
|
|
|
layer->duration = layer->noteDuration * sp3A / 256;
|
|
|
|
if ((seqPlayer->muted && (seqChannel->muteBehavior & MUTE_BEHAVIOR_40) != 0)
|
|
|
|
|| seqChannel->unk0b10 || !seqChannel->hasInstrument) {
|
|
|
|
layer->unk0b20 = TRUE;
|
|
|
|
} else {
|
|
|
|
if (seqChannel->instOrWave == 0) {
|
|
|
|
temp_t0 = phi_v1 + seqChannel->transposition + layer->transposition;
|
|
|
|
if (temp_t0 >= gCtlEntries[seqChannel->bankId].numDrums) {
|
|
|
|
temp_t0 = gCtlEntries[seqChannel->bankId].numDrums;
|
|
|
|
if (temp_t0 == 0) {
|
|
|
|
// this goto look a bit like a function return...
|
|
|
|
layer->unk0b20 = TRUE;
|
|
|
|
goto skip;
|
|
|
|
}
|
|
|
|
temp_t0--;
|
|
|
|
}
|
|
|
|
|
|
|
|
drum = gCtlEntries[seqChannel->bankId].drums[temp_t0];
|
|
|
|
if (drum == NULL) {
|
|
|
|
layer->unk0b20 = TRUE;
|
|
|
|
} else {
|
|
|
|
layer->adsr.envelope = drum->envelope;
|
|
|
|
layer->adsr.releaseRate = drum->releaseRate;
|
|
|
|
|
|
|
|
layer->pan = FLOAT_CAST(drum->unk1) / US_FLOAT(128.0);
|
|
|
|
layer->sound = &drum->sound;
|
|
|
|
layer->freqScale = layer->sound->tuning;
|
|
|
|
}
|
|
|
|
skip:;
|
|
|
|
} else {
|
|
|
|
temp_v0_20 = phi_v1 + seqPlayer->transposition + seqChannel->transposition
|
|
|
|
+ layer->transposition;
|
|
|
|
if (temp_v0_20 >= 0x80) {
|
|
|
|
layer->unk0b20 = TRUE;
|
|
|
|
} else {
|
|
|
|
phi_v1_2 = layer->instrument;
|
|
|
|
if (layer->instrument == NULL) {
|
|
|
|
phi_v1_2 = seqChannel->instrument;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (layer->portamento.mode != 0) {
|
|
|
|
phi_a1_3 = layer->portamentoTargetNote;
|
|
|
|
if (phi_a1_3 < temp_v0_20) {
|
|
|
|
phi_a1_3 = temp_v0_20;
|
|
|
|
}
|
|
|
|
if (phi_v1_2 != NULL) {
|
|
|
|
if (phi_a1_3 < phi_v1_2->normalRangeLo) {
|
|
|
|
phi_v0_5 = &phi_v1_2->lowNotesSound;
|
|
|
|
} else if (phi_a1_3 <= phi_v1_2->normalRangeHi) {
|
|
|
|
phi_v0_5 = &phi_v1_2->normalNotesSound;
|
|
|
|
} else {
|
|
|
|
phi_v0_5 = &phi_v1_2->highNotesSound;
|
|
|
|
}
|
|
|
|
sameSound = (phi_v0_5 == layer->sound);
|
|
|
|
layer->sound = phi_v0_5;
|
|
|
|
phi_f0 = phi_v0_5->tuning;
|
|
|
|
} else {
|
|
|
|
layer->sound = NULL;
|
|
|
|
phi_f0 = 1.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
temp_f2 = gNoteFrequencies[temp_v0_20] * phi_f0;
|
|
|
|
temp_f12 = gNoteFrequencies[layer->portamentoTargetNote] * phi_f0;
|
|
|
|
|
|
|
|
portamento = &layer->portamento;
|
|
|
|
switch (layer->portamento.mode & ~0x80) {
|
|
|
|
case 1:
|
|
|
|
case 3:
|
|
|
|
case 5:
|
|
|
|
sp24 = temp_f2;
|
|
|
|
sp28 = temp_f12;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
case 4:
|
|
|
|
sp24 = temp_f12;
|
|
|
|
sp28 = temp_f2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
portamento->extent = sp24 / sp28 - US_FLOAT(1.0);
|
|
|
|
if (layer->portamento.mode & 0x80) {
|
|
|
|
portamento->speed = US_FLOAT(32512.0) * FLOAT_CAST(seqPlayer->tempo)
|
|
|
|
/ ((f32) layer->delay * (f32) gTempoInternalToExternal
|
|
|
|
* FLOAT_CAST(layer->portamentoTime));
|
|
|
|
} else {
|
|
|
|
portamento->speed = US_FLOAT(127.0) / FLOAT_CAST(layer->portamentoTime);
|
|
|
|
}
|
|
|
|
portamento->cur = 0.0f;
|
|
|
|
layer->freqScale = sp28;
|
|
|
|
if ((layer->portamento.mode & ~0x80) == 5) {
|
|
|
|
layer->portamentoTargetNote = temp_v0_20;
|
|
|
|
}
|
|
|
|
} else if (phi_v1_2 != NULL) {
|
|
|
|
if (temp_v0_20 < phi_v1_2->normalRangeLo) {
|
|
|
|
phi_v0_6 = &phi_v1_2->lowNotesSound;
|
|
|
|
} else if (temp_v0_20 <= phi_v1_2->normalRangeHi) {
|
|
|
|
phi_v0_6 = &phi_v1_2->normalNotesSound;
|
|
|
|
} else {
|
|
|
|
phi_v0_6 = &phi_v1_2->highNotesSound;
|
|
|
|
}
|
|
|
|
sameSound = (phi_v0_6 == layer->sound);
|
|
|
|
layer->sound = phi_v0_6;
|
|
|
|
layer->freqScale = phi_v0_6->tuning * gNoteFrequencies[temp_v0_20];
|
|
|
|
} else {
|
|
|
|
layer->sound = NULL;
|
|
|
|
layer->freqScale = gNoteFrequencies[temp_v0_20];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
layer->delayUnused = layer->delay;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (layer->unk0b20 == TRUE) {
|
|
|
|
if (layer->note != NULL || layer->unk0b10) {
|
|
|
|
seq_channel_layer_note_decay(layer);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!layer->unk0b10) {
|
|
|
|
sp3D = TRUE;
|
|
|
|
} else if (layer->note == NULL || layer->unk1 == 0) {
|
|
|
|
sp3D = TRUE;
|
|
|
|
} else if (sameSound == FALSE) {
|
|
|
|
seq_channel_layer_note_decay(layer);
|
|
|
|
sp3D = TRUE;
|
|
|
|
} else {
|
|
|
|
sp3D = FALSE;
|
|
|
|
if (layer->sound == NULL) {
|
|
|
|
func_80319164(layer->note, layer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sp3D != FALSE) {
|
|
|
|
layer->note = alloc_note(layer);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (layer->note != NULL && layer->note->parentLayer == layer) {
|
|
|
|
note_vibrato_init(layer->note);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#elif defined(VERSION_JP)
|
|
|
|
GLOBAL_ASM("asm/non_matchings/seq_channel_layer_process_script_jp.s")
|
|
|
|
#else
|
|
|
|
GLOBAL_ASM("asm/non_matchings/seq_channel_layer_process_script_us.s")
|
|
|
|
#endif
|
|
|
|
|
|
|
|
u8 get_instrument(struct SequenceChannel *seqChannel, u8 instId, struct Instrument **instOut,
|
|
|
|
struct AdsrSettings *adsr) {
|
|
|
|
struct Instrument *inst;
|
|
|
|
UNUSED u32 pad;
|
|
|
|
|
|
|
|
if (instId >= gCtlEntries[seqChannel->bankId].numInstruments) {
|
|
|
|
instId = gCtlEntries[seqChannel->bankId].numInstruments;
|
|
|
|
if (instId == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
instId--;
|
|
|
|
}
|
|
|
|
|
|
|
|
inst = gCtlEntries[seqChannel->bankId].instruments[instId];
|
|
|
|
if (inst == NULL) {
|
|
|
|
struct SequenceChannel seqChannelCpy = *seqChannel;
|
|
|
|
|
|
|
|
while (instId != 0xff) {
|
|
|
|
inst = gCtlEntries[seqChannelCpy.bankId].instruments[instId];
|
2019-09-01 15:50:50 -04:00
|
|
|
if (inst != NULL) {
|
2019-08-25 00:46:40 -04:00
|
|
|
break;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
instId--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-05 15:08:05 -04:00
|
|
|
if (((uintptr_t) gBankLoadedPool.persistent.pool.start <= (uintptr_t) inst
|
|
|
|
&& (uintptr_t) inst <= (uintptr_t)(gBankLoadedPool.persistent.pool.start
|
|
|
|
+ gBankLoadedPool.persistent.pool.size))
|
|
|
|
|| ((uintptr_t) gBankLoadedPool.temporary.pool.start <= (uintptr_t) inst
|
|
|
|
&& (uintptr_t) inst <= (uintptr_t)(gBankLoadedPool.temporary.pool.start
|
2019-08-25 00:46:40 -04:00
|
|
|
+ gBankLoadedPool.temporary.pool.size))) {
|
|
|
|
adsr->envelope = inst->envelope;
|
|
|
|
adsr->releaseRate = inst->releaseRate;
|
|
|
|
*instOut = inst;
|
|
|
|
instId++;
|
|
|
|
return instId;
|
|
|
|
}
|
|
|
|
|
|
|
|
gAudioErrorFlags = instId + 0x20000;
|
|
|
|
*instOut = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_instrument(struct SequenceChannel *seqChannel, u8 instId) {
|
|
|
|
if (instId >= 0x80) {
|
|
|
|
seqChannel->instOrWave = instId;
|
|
|
|
seqChannel->instrument = NULL;
|
|
|
|
} else if (instId == 0x7f) {
|
|
|
|
seqChannel->instOrWave = 0;
|
|
|
|
seqChannel->instrument = (struct Instrument *) 1;
|
|
|
|
} else {
|
|
|
|
seqChannel->instOrWave =
|
|
|
|
get_instrument(seqChannel, instId, &seqChannel->instrument, &seqChannel->adsr);
|
|
|
|
if (seqChannel->instOrWave == 0) {
|
|
|
|
seqChannel->hasInstrument = FALSE;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
seqChannel->hasInstrument = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sequence_channel_set_volume(struct SequenceChannel *seqChannel, u8 volume) {
|
|
|
|
seqChannel->volume = FLOAT_CAST(volume) / US_FLOAT(127.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef NON_MATCHING
|
|
|
|
void sequence_channel_process_script(struct SequenceChannel *seqChannel) {
|
|
|
|
u16 sp5A;
|
|
|
|
s8 value; // sp53
|
|
|
|
u8 sp38;
|
|
|
|
u8 cmd; // v1, s1
|
|
|
|
u8 loBits; // t0, a0
|
|
|
|
struct M64ScriptState *state;
|
|
|
|
struct SequencePlayer *seqPlayer;
|
|
|
|
u8 temp;
|
|
|
|
s8 tempSigned;
|
|
|
|
s32 offset;
|
|
|
|
s32 i;
|
|
|
|
u8 temp2;
|
|
|
|
|
|
|
|
if (!seqChannel->enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (seqChannel->stopScript) {
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
if (seqChannel->layers[i] != NULL) {
|
|
|
|
seq_channel_layer_process_script(seqChannel->layers[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
seqPlayer = seqChannel->seqPlayer;
|
|
|
|
if (seqPlayer->muted && (seqChannel->muteBehavior & MUTE_BEHAVIOR_80) != 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (seqChannel->delay != 0) {
|
|
|
|
seqChannel->delay--;
|
|
|
|
}
|
|
|
|
|
|
|
|
state = &seqChannel->scriptState;
|
|
|
|
if (seqChannel->delay == 0) {
|
|
|
|
for (;;) {
|
|
|
|
cmd = m64_read_u8(state);
|
|
|
|
if (cmd == 0xff) // function return or end of script
|
|
|
|
{
|
|
|
|
// This fixes a reordering in 'case 0x90', somehow
|
|
|
|
sp5A = state->depth;
|
|
|
|
if (sp5A == 0) {
|
|
|
|
sequence_channel_disable(seqChannel);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
state->depth--, state->pc = state->stack[state->depth];
|
|
|
|
}
|
|
|
|
if (cmd == 0xfe) // delay for 1 frame
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (cmd == 0xfd) // delay for N frames
|
|
|
|
{
|
|
|
|
seqChannel->delay = m64_read_compressed_u16(state);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (cmd == 0xf3) // delay forever
|
|
|
|
{
|
|
|
|
seqChannel->stopScript = TRUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// (new_var = cmd fixes order of s1/s2, but causes a reordering
|
|
|
|
// towards the bottom of the function)
|
|
|
|
if (cmd > 0xc0) {
|
|
|
|
switch (cmd) {
|
|
|
|
case 0xfc: // function call
|
|
|
|
sp5A = m64_read_s16(state);
|
|
|
|
state->depth++, state->stack[state->depth - 1] = state->pc;
|
|
|
|
state->pc = seqPlayer->seqData + sp5A;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf8: // loop start, N iterations (or 256 if N = 0)
|
|
|
|
state->remLoopIters[state->depth] = m64_read_u8(state);
|
|
|
|
state->depth++, state->stack[state->depth - 1] = state->pc;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf7: // loop end
|
|
|
|
state->remLoopIters[state->depth - 1]--;
|
|
|
|
if (state->remLoopIters[state->depth - 1] != 0) {
|
|
|
|
state->pc = state->stack[state->depth - 1];
|
|
|
|
} else {
|
|
|
|
state->depth--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf6: // break loop, if combined with jump
|
|
|
|
state->depth--;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xfb: // unconditional jump
|
|
|
|
case 0xfa: // jump if == 0
|
|
|
|
case 0xf9: // jump if < 0
|
|
|
|
case 0xf5: // jump if >= 0
|
|
|
|
sp5A = m64_read_s16(state);
|
|
|
|
if (cmd == 0xfa && value != 0)
|
|
|
|
break;
|
|
|
|
if (cmd == 0xf9 && value >= 0)
|
|
|
|
break;
|
|
|
|
if (cmd == 0xf5 && value < 0)
|
|
|
|
break;
|
|
|
|
state->pc = seqPlayer->seqData + sp5A;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf2: // reserve N notes for channel
|
|
|
|
// seqChannel->notePool should live in a saved register
|
|
|
|
note_pool_clear(&seqChannel->notePool);
|
|
|
|
temp = m64_read_u8(state);
|
|
|
|
note_pool_fill(&seqChannel->notePool, temp);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf1: // reserve 0 notes for channel
|
|
|
|
note_pool_clear(&seqChannel->notePool);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc2: // set up dynamic table
|
|
|
|
sp5A = m64_read_s16(state);
|
|
|
|
seqChannel->dynTable = (void *) (seqPlayer->seqData + sp5A);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc5: // set up dynamic table, dynamically
|
|
|
|
if (value != -1) {
|
|
|
|
sp5A = (*seqChannel->dynTable)[value][1]
|
|
|
|
+ ((*seqChannel->dynTable)[value][0] << 8);
|
|
|
|
seqChannel->dynTable = (void *) (seqPlayer->seqData + sp5A);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc1: // set instrument (or "set program")
|
|
|
|
set_instrument(seqChannel, m64_read_u8(state));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc3:
|
|
|
|
seqChannel->largeNotes = FALSE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc4:
|
|
|
|
seqChannel->largeNotes = TRUE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xdf: // set volume
|
|
|
|
sequence_channel_set_volume(seqChannel, m64_read_u8(state));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xe0: // set volume scale
|
|
|
|
seqChannel->volumeScale = FLOAT_CAST(m64_read_u8(state)) / US_FLOAT(128.0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xde: // pitch bend using raw frequency multiplier N/2^15 (N is u16)
|
|
|
|
sp5A = m64_read_s16(state);
|
|
|
|
seqChannel->freqScale = FLOAT_CAST(sp5A) / US_FLOAT(32768.0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd3: // pitch bend by <= 1 octave in either direction (-127..127)
|
|
|
|
// (m64_read_u8(state) is really s8 here)
|
|
|
|
temp = m64_read_u8(state) + 127;
|
|
|
|
seqChannel->freqScale = gPitchBendFrequencyScale[temp];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xdd: // set pan
|
|
|
|
seqChannel->pan = FLOAT_CAST(m64_read_u8(state)) / US_FLOAT(128.0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xdc: // set proportion of pan to come from channel (0..128)
|
|
|
|
seqChannel->panChannelWeight = FLOAT_CAST(m64_read_u8(state)) / US_FLOAT(128.0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xdb: // set transposition in semitones
|
|
|
|
tempSigned = *state->pc;
|
|
|
|
state->pc++;
|
|
|
|
seqChannel->transposition = tempSigned;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xda: // set adsr envelope
|
|
|
|
sp5A = m64_read_s16(state);
|
|
|
|
seqChannel->adsr.envelope = (struct AdsrEnvelope *) (seqPlayer->seqData + sp5A);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd9: // set adsr decay/release
|
|
|
|
seqChannel->adsr.releaseRate = m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd8: // set vibrato extent
|
|
|
|
seqChannel->vibratoExtentTarget = m64_read_u8(state) * 8;
|
|
|
|
seqChannel->vibratoExtentStart = 0;
|
|
|
|
seqChannel->vibratoExtentChangeDelay = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd7: // set vibrato rate
|
|
|
|
seqChannel->vibratoRateStart = seqChannel->vibratoRateTarget =
|
|
|
|
m64_read_u8(state) * 32;
|
|
|
|
seqChannel->vibratoRateChangeDelay = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xe2: // set vibrato extent to change linearly per note
|
|
|
|
seqChannel->vibratoExtentStart = m64_read_u8(state) * 8;
|
|
|
|
seqChannel->vibratoExtentTarget = m64_read_u8(state) * 8;
|
|
|
|
seqChannel->vibratoExtentChangeDelay = m64_read_u8(state) * 16;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xe1: // set vibrato rate to change linearly per note
|
|
|
|
seqChannel->vibratoRateStart = m64_read_u8(state) * 32;
|
|
|
|
seqChannel->vibratoRateTarget = m64_read_u8(state) * 32;
|
|
|
|
seqChannel->vibratoRateChangeDelay = m64_read_u8(state) * 16;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xe3: // set delay until vibrato start for each note
|
|
|
|
seqChannel->vibratoDelay = m64_read_u8(state) * 16;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd6: // noop, intended to set updates per frame
|
|
|
|
temp = m64_read_u8(state);
|
|
|
|
if (temp == 0) {
|
|
|
|
temp = gAudioUpdatesPerFrame;
|
|
|
|
}
|
|
|
|
seqChannel->updatesPerFrameUnused = temp;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd4: // "dry/wet mix of the effects loop", in practice reverb
|
|
|
|
seqChannel->reverb = m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc6: // switch bank within set
|
|
|
|
temp = m64_read_u8(state);
|
2019-09-01 15:50:50 -04:00
|
|
|
// Switch to the temp's (0-indexed) bank in this sequence's
|
|
|
|
// bank set. Note that in the binary format (not in the JSON!)
|
|
|
|
// the banks are listed backwards, so we counts from the back.
|
2019-08-25 00:46:40 -04:00
|
|
|
// (gAlBankSets[offset] is number of banks)
|
|
|
|
offset = ((u16 *) gAlBankSets)[seqPlayer->seqId];
|
|
|
|
temp = gAlBankSets[offset + gAlBankSets[offset] - temp];
|
|
|
|
// temp should be in a saved register across this call
|
|
|
|
if (get_bank_or_seq(&gBankLoadedPool, 2, temp) != NULL) {
|
|
|
|
seqChannel->bankId = temp;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc7: // write to sequence data (!)
|
|
|
|
// sp38 doesn't go on the stack
|
|
|
|
sp38 = value;
|
|
|
|
temp2 = m64_read_u8(state);
|
|
|
|
sp5A = m64_read_s16(state);
|
|
|
|
seqPlayer->seqData[sp5A] = sp38 + temp2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc8: // subtraction
|
|
|
|
case 0xc9: // bitand
|
|
|
|
case 0xcc: // set temp
|
|
|
|
temp = m64_read_u8(state);
|
|
|
|
if (cmd == 0xc8) {
|
|
|
|
value -= temp;
|
|
|
|
} else if (cmd == 0xcc) {
|
|
|
|
value = temp;
|
|
|
|
} else {
|
|
|
|
value &= temp;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xca: // set mute behavior
|
|
|
|
seqChannel->muteBehavior = m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xcb: // read from array in sequence data
|
|
|
|
sp5A = m64_read_s16(state);
|
|
|
|
value = seqPlayer->seqData[sp5A + value];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd0: // set whether stereo/headset audio plays differently
|
|
|
|
seqChannel->stereoHeadsetEffects = m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd1: // set note allocation policy (see playback.h)
|
|
|
|
seqChannel->noteAllocPolicy = m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd2: // set adsr sustain level (0..2^16)
|
|
|
|
seqChannel->adsr.sustain = m64_read_u8(state) << 8;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xe4: // dynamic function pointer call
|
|
|
|
if (value != -1) {
|
|
|
|
u8(*thingy)[2] = *seqChannel->dynTable;
|
|
|
|
state->depth++, state->stack[state->depth - 1] = state->pc;
|
|
|
|
sp5A = thingy[value][1] + (thingy[value][0] << 8);
|
|
|
|
state->pc = seqPlayer->seqData + sp5A;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// loBits is recomputed a lot
|
|
|
|
loBits = cmd & 0xf;
|
|
|
|
// #define loBits (cmd & 0xf)
|
|
|
|
switch (cmd & 0xf0) {
|
|
|
|
case 0x00: // test finished
|
|
|
|
if (seqChannel->layers[loBits] != NULL) {
|
|
|
|
value = seqChannel->layers[loBits]->finished;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x70: // write data back to audio lib
|
|
|
|
seqChannel->soundScriptIO[loBits] = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x80: // read data from audio lib
|
|
|
|
value = seqChannel->soundScriptIO[loBits];
|
|
|
|
if (loBits < 4) {
|
|
|
|
seqChannel->soundScriptIO[loBits] = -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x50: // subtract with read data from audio lib
|
|
|
|
value -= seqChannel->soundScriptIO[loBits];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x90: // set layer
|
|
|
|
sp5A = m64_read_s16(state);
|
|
|
|
if (seq_channel_set_layer(seqChannel, loBits) == 0) {
|
|
|
|
seqChannel->layers[loBits]->scriptState.pc = seqPlayer->seqData + sp5A;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xa0: // free layer
|
|
|
|
seq_channel_layer_free(seqChannel, loBits);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xb0: // set dynamic layer
|
|
|
|
if (value != -1 && seq_channel_set_layer(seqChannel, loBits) != -1) {
|
|
|
|
temp = (*seqChannel->dynTable)[value][0]
|
|
|
|
+ ((*seqChannel->dynTable)[value][1] << 8);
|
|
|
|
seqChannel->layers[loBits]->scriptState.pc = seqPlayer->seqData + temp;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x60: // set note priority (>= 2)
|
|
|
|
seqChannel->notePriority = loBits;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x10: // start a channel with a given sound script
|
|
|
|
sp5A = m64_read_s16(state);
|
|
|
|
sequence_channel_enable(seqPlayer, loBits, seqPlayer->seqData + sp5A);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x20: // disable a channel
|
|
|
|
sequence_channel_disable(seqPlayer->channels[loBits]);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x30: // write data back to audio lib for another channel
|
|
|
|
seqPlayer->channels[loBits]->soundScriptIO[m64_read_u8(state)] = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x40: // read data from audio lib from another channel
|
|
|
|
value = seqPlayer->channels[loBits]->soundScriptIO[m64_read_u8(state)];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
if (seqChannel->layers[i] != 0) {
|
|
|
|
seq_channel_layer_process_script(seqChannel->layers[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#elif defined(VERSION_JP)
|
|
|
|
GLOBAL_ASM("asm/non_matchings/sequence_channel_process_script_jp.s")
|
|
|
|
#else
|
|
|
|
GLOBAL_ASM("asm/non_matchings/sequence_channel_process_script_us.s")
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void sequence_player_process_sequence(struct SequencePlayer *seqPlayer) {
|
|
|
|
u8 cmd;
|
|
|
|
u8 loBits;
|
|
|
|
u8 temp;
|
|
|
|
s8 tempSigned;
|
|
|
|
s32 value;
|
|
|
|
s32 i;
|
|
|
|
u16 u16v;
|
|
|
|
u8 *tempPtr;
|
|
|
|
struct M64ScriptState *state;
|
|
|
|
|
|
|
|
if (seqPlayer->enabled == FALSE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (seqPlayer->bankDmaInProgress == TRUE) {
|
|
|
|
if (seqPlayer->bankDmaMesg == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (seqPlayer->bankDmaRemaining == 0) {
|
|
|
|
seqPlayer->bankDmaInProgress = FALSE;
|
|
|
|
func_8031784C(seqPlayer->loadingBank, gAlTbl->seqArray[seqPlayer->loadingBankId].offset,
|
|
|
|
seqPlayer->loadingBankNumInstruments, seqPlayer->loadingBankNumDrums);
|
|
|
|
gCtlEntries[seqPlayer->loadingBankId].numInstruments = seqPlayer->loadingBankNumInstruments;
|
|
|
|
gCtlEntries[seqPlayer->loadingBankId].numDrums = seqPlayer->loadingBankNumDrums;
|
|
|
|
gCtlEntries[seqPlayer->loadingBankId].instruments = seqPlayer->loadingBank->instruments;
|
|
|
|
gCtlEntries[seqPlayer->loadingBankId].drums = seqPlayer->loadingBank->drums;
|
|
|
|
gBankLoadStatus[seqPlayer->loadingBankId] = SOUND_LOAD_STATUS_COMPLETE;
|
|
|
|
} else {
|
|
|
|
osCreateMesgQueue(&seqPlayer->bankDmaMesgQueue, &seqPlayer->bankDmaMesg, 1);
|
|
|
|
seqPlayer->bankDmaMesg = NULL;
|
|
|
|
audio_dma_partial_copy_async(&seqPlayer->bankDmaCurrDevAddr, &seqPlayer->bankDmaCurrMemAddr,
|
|
|
|
&seqPlayer->bankDmaRemaining, &seqPlayer->bankDmaMesgQueue,
|
|
|
|
&seqPlayer->bankDmaIoMesg);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (seqPlayer->seqDmaInProgress == TRUE) {
|
|
|
|
if (seqPlayer->seqDmaMesg == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
seqPlayer->seqDmaInProgress = FALSE;
|
|
|
|
gSeqLoadStatus[seqPlayer->seqId] = SOUND_LOAD_STATUS_COMPLETE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If discarded, bail out.
|
|
|
|
if (IS_SEQ_LOAD_COMPLETE(seqPlayer->seqId) == FALSE
|
|
|
|
|| IS_BANK_LOAD_COMPLETE(seqPlayer->anyBank[0]) == FALSE) {
|
|
|
|
sequence_player_disable(seqPlayer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove possible SOUND_LOAD_STATUS_DISCARDABLE marks.
|
|
|
|
gSeqLoadStatus[seqPlayer->seqId] = SOUND_LOAD_STATUS_COMPLETE;
|
|
|
|
gBankLoadStatus[seqPlayer->anyBank[0]] = SOUND_LOAD_STATUS_COMPLETE;
|
|
|
|
|
|
|
|
if (seqPlayer->muted && (seqPlayer->muteBehavior & MUTE_BEHAVIOR_80) != 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we surpass the number of ticks needed for a tatum, else stop.
|
|
|
|
seqPlayer->tempoAcc += seqPlayer->tempo;
|
|
|
|
if (seqPlayer->tempoAcc < gTempoInternalToExternal) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
seqPlayer->tempoAcc -= (u16) gTempoInternalToExternal;
|
|
|
|
|
|
|
|
state = &seqPlayer->scriptState;
|
|
|
|
if (seqPlayer->delay > 1) {
|
|
|
|
seqPlayer->delay--;
|
|
|
|
} else {
|
|
|
|
for (;;) {
|
|
|
|
cmd = m64_read_u8(state);
|
|
|
|
if (cmd == 0xff) // function return or end of script
|
|
|
|
{
|
|
|
|
if (state->depth == 0) {
|
|
|
|
sequence_player_disable(seqPlayer);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
state->depth--, state->pc = state->stack[state->depth];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd == 0xfd) // delay for N frames
|
|
|
|
{
|
|
|
|
seqPlayer->delay = m64_read_compressed_u16(state);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd == 0xfe) // delay for 1 frame
|
|
|
|
{
|
|
|
|
seqPlayer->delay = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd >= 0xc0) {
|
|
|
|
switch (cmd) {
|
|
|
|
case 0xff:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xfc: // function call
|
|
|
|
u16v = m64_read_s16(state);
|
|
|
|
state->depth++, state->stack[state->depth - 1] = state->pc;
|
|
|
|
state->pc = seqPlayer->seqData + u16v;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf8: // loop start, N iterations (or 256 if N = 0)
|
|
|
|
state->remLoopIters[state->depth] = m64_read_u8(state);
|
|
|
|
state->depth++, state->stack[state->depth - 1] = state->pc;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf7: // loop end
|
|
|
|
state->remLoopIters[state->depth - 1]--;
|
|
|
|
if (state->remLoopIters[state->depth - 1] != 0) {
|
|
|
|
state->pc = state->stack[state->depth - 1];
|
|
|
|
} else {
|
|
|
|
state->depth--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xfb: // unconditional jump
|
|
|
|
case 0xfa: // jump if == 0
|
|
|
|
case 0xf9: // jump if < 0
|
|
|
|
case 0xf5: // jump if >= 0
|
|
|
|
u16v = m64_read_s16(state);
|
2019-09-01 15:50:50 -04:00
|
|
|
if (cmd == 0xfa && value != 0) {
|
2019-08-25 00:46:40 -04:00
|
|
|
break;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if (cmd == 0xf9 && value >= 0) {
|
2019-08-25 00:46:40 -04:00
|
|
|
break;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
|
|
|
if (cmd == 0xf5 && value < 0) {
|
2019-08-25 00:46:40 -04:00
|
|
|
break;
|
2019-09-01 15:50:50 -04:00
|
|
|
}
|
2019-08-25 00:46:40 -04:00
|
|
|
state->pc = seqPlayer->seqData + u16v;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf2: // reserve N notes for sequence
|
|
|
|
note_pool_clear(&seqPlayer->notePool);
|
|
|
|
note_pool_fill(&seqPlayer->notePool, m64_read_u8(state));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xf1: // reserve 0 notes for sequence
|
|
|
|
note_pool_clear(&seqPlayer->notePool);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xdf: // set transposition in semitones
|
|
|
|
seqPlayer->transposition = 0;
|
|
|
|
// fallthrough
|
|
|
|
|
|
|
|
case 0xde: // add transposition
|
|
|
|
seqPlayer->transposition += (s8) m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xdd: // set tempo (bpm)
|
|
|
|
case 0xdc: // increase tempo (bpm)
|
|
|
|
temp = m64_read_u8(state);
|
|
|
|
if (cmd == 0xdd) {
|
|
|
|
seqPlayer->tempo = temp * TEMPO_SCALE;
|
|
|
|
} else {
|
|
|
|
seqPlayer->tempo += (s8) temp * TEMPO_SCALE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (seqPlayer->tempo > gTempoInternalToExternal) {
|
|
|
|
seqPlayer->tempo = gTempoInternalToExternal;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((s16) seqPlayer->tempo <= 0) {
|
|
|
|
seqPlayer->tempo = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xdb: // set volume
|
|
|
|
temp = m64_read_u8(state);
|
|
|
|
switch (seqPlayer->state) {
|
|
|
|
case SEQUENCE_PLAYER_STATE_2:
|
|
|
|
if (seqPlayer->fadeTimer != 0) {
|
|
|
|
f32 targetVolume = FLOAT_CAST(temp) / US_FLOAT(127.0);
|
|
|
|
seqPlayer->fadeVelocity = (targetVolume - seqPlayer->fadeVolume)
|
|
|
|
/ FLOAT_CAST(seqPlayer->fadeTimer);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// fallthrough
|
|
|
|
case SEQUENCE_PLAYER_STATE_0:
|
|
|
|
seqPlayer->fadeVolume = FLOAT_CAST(temp) / US_FLOAT(127.0);
|
|
|
|
break;
|
|
|
|
case SEQUENCE_PLAYER_STATE_FADE_OUT:
|
|
|
|
case SEQUENCE_PLAYER_STATE_4:
|
|
|
|
seqPlayer->volume = FLOAT_CAST(temp) / US_FLOAT(127.0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xda: // increase/decrease volume
|
|
|
|
tempSigned = m64_read_u8(state);
|
|
|
|
seqPlayer->fadeVolume =
|
|
|
|
seqPlayer->fadeVolume + (f32) tempSigned / US_FLOAT(127.0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd7: // init channels
|
|
|
|
u16v = m64_read_s16(state);
|
|
|
|
sequence_player_init_channels(seqPlayer, u16v);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd6: // disable channels
|
|
|
|
u16v = m64_read_s16(state);
|
|
|
|
sequence_player_disable_channels(seqPlayer, u16v);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd5: // set volume multiplier for muted player
|
|
|
|
tempSigned = m64_read_u8(state);
|
|
|
|
seqPlayer->muteVolumeScale = (f32) tempSigned / US_FLOAT(127.0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd4: // mute
|
|
|
|
seqPlayer->muted = TRUE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd3: // set mute behavior
|
|
|
|
seqPlayer->muteBehavior = m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd2: // set short note velocity table
|
|
|
|
case 0xd1: // set short note duration table
|
|
|
|
u16v = m64_read_s16(state);
|
|
|
|
tempPtr = seqPlayer->seqData + u16v;
|
|
|
|
if (cmd == 0xd2) {
|
|
|
|
seqPlayer->shortNoteVelocityTable = tempPtr;
|
|
|
|
} else {
|
|
|
|
seqPlayer->shortNoteDurationTable = tempPtr;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xd0: // set note allocation policy (see playback.h)
|
|
|
|
seqPlayer->noteAllocPolicy = m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xcc: // set temp
|
|
|
|
value = m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc9: // bitand
|
|
|
|
value = m64_read_u8(state) & value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc8: // subtraction
|
|
|
|
value = value - m64_read_u8(state);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
loBits = cmd & 0xf;
|
|
|
|
switch (cmd & 0xf0) {
|
|
|
|
case 0x00: // test whether channel has been disabled by channel script
|
|
|
|
if (IS_SEQUENCE_CHANNEL_VALID(seqPlayer->channels[loBits]) == TRUE) {
|
|
|
|
value = seqPlayer->channels[loBits]->finished;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x10:
|
|
|
|
break;
|
|
|
|
case 0x20:
|
|
|
|
break;
|
|
|
|
case 0x40:
|
|
|
|
break;
|
|
|
|
case 0x50:
|
|
|
|
value -= seqPlayer->seqVariation;
|
|
|
|
break;
|
|
|
|
case 0x60:
|
|
|
|
break;
|
|
|
|
case 0x70:
|
|
|
|
seqPlayer->seqVariation = value;
|
|
|
|
break;
|
|
|
|
case 0x80:
|
|
|
|
value = seqPlayer->seqVariation;
|
|
|
|
break;
|
|
|
|
case 0x90: // start a channel with a given sound script
|
|
|
|
u16v = m64_read_s16(state);
|
|
|
|
sequence_channel_enable(seqPlayer, loBits, seqPlayer->seqData + u16v);
|
|
|
|
break;
|
|
|
|
case 0xa0:
|
|
|
|
break;
|
|
|
|
case 0xd8: // (this makes no sense)
|
|
|
|
break;
|
|
|
|
case 0xd9:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < CHANNELS_MAX; i++) {
|
|
|
|
if (seqPlayer->channels[i] != &gSequenceChannelNone) {
|
|
|
|
sequence_channel_process_script(seqPlayer->channels[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This runs 240 times per second.
|
|
|
|
void process_sequences(UNUSED s32 iterationsRemaining) {
|
|
|
|
s32 i;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
|
|
if (gSequencePlayers[i].enabled == TRUE) {
|
|
|
|
sequence_player_process_sequence(gSequencePlayers + i);
|
|
|
|
sequence_player_process_sound(gSequencePlayers + i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func_80319BC8();
|
|
|
|
func_80318908();
|
|
|
|
}
|
|
|
|
|
|
|
|
void init_sequence_player(u32 player) {
|
|
|
|
struct SequencePlayer *seqPlayer = &gSequencePlayers[player];
|
|
|
|
seqPlayer->muted = FALSE;
|
|
|
|
seqPlayer->delay = 0;
|
|
|
|
seqPlayer->state = SEQUENCE_PLAYER_STATE_0;
|
|
|
|
seqPlayer->fadeTimer = 0;
|
|
|
|
seqPlayer->tempoAcc = 0;
|
|
|
|
seqPlayer->tempo = 120 * TEMPO_SCALE; // 120 BPM
|
|
|
|
seqPlayer->transposition = 0;
|
|
|
|
seqPlayer->muteBehavior = MUTE_BEHAVIOR_80 | MUTE_BEHAVIOR_40 | MUTE_BEHAVIOR_20;
|
|
|
|
seqPlayer->noteAllocPolicy = 0;
|
|
|
|
seqPlayer->shortNoteVelocityTable = gDefaultShortNoteVelocityTable;
|
|
|
|
seqPlayer->shortNoteDurationTable = gDefaultShortNoteDurationTable;
|
|
|
|
seqPlayer->fadeVolume = 1.0f;
|
|
|
|
seqPlayer->fadeVelocity = 0.0f;
|
|
|
|
seqPlayer->volume = 0.0f;
|
|
|
|
seqPlayer->muteVolumeScale = 0.5f;
|
|
|
|
}
|
|
|
|
|
|
|
|
void func_8031D4B8(void) {
|
|
|
|
// Initialization function, called from audio_init
|
|
|
|
s32 i, j;
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
gSequenceChannels[i].seqPlayer = NULL;
|
|
|
|
gSequenceChannels[i].enabled = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
//! Size of wrong array. Zeroes out second half of gSequenceChannels[0],
|
|
|
|
// all of gSequenceChannels[1..31], and part of D_802245D8[0].
|
|
|
|
// However, this is only called at startup, so it's pretty harmless.
|
|
|
|
for (j = 0; j < ARRAY_COUNT(D_802245D8); j++) {
|
|
|
|
gSequenceChannels[i].layers[j] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func_8031AF74();
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_COUNT(D_802245D8); i++) {
|
|
|
|
D_802245D8[i].seqChannel = NULL;
|
|
|
|
D_802245D8[i].enabled = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < SEQUENCE_PLAYERS; i++) {
|
|
|
|
for (j = 0; j < CHANNELS_MAX; j++) {
|
|
|
|
gSequencePlayers[i].channels[j] = &gSequenceChannelNone;
|
|
|
|
}
|
|
|
|
|
|
|
|
gSequencePlayers[i].seqVariation = -1;
|
|
|
|
gSequencePlayers[i].bankDmaInProgress = FALSE;
|
|
|
|
gSequencePlayers[i].seqDmaInProgress = FALSE;
|
|
|
|
init_note_lists(&gSequencePlayers[i].notePool);
|
|
|
|
init_sequence_player(i);
|
|
|
|
}
|
|
|
|
}
|