Files
Microtransactions64/src/audio/synthesis.c
2021-09-23 13:37:03 -07:00

1804 lines
78 KiB
C

#ifndef VERSION_SH
#include <ultra64.h>
#include "synthesis.h"
#include "heap.h"
#include "data.h"
#include "load.h"
#include "seqplayer.h"
#include "internal.h"
#include "external.h"
#include "game/game_init.h"
#define DMEM_ADDR_TEMP 0x0
#define DMEM_ADDR_RESAMPLED 0x20
#define DMEM_ADDR_RESAMPLED2 0x160
#define DMEM_ADDR_UNCOMPRESSED_NOTE 0x180
#define DMEM_ADDR_NOTE_PAN_TEMP 0x200
#define DMEM_ADDR_STEREO_STRONG_TEMP_DRY 0x200
#define DMEM_ADDR_STEREO_STRONG_TEMP_WET 0x340
#define DMEM_ADDR_COMPRESSED_ADPCM_DATA 0x3f0
#define DMEM_ADDR_LEFT_CH 0x4c0
#define DMEM_ADDR_RIGHT_CH 0x600
#define DMEM_ADDR_WET_LEFT_CH 0x740
#define DMEM_ADDR_WET_RIGHT_CH 0x880
#define aSetLoadBufferPair(pkt, c, off) \
aSetBuffer(pkt, 0, c + DMEM_ADDR_WET_LEFT_CH, 0, DEFAULT_LEN_1CH - c); \
aLoadBuffer(pkt, VIRTUAL_TO_PHYSICAL2(gSynthesisReverb.ringBuffer.left + (off))); \
aSetBuffer(pkt, 0, c + DMEM_ADDR_WET_RIGHT_CH, 0, DEFAULT_LEN_1CH - c); \
aLoadBuffer(pkt, VIRTUAL_TO_PHYSICAL2(gSynthesisReverb.ringBuffer.right + (off)))
#define aSetSaveBufferPair(pkt, c, d, off) \
aSetBuffer(pkt, 0, 0, c + DMEM_ADDR_WET_LEFT_CH, d); \
aSaveBuffer(pkt, VIRTUAL_TO_PHYSICAL2(gSynthesisReverb.ringBuffer.left + (off))); \
aSetBuffer(pkt, 0, 0, c + DMEM_ADDR_WET_RIGHT_CH, d); \
aSaveBuffer(pkt, VIRTUAL_TO_PHYSICAL2(gSynthesisReverb.ringBuffer.right + (off)));
#define AUDIO_ALIGN(val, amnt) (((val) + (1 << amnt) - 1) & ~((1 << amnt) - 1))
#if defined(BETTER_REVERB) && (defined(VERSION_US) || defined(VERSION_JP))
/* ----------------------------------------------------------------------BEGIN REVERB PARAMETERS---------------------------------------------------------------------- */
/**
* This reverb is a much more natural, ambient implementation over vanilla's, though at the cost of some memory and performance.
* These parameters are here to provide maximum control over the usage of the reverb effect, as well as with game performance.
*
* To take advantage of the reverb effect, you can change the echo parameters set in levels/level_defines.h to tailor the reverb to each specific level area.
* To adjust reverb presence with individual sound effects, apply the .set_reverb command within sound/sequences/00_sound_player.s (see examples of other sounds that use it).
* To use with M64 sequences, set the Effect parameter for each channel accordingly (CC 91 for MIDI files).
*
* Most parameter configuration is to be done here, though BETTER_REVERB_SIZE can be adjusted in audio/synthesis.h.
*
* If after changing the parameters, you hear increasing noise followed by a sudden disappearance of reverb and/or scratchy audio, this indicates an s16 overflow.
* If this happens, stop immediately and reduce the parameters at fault. This becomes a ticking time bomb, and may eventually result in very loud noise if it reaches the point of s32 overflow.
* Depending on the violating parameters chosen, you probably won't ever experience s32 overflow, but s16 overflow still isn't a pleasant experience.
* Checks to prevent this have not been implemented to maximize performance potential, so choose your parameters wisely. The current defaults are unlikely to have this problem.
* Generally speaking, a sound that doesn't seem to be fading at a natural rate is a parameter red flag (also known as feedback).
*/
// Setting this to 4 corrupts the game, so set this value to -1 to use vanilla reverb if this is too slow, or if it just doesn't fit the desired aesthetic of a level.
// You can change this value before audio_reset_session gets called if different levels can tolerate the demand better than others or just have different reverb goals.
// A higher downsample value hits the game's frequency limit sooner, which can cause the reverb sometimes to be off pitch. This is a vanilla level issue (and also counter intuitive).
// Higher downsample values also result in slightly shorter reverb decay times.
s8 betterReverbDownsampleConsole = 3;
// Most emulators can handle a default value of 2, but 3 may be advisable in some cases if targeting older emulators (e.g. PJ64 1.6). Setting this to -1 also uses vanilla reverb.
// Using a value of 1 is not recommended except in very specific situations. If you do decide to use 1 here, you must adjust BETTER_REVERB_SIZE appropriately.
// You can change this value before audio_reset_session gets called if different levels can tolerate the demand better than others or just have different reverb goals.
// A higher downsample value hits the game's frequency limit sooner, which can cause the reverb sometimes to be off pitch. This is a vanilla level issue (and also counter intuitive).
// Higher downsample values also result in slightly shorter reverb decay times.
s8 betterReverbDownsampleEmulator = 2;
// This value represents the number of filters to use with the reverb. This can be decreased to improve performance, but at the cost of a lesser presence of reverb in the final audio.
// Filter count should always be a multiple of 3. Never ever set this value to be greater than NUM_ALLPASS.
// Setting it to anything less than 3 will disable reverb outright.
// This can be changed at any time, but is best set when calling audio_reset_session.
u32 reverbFilterCountConsole = NUM_ALLPASS - 6;
// This value represents the number of filters to use with the reverb. This can be decreased to improve performance, but at the cost of a lesser presence of reverb in the final audio.
// Filter count should always be a multiple of 3. Never ever set this value to be greater than NUM_ALLPASS.
// Setting it to anything less than 3 will disable reverb outright.
// This can be changed at any time, but is best set when calling audio_reset_session.
u32 reverbFilterCountEmulator = NUM_ALLPASS;
// Set this to TRUE to use mono over stereo for reverb. This should increase performance, but at the cost of a less fullfilling reverb experience.
// If performance is desirable, it is recommended to change reverbFilterCountConsole or betterReverbDownsampleConsole first.
// This can be changed at any time, but is best set when calling audio_reset_session.
u8 monoReverbConsole = FALSE;
// Set this to TRUE to use mono over stereo for reverb. This should increase performance, but at the cost of a less fullfilling reverb experience.
// If performance is desirable, it is recommended to change reverbFilterCountEmulator or betterReverbDownsampleEmulator first.
// This can be changed at any time, but is best set when calling audio_reset_session.
u8 monoReverbEmulator = FALSE;
// This value controls the size of the reverb buffer. It affects the global reverb delay time. This variable is one of the easiest to control.
// It is not recommended setting this to values greater than 0x1000 * 2^(downsample factor - 1), as you run the risk of running into a memory issue (though this is far from a guarantee).
// Setting the value lower than the downsample buffer size will destroy the game audio. This is taken into account automatically, but also means the value set here isn't what always gets used.
// If this value is changed, it will go into effect the next time audio_reset_session is called.
// Set to -1 to use a default preset instead. Higher values represent more audio delay (usually better for echoey spaces).
s32 betterReverbWindowsSize = -1;
// These are set to defines rather than variables to increase performance. Change these to s32 if you want them to be configurable in-game. (Maybe extern them in synthesis.h)
// Setting these to values larger than 0xFF (255) or less than 0 may cause issues and is not recommended.
#define REVERB_REV_INDEX 0x60 // Affects decay time mostly (large values can cause terrible feedback!); can be messed with at any time
#define REVERB_GAIN_INDEX 0xA0 // Affects signal immediately retransmitted back into buffers (mid-high values yield the strongest effect); can be messed with at any time
#define REVERB_WET_SIGNAL 0xE8 // Amount of reverb specific output in final signal (also affects decay); can be messed with at any time, also very easy to control
// #define REVERB_DRY_SIGNAL = 0x00; // Amount of original input in final signal (large values can cause terrible feedback!); declaration and uses commented out by default to improve compiler optimization
/* ---------------------------------------------------------------------ADVANCED REVERB PARAMETERS-------------------------------------------------------------------- */
// These values affect filter delays. Bigger values will result in fatter echo (and more memory); must be cumulatively smaller than BETTER_REVERB_SIZE/2.
// If setting a reverb downsample value to 1, these must be cumulatively smaller than BETTER_REVERB_SIZE/4.
// These values should never be changed unless in this declaration or during a call to audio_reset_session, as it could otherwise lead to a major memory leak or garbage audio.
// None of the delay values should ever be smaller than 1 either; these are s32s purely to avoid typecasts.
// These values are currently set by using delaysBaseline in the audio_reset_session function, so its behavior must be overridden to use dynamically (or at all).
s32 delaysL[NUM_ALLPASS] = {
1080, 1352, 1200,
1200, 1232, 1432,
1384, 1048, 1352,
928, 1504, 1512
};
s32 delaysR[NUM_ALLPASS] = {
1384, 1352, 1048,
928, 1512, 1504,
1080, 1200, 1352,
1200, 1432, 1232
};
// Like the delays array, but represents default max values that don't change (also probably somewhat redundant)
// Change this array rather than the delays array to customize reverb delay times globally.
// Similarly to delaysL/R, these should be kept within the memory constraints defined by BETTER_REVERB_SIZE.
const s32 delaysBaselineL[NUM_ALLPASS] = {
1080, 1352, 1200,
1200, 1232, 1432,
1384, 1048, 1352,
928, 1504, 1512
};
const s32 delaysBaselineR[NUM_ALLPASS] = {
1384, 1352, 1048,
928, 1512, 1504,
1080, 1200, 1352,
1200, 1432, 1232
};
// These values affect reverb decay depending on the filter index; can be messed with at any time
s32 reverbMultsL[NUM_ALLPASS / 3] = {0xD7, 0x6F, 0x36, 0x22};
s32 reverbMultsR[NUM_ALLPASS / 3] = {0xCF, 0x73, 0x38, 0x1F};
/* -----------------------------------------------------------------------END REVERB PARAMETERS----------------------------------------------------------------------- */
// Do not touch these values manually, unless you want potential for problems.
s32 reverbFilterCount = NUM_ALLPASS;
s32 reverbFilterCountm1 = NUM_ALLPASS - 1;
u8 monoReverb = FALSE;
u8 toggleBetterReverb = TRUE;
s32 allpassIdxL[NUM_ALLPASS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
s32 allpassIdxR[NUM_ALLPASS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
s32 tmpBufL[NUM_ALLPASS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
s32 tmpBufR[NUM_ALLPASS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
s32 **delayBufsL;
s32 **delayBufsR;
#endif
struct VolumeChange {
u16 sourceLeft;
u16 sourceRight;
u16 targetLeft;
u16 targetRight;
};
u64 *synthesis_do_one_audio_update(s16 *aiBuf, s32 bufLen, u64 *cmd, s32 updateIndex);
#ifdef VERSION_EU
u64 *synthesis_process_note(struct Note *note, struct NoteSubEu *noteSubEu, struct NoteSynthesisState *synthesisState, s16 *aiBuf, s32 bufLen, u64 *cmd);
u64 *load_wave_samples(u64 *cmd, struct NoteSubEu *noteSubEu, struct NoteSynthesisState *synthesisState, s32 nSamplesToLoad);
u64 *final_resample(u64 *cmd, struct NoteSynthesisState *synthesisState, s32 count, u16 pitch, u16 dmemIn, u32 flags);
u64 *process_envelope(u64 *cmd, struct NoteSubEu *noteSubEu, struct NoteSynthesisState *synthesisState, s32 nSamples, u16 inBuf, s32 headsetPanSettings, u32 flags);
u64 *note_apply_headset_pan_effects(u64 *cmd, struct NoteSubEu *noteSubEu, struct NoteSynthesisState *note, s32 bufLen, s32 flags, s32 leftRight);
#else
u64 *synthesis_process_notes(s16 *aiBuf, s32 bufLen, u64 *cmd);
u64 *load_wave_samples(u64 *cmd, struct Note *note, s32 nSamplesToLoad);
u64 *final_resample(u64 *cmd, struct Note *note, s32 count, u16 pitch, u16 dmemIn, u32 flags);
u64 *process_envelope(u64 *cmd, struct Note *note, s32 nSamples, u16 inBuf, s32 headsetPanSettings,
u32 flags);
u64 *process_envelope_inner(u64 *cmd, struct Note *note, s32 nSamples, u16 inBuf,
s32 headsetPanSettings, struct VolumeChange *vol);
u64 *note_apply_headset_pan_effects(u64 *cmd, struct Note *note, s32 bufLen, s32 flags, s32 leftRight);
#endif
#ifdef VERSION_EU
struct SynthesisReverb gSynthesisReverbs[4];
u8 sAudioSynthesisPad[0x10];
#else
struct SynthesisReverb gSynthesisReverb;
u8 sAudioSynthesisPad[0x20];
#endif
#if defined(BETTER_REVERB) && (defined(VERSION_US) || defined(VERSION_JP))
static inline s16 clamp16(s32 x) {
if (x >= 32767)
return 32767;
if (x <= -32768)
return -32768;
return (s16) x;
}
static inline void reverb_samples(s16 *outSampleL, s16 *outSampleR, s32 inSampleL, s32 inSampleR) {
s32 i = 0;
s32 j = 0;
s32 k = 0;
s32 outTmpL = 0;
s32 outTmpR = 0;
s32 tmpCarryoverL = ((tmpBufL[reverbFilterCountm1] * REVERB_REV_INDEX) / 256) + inSampleL;
s32 tmpCarryoverR = ((tmpBufR[reverbFilterCountm1] * REVERB_REV_INDEX) / 256) + inSampleR;
for (; i != reverbFilterCount; ++i, ++j) {
tmpBufL[i] = delayBufsL[i][allpassIdxL[i]];
tmpBufR[i] = delayBufsR[i][allpassIdxR[i]];
if (j == 2) {
j = -1;
outTmpL += (tmpBufL[i] * reverbMultsL[k]) / 256;
outTmpR += (tmpBufR[i] * reverbMultsR[k++]) / 256;
delayBufsL[i][allpassIdxL[i]] = tmpCarryoverL;
delayBufsR[i][allpassIdxR[i]] = tmpCarryoverR;
if (i != reverbFilterCountm1) {
tmpCarryoverL = (tmpBufL[i] * REVERB_REV_INDEX) / 256;
tmpCarryoverR = (tmpBufR[i] * REVERB_REV_INDEX) / 256;
}
}
else {
delayBufsL[i][allpassIdxL[i]] = (tmpBufL[i] * (-REVERB_GAIN_INDEX)) / 256 + tmpCarryoverL;
delayBufsR[i][allpassIdxR[i]] = (tmpBufR[i] * (-REVERB_GAIN_INDEX)) / 256 + tmpCarryoverR;
tmpCarryoverL = (delayBufsL[i][allpassIdxL[i]] * REVERB_GAIN_INDEX) / 256 + tmpBufL[i];
tmpCarryoverR = (delayBufsR[i][allpassIdxR[i]] * REVERB_GAIN_INDEX) / 256 + tmpBufR[i];
}
if (++allpassIdxL[i] == delaysL[i])
allpassIdxL[i] = 0;
if (++allpassIdxR[i] == delaysR[i])
allpassIdxR[i] = 0;
}
*outSampleL = clamp16((outTmpL * REVERB_WET_SIGNAL/* + inSampleL * REVERB_DRY_SIGNAL*/) / 256);
*outSampleR = clamp16((outTmpR * REVERB_WET_SIGNAL/* + inSampleR * REVERB_DRY_SIGNAL*/) / 256);
}
static inline void reverb_mono_sample(s16 *outSample, s32 inSample) {
s32 i = 0;
s32 j = 0;
s32 k = 0;
s32 outTmp = 0;
s32 tmpCarryover = ((tmpBufL[reverbFilterCountm1] * REVERB_REV_INDEX) / 256) + inSample;
for (; i != reverbFilterCount; ++i, ++j) {
tmpBufL[i] = delayBufsL[i][allpassIdxL[i]];
if (j == 2) {
j = -1;
outTmp += (tmpBufL[i] * reverbMultsL[k++]) / 256;
delayBufsL[i][allpassIdxL[i]] = tmpCarryover;
if (i != reverbFilterCountm1)
tmpCarryover = (tmpBufL[i] * REVERB_REV_INDEX) / 256;
}
else {
delayBufsL[i][allpassIdxL[i]] = (tmpBufL[i] * (-REVERB_GAIN_INDEX)) / 256 + tmpCarryover;
tmpCarryover = (delayBufsL[i][allpassIdxL[i]] * REVERB_GAIN_INDEX) / 256 + tmpBufL[i];
}
if (++allpassIdxL[i] == delaysL[i])
allpassIdxL[i] = 0;
}
*outSample = clamp16((outTmp * REVERB_WET_SIGNAL/* + inSample * REVERB_DRY_SIGNAL*/) / 256);
}
#endif
#ifdef VERSION_EU
s16 gVolume;
s8 gUseReverb;
s8 gNumSynthesisReverbs;
struct NoteSubEu *gNoteSubsEu;
#endif
#ifdef VERSION_EU
f32 gLeftVolRampings[3][1024];
f32 gRightVolRampings[3][1024];
f32 *gCurrentLeftVolRamping; // Points to any of the three left buffers above
f32 *gCurrentRightVolRamping; // Points to any of the three right buffers above
u8 audioString1[] = "pitch %x: delaybytes %d : olddelay %d\n";
u8 audioString2[] = "cont %x: delaybytes %d : olddelay %d\n";
#endif
#ifdef VERSION_EU
// Equivalent functionality as the US/JP version,
// just that the reverb structure is chosen from an array with index
void prepare_reverb_ring_buffer(s32 chunkLen, u32 updateIndex, s32 reverbIndex) {
struct ReverbRingBufferItem *item;
struct SynthesisReverb *reverb = &gSynthesisReverbs[reverbIndex];
s32 srcPos;
s32 dstPos;
s32 nSamples;
s32 excessiveSamples;
s32 UNUSED pad[3];
if (reverb->downsampleRate != 1) {
if (reverb->framesLeftToIgnore == 0) {
// Now that the RSP has finished, downsample the samples produced two frames ago by skipping
// samples.
item = &reverb->items[reverb->curFrame][updateIndex];
// Touches both left and right since they are adjacent in memory
osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH);
for (srcPos = 0, dstPos = 0; dstPos < item->lengthA / 2;
srcPos += reverb->downsampleRate, dstPos++) {
reverb->ringBuffer.left[item->startPos + dstPos] =
item->toDownsampleLeft[srcPos];
reverb->ringBuffer.right[item->startPos + dstPos] =
item->toDownsampleRight[srcPos];
}
for (dstPos = 0; dstPos < item->lengthB / 2; srcPos += reverb->downsampleRate, dstPos++) {
reverb->ringBuffer.left[dstPos] = item->toDownsampleLeft[srcPos];
reverb->ringBuffer.right[dstPos] = item->toDownsampleRight[srcPos];
}
}
}
item = &reverb->items[reverb->curFrame][updateIndex];
nSamples = chunkLen / reverb->downsampleRate;
excessiveSamples = (nSamples + reverb->nextRingBufferPos) - reverb->bufSizePerChannel;
if (excessiveSamples < 0) {
// There is space in the ring buffer before it wraps around
item->lengthA = nSamples * 2;
item->lengthB = 0;
item->startPos = (s32) reverb->nextRingBufferPos;
reverb->nextRingBufferPos += nSamples;
} else {
// Ring buffer wrapped around
item->lengthA = (nSamples - excessiveSamples) * 2;
item->lengthB = excessiveSamples * 2;
item->startPos = reverb->nextRingBufferPos;
reverb->nextRingBufferPos = excessiveSamples;
}
// These fields are never read later
item->numSamplesAfterDownsampling = nSamples;
item->chunkLen = chunkLen;
}
#else
void prepare_reverb_ring_buffer(s32 chunkLen, u32 updateIndex) {
struct ReverbRingBufferItem *item;
s32 srcPos;
s32 dstPos;
s32 nSamples;
s32 numSamplesAfterDownsampling;
s32 excessiveSamples;
#ifdef BETTER_REVERB
if (!toggleBetterReverb && gReverbDownsampleRate != 1) {
#else
if (gReverbDownsampleRate != 1) {
#endif
if (gSynthesisReverb.framesLeftToIgnore == 0) {
// Now that the RSP has finished, downsample the samples produced two frames ago by skipping
// samples.
item = &gSynthesisReverb.items[gSynthesisReverb.curFrame][updateIndex];
// Touches both left and right since they are adjacent in memory
osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH);
for (srcPos = 0, dstPos = 0; dstPos < item->lengthA / 2;
srcPos += gReverbDownsampleRate, dstPos++) {
gSynthesisReverb.ringBuffer.left[dstPos + item->startPos] =
item->toDownsampleLeft[srcPos];
gSynthesisReverb.ringBuffer.right[dstPos + item->startPos] =
item->toDownsampleRight[srcPos];
}
for (dstPos = 0; dstPos < item->lengthB / 2; srcPos += gReverbDownsampleRate, dstPos++) {
gSynthesisReverb.ringBuffer.left[dstPos] = item->toDownsampleLeft[srcPos];
gSynthesisReverb.ringBuffer.right[dstPos] = item->toDownsampleRight[srcPos];
}
}
}
#ifdef BETTER_REVERB
else if (toggleBetterReverb) {
item = &gSynthesisReverb.items[gSynthesisReverb.curFrame][updateIndex];
if (gSoundMode == SOUND_MODE_MONO || monoReverb) {
if (gReverbDownsampleRate != 1) {
osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH);
for (srcPos = 0, dstPos = item->startPos; dstPos < item->lengthA / 2 + item->startPos; srcPos += gReverbDownsampleRate, dstPos++) {
reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) item->toDownsampleLeft[srcPos] + (s32) item->toDownsampleRight[srcPos]) / 2);
gSynthesisReverb.ringBuffer.right[dstPos] = gSynthesisReverb.ringBuffer.left[dstPos];
}
for (dstPos = 0; dstPos < item->lengthB / 2; srcPos += gReverbDownsampleRate, dstPos++) {
reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) item->toDownsampleLeft[srcPos] + (s32) item->toDownsampleRight[srcPos]) / 2);
gSynthesisReverb.ringBuffer.right[dstPos] = gSynthesisReverb.ringBuffer.left[dstPos];
}
}
else { // Too slow for practical use, not recommended most of the time.
for (dstPos = item->startPos; dstPos < item->lengthA / 2 + item->startPos; dstPos++) {
reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) gSynthesisReverb.ringBuffer.left[dstPos] + (s32) gSynthesisReverb.ringBuffer.right[dstPos]) / 2);
gSynthesisReverb.ringBuffer.right[dstPos] = gSynthesisReverb.ringBuffer.left[dstPos];
}
for (dstPos = 0; dstPos < item->lengthB / 2; dstPos++) {
reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) gSynthesisReverb.ringBuffer.left[dstPos] + (s32) gSynthesisReverb.ringBuffer.right[dstPos]) / 2);
gSynthesisReverb.ringBuffer.right[dstPos] = gSynthesisReverb.ringBuffer.left[dstPos];
}
}
}
else {
if (gReverbDownsampleRate != 1) {
osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH);
for (srcPos = 0, dstPos = item->startPos; dstPos < item->lengthA / 2 + item->startPos; srcPos += gReverbDownsampleRate, dstPos++)
reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], item->toDownsampleLeft[srcPos], item->toDownsampleRight[srcPos]);
for (dstPos = 0; dstPos < item->lengthB / 2; srcPos += gReverbDownsampleRate, dstPos++)
reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], item->toDownsampleLeft[srcPos], item->toDownsampleRight[srcPos]);
}
else { // Too slow for practical use, not recommended most of the time.
for (dstPos = item->startPos; dstPos < item->lengthA / 2 + item->startPos; dstPos++)
reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], gSynthesisReverb.ringBuffer.left[dstPos], gSynthesisReverb.ringBuffer.right[dstPos]);
for (dstPos = 0; dstPos < item->lengthB / 2; dstPos++)
reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], gSynthesisReverb.ringBuffer.left[dstPos], gSynthesisReverb.ringBuffer.right[dstPos]);
}
}
}
#endif
item = &gSynthesisReverb.items[gSynthesisReverb.curFrame][updateIndex];
numSamplesAfterDownsampling = chunkLen / gReverbDownsampleRate;
if (((numSamplesAfterDownsampling + gSynthesisReverb.nextRingBufferPos) - gSynthesisReverb.bufSizePerChannel) < 0) {
// There is space in the ring buffer before it wraps around
item->lengthA = numSamplesAfterDownsampling * 2;
item->lengthB = 0;
item->startPos = (s32) gSynthesisReverb.nextRingBufferPos;
gSynthesisReverb.nextRingBufferPos += numSamplesAfterDownsampling;
} else {
// Ring buffer wrapped around
excessiveSamples =
(numSamplesAfterDownsampling + gSynthesisReverb.nextRingBufferPos) - gSynthesisReverb.bufSizePerChannel;
nSamples = numSamplesAfterDownsampling - excessiveSamples;
item->lengthA = nSamples * 2;
item->lengthB = excessiveSamples * 2;
item->startPos = gSynthesisReverb.nextRingBufferPos;
gSynthesisReverb.nextRingBufferPos = excessiveSamples;
}
// These fields are never read later
item->numSamplesAfterDownsampling = numSamplesAfterDownsampling;
item->chunkLen = chunkLen;
}
#endif
#ifdef VERSION_EU
u64 *synthesis_load_reverb_ring_buffer(u64 *cmd, u16 addr, u16 srcOffset, s32 len, s32 reverbIndex) {
aSetBuffer(cmd++, 0, addr, 0, len);
aLoadBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(&gSynthesisReverbs[reverbIndex].ringBuffer.left[srcOffset]));
aSetBuffer(cmd++, 0, addr + DEFAULT_LEN_1CH, 0, len);
aLoadBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(&gSynthesisReverbs[reverbIndex].ringBuffer.right[srcOffset]));
return cmd;
}
#endif
#ifdef VERSION_EU
u64 *synthesis_save_reverb_ring_buffer(u64 *cmd, u16 addr, u16 destOffset, s32 len, s32 reverbIndex) {
aSetBuffer(cmd++, 0, 0, addr, len);
aSaveBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(&gSynthesisReverbs[reverbIndex].ringBuffer.left[destOffset]));
aSetBuffer(cmd++, 0, 0, addr + DEFAULT_LEN_1CH, len);
aSaveBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(&gSynthesisReverbs[reverbIndex].ringBuffer.right[destOffset]));
return cmd;
}
#endif
#ifdef VERSION_EU
void synthesis_load_note_subs_eu(s32 updateIndex) {
struct NoteSubEu *src;
struct NoteSubEu *dest;
s32 i;
for (i = 0; i < gMaxSimultaneousNotes; i++) {
src = &gNotes[i].noteSubEu;
dest = &gNoteSubsEu[gMaxSimultaneousNotes * updateIndex + i];
if (src->enabled) {
*dest = *src;
src->needsInit = FALSE;
} else {
dest->enabled = FALSE;
}
}
}
#endif
#ifndef VERSION_EU
s32 get_volume_ramping(u16 sourceVol, u16 targetVol, s32 arg2) {
// This roughly computes 2^16 * (targetVol / sourceVol) ^ (8 / arg2),
// but with discretizations of targetVol, sourceVol and arg2.
f32 ret;
switch (arg2) {
default:
ret = gVolRampingLhs136[targetVol >> 8] * gVolRampingRhs136[sourceVol >> 8];
break;
case 128:
ret = gVolRampingLhs128[targetVol >> 8] * gVolRampingRhs128[sourceVol >> 8];
break;
case 136:
ret = gVolRampingLhs136[targetVol >> 8] * gVolRampingRhs136[sourceVol >> 8];
break;
case 144:
ret = gVolRampingLhs144[targetVol >> 8] * gVolRampingRhs144[sourceVol >> 8];
break;
}
return ret;
}
#endif
#ifdef VERSION_EU
// TODO: (Scrub C) pointless mask and whitespace
u64 *synthesis_execute(u64 *cmdBuf, s32 *writtenCmds, s16 *aiBuf, s32 bufLen) {
s32 i, j;
f32 *leftVolRamp;
f32 *rightVolRamp;
u32 *aiBufPtr;
u64 *cmd = cmdBuf;
s32 chunkLen;
s32 nextVolRampTable;
for (i = gAudioBufferParameters.updatesPerFrame; i > 0; i--) {
process_sequences(i - 1);
synthesis_load_note_subs_eu(gAudioBufferParameters.updatesPerFrame - i);
}
aSegment(cmd++, 0, 0);
aiBufPtr = (u32 *) aiBuf;
for (i = gAudioBufferParameters.updatesPerFrame; i > 0; i--) {
if (i == 1) {
#pragma GCC diagnostic push
#if defined(__clang__)
#pragma GCC diagnostic ignored "-Wself-assign"
#endif
// self-assignment has no affect when added here, could possibly simplify a macro definition
chunkLen = bufLen; nextVolRampTable = nextVolRampTable; leftVolRamp = gLeftVolRampings[nextVolRampTable]; rightVolRamp = gRightVolRampings[nextVolRampTable & 0xFFFFFFFF];
#pragma GCC diagnostic pop
} else {
if (bufLen / i >= gAudioBufferParameters.samplesPerUpdateMax) {
chunkLen = gAudioBufferParameters.samplesPerUpdateMax; nextVolRampTable = 2; leftVolRamp = gLeftVolRampings[2]; rightVolRamp = gRightVolRampings[2];
} else if (bufLen / i <= gAudioBufferParameters.samplesPerUpdateMin) {
chunkLen = gAudioBufferParameters.samplesPerUpdateMin; nextVolRampTable = 0; leftVolRamp = gLeftVolRampings[0]; rightVolRamp = gRightVolRampings[0];
} else {
chunkLen = gAudioBufferParameters.samplesPerUpdate; nextVolRampTable = 1; leftVolRamp = gLeftVolRampings[1]; rightVolRamp = gRightVolRampings[1];
}
}
gCurrentLeftVolRamping = leftVolRamp;
gCurrentRightVolRamping = rightVolRamp;
for (j = 0; j < gNumSynthesisReverbs; j++) {
if (gSynthesisReverbs[j].useReverb != 0) {
prepare_reverb_ring_buffer(chunkLen, gAudioBufferParameters.updatesPerFrame - i, j);
}
}
cmd = synthesis_do_one_audio_update((s16 *) aiBufPtr, chunkLen, cmd, gAudioBufferParameters.updatesPerFrame - i);
bufLen -= chunkLen;
aiBufPtr += chunkLen;
}
for (j = 0; j < gNumSynthesisReverbs; j++) {
if (gSynthesisReverbs[j].framesLeftToIgnore != 0) {
gSynthesisReverbs[j].framesLeftToIgnore--;
}
gSynthesisReverbs[j].curFrame ^= 1;
}
*writtenCmds = cmd - cmdBuf;
return cmd;
}
#else
// bufLen will be divisible by 16
u64 *synthesis_execute(u64 *cmdBuf, s32 *writtenCmds, s16 *aiBuf, s32 bufLen) {
s32 chunkLen;
s32 i;
u32 *aiBufPtr = (u32 *) aiBuf;
u64 *cmd = cmdBuf + 1;
s32 v0;
aSegment(cmdBuf, 0, 0);
#ifdef BETTER_REVERB
if (gIsConsole) {
reverbFilterCount = (s32) reverbFilterCountConsole;
monoReverb = monoReverbConsole;
}
else {
reverbFilterCount = (s32) reverbFilterCountEmulator;
monoReverb = monoReverbEmulator;
}
if (reverbFilterCount > NUM_ALLPASS)
reverbFilterCount = NUM_ALLPASS;
reverbFilterCountm1 = reverbFilterCount - 1;
if (reverbFilterCount < 3)
reverbFilterCountm1 = 0;
#endif
for (i = gAudioUpdatesPerFrame; i > 0; i--) {
if (i == 1) {
// 'bufLen' will automatically be divisible by 8, no need to round
chunkLen = bufLen;
} else {
v0 = bufLen / i;
// chunkLen = v0 rounded to nearest multiple of 8
chunkLen = v0 - (v0 & 7);
if ((v0 & 7) >= 4) {
chunkLen += 8;
}
}
process_sequences(i - 1);
if (gSynthesisReverb.useReverb != 0) {
prepare_reverb_ring_buffer(chunkLen, gAudioUpdatesPerFrame - i);
}
cmd = synthesis_do_one_audio_update((s16 *) aiBufPtr, chunkLen, cmd, gAudioUpdatesPerFrame - i);
bufLen -= chunkLen;
aiBufPtr += chunkLen;
}
if (gSynthesisReverb.framesLeftToIgnore != 0) {
gSynthesisReverb.framesLeftToIgnore--;
}
gSynthesisReverb.curFrame ^= 1;
*writtenCmds = cmd - cmdBuf;
return cmd;
}
#endif
#ifdef VERSION_EU
u64 *synthesis_resample_and_mix_reverb(u64 *cmd, s32 bufLen, s16 reverbIndex, s16 updateIndex) {
struct ReverbRingBufferItem *item;
s16 startPad;
s16 paddedLengthA;
item = &gSynthesisReverbs[reverbIndex].items[gSynthesisReverbs[reverbIndex].curFrame][updateIndex];
aClearBuffer(cmd++, DMEM_ADDR_WET_LEFT_CH, DEFAULT_LEN_2CH);
if (gSynthesisReverbs[reverbIndex].downsampleRate == 1) {
cmd = synthesis_load_reverb_ring_buffer(cmd, DMEM_ADDR_WET_LEFT_CH, item->startPos, item->lengthA, reverbIndex);
if (item->lengthB != 0) {
cmd = synthesis_load_reverb_ring_buffer(cmd, DMEM_ADDR_WET_LEFT_CH + item->lengthA, 0, item->lengthB, reverbIndex);
}
aSetBuffer(cmd++, 0, 0, 0, DEFAULT_LEN_2CH);
aMix(cmd++, 0, 0x7fff, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_LEFT_CH);
aMix(cmd++, 0, 0x8000 + gSynthesisReverbs[reverbIndex].reverbGain, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_WET_LEFT_CH);
} else {
startPad = (item->startPos % 8u) * 2;
paddedLengthA = AUDIO_ALIGN(startPad + item->lengthA, 4);
cmd = synthesis_load_reverb_ring_buffer(cmd, DMEM_ADDR_RESAMPLED, (item->startPos - startPad / 2), DEFAULT_LEN_1CH, reverbIndex);
if (item->lengthB != 0) {
cmd = synthesis_load_reverb_ring_buffer(cmd, DMEM_ADDR_RESAMPLED + paddedLengthA, 0, DEFAULT_LEN_1CH - paddedLengthA, reverbIndex);
}
aSetBuffer(cmd++, 0, DMEM_ADDR_RESAMPLED + startPad, DMEM_ADDR_WET_LEFT_CH, bufLen * 2);
aResample(cmd++, gSynthesisReverbs[reverbIndex].resampleFlags, gSynthesisReverbs[reverbIndex].resampleRate, VIRTUAL_TO_PHYSICAL2(gSynthesisReverbs[reverbIndex].resampleStateLeft));
aSetBuffer(cmd++, 0, DMEM_ADDR_RESAMPLED2 + startPad, DMEM_ADDR_WET_RIGHT_CH, bufLen * 2);
aResample(cmd++, gSynthesisReverbs[reverbIndex].resampleFlags, gSynthesisReverbs[reverbIndex].resampleRate, VIRTUAL_TO_PHYSICAL2(gSynthesisReverbs[reverbIndex].resampleStateRight));
aSetBuffer(cmd++, 0, 0, 0, DEFAULT_LEN_2CH);
aMix(cmd++, 0, 0x7fff, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_LEFT_CH);
aMix(cmd++, 0, 0x8000 + gSynthesisReverbs[reverbIndex].reverbGain, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_WET_LEFT_CH);
}
return cmd;
}
u64 *synthesis_save_reverb_samples(u64 *cmd, s16 reverbIndex, s16 updateIndex) {
struct ReverbRingBufferItem *item;
item = &gSynthesisReverbs[reverbIndex].items[gSynthesisReverbs[reverbIndex].curFrame][updateIndex];
if (gSynthesisReverbs[reverbIndex].useReverb != 0) {
switch (gSynthesisReverbs[reverbIndex].downsampleRate) {
case 1:
// Put the oldest samples in the ring buffer into the wet channels
cmd = synthesis_save_reverb_ring_buffer(cmd, DMEM_ADDR_WET_LEFT_CH, item->startPos, item->lengthA, reverbIndex);
if (item->lengthB != 0) {
// Ring buffer wrapped
cmd = synthesis_save_reverb_ring_buffer(cmd, DMEM_ADDR_WET_LEFT_CH + item->lengthA, 0, item->lengthB, reverbIndex);
}
break;
default:
// Downsampling is done later by CPU when RSP is done, therefore we need to have double
// buffering. Left and right buffers are adjacent in memory.
aSetBuffer(cmd++, 0, 0, DMEM_ADDR_WET_LEFT_CH, DEFAULT_LEN_2CH);
aSaveBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(gSynthesisReverbs[reverbIndex].items[gSynthesisReverbs[reverbIndex].curFrame][updateIndex].toDownsampleLeft));
gSynthesisReverbs[reverbIndex].resampleFlags = 0;
break;
}
}
return cmd;
}
#endif
#ifdef VERSION_EU
u64 *synthesis_do_one_audio_update(s16 *aiBuf, s32 bufLen, u64 *cmd, s32 updateIndex) {
struct NoteSubEu *noteSubEu;
u8 noteIndices[56];
s32 temp;
s32 i;
s16 j;
s16 notePos = 0;
if (gNumSynthesisReverbs == 0) {
for (i = 0; i < gMaxSimultaneousNotes; i++) {
if (gNoteSubsEu[gMaxSimultaneousNotes * updateIndex + i].enabled) {
noteIndices[notePos++] = i;
}
}
} else {
for (j = 0; j < gNumSynthesisReverbs; j++) {
for (i = 0; i < gMaxSimultaneousNotes; i++) {
noteSubEu = &gNoteSubsEu[gMaxSimultaneousNotes * updateIndex + i];
if (noteSubEu->enabled && j == noteSubEu->reverbIndex) {
noteIndices[notePos++] = i;
}
}
}
for (i = 0; i < gMaxSimultaneousNotes; i++) {
noteSubEu = &gNoteSubsEu[gMaxSimultaneousNotes * updateIndex + i];
if (noteSubEu->enabled && noteSubEu->reverbIndex >= gNumSynthesisReverbs) {
noteIndices[notePos++] = i;
}
}
}
aClearBuffer(cmd++, DMEM_ADDR_LEFT_CH, DEFAULT_LEN_2CH);
i = 0;
for (j = 0; j < gNumSynthesisReverbs; j++) {
gUseReverb = gSynthesisReverbs[j].useReverb;
if (gUseReverb != 0) {
cmd = synthesis_resample_and_mix_reverb(cmd, bufLen, j, updateIndex);
}
for (; i < notePos; i++) {
temp = updateIndex * gMaxSimultaneousNotes;
if (j == gNoteSubsEu[temp + noteIndices[i]].reverbIndex) {
cmd = synthesis_process_note(&gNotes[noteIndices[i]],
&gNoteSubsEu[temp + noteIndices[i]],
&gNotes[noteIndices[i]].synthesisState,
aiBuf, bufLen, cmd);
continue;
} else {
break;
}
}
if (gSynthesisReverbs[j].useReverb != 0) {
cmd = synthesis_save_reverb_samples(cmd, j, updateIndex);
}
}
for (; i < notePos; i++) {
temp = updateIndex * gMaxSimultaneousNotes;
if (IS_BANK_LOAD_COMPLETE(gNoteSubsEu[temp + noteIndices[i]].bankId) == TRUE) {
cmd = synthesis_process_note(&gNotes[noteIndices[i]],
&gNoteSubsEu[temp + noteIndices[i]],
&gNotes[noteIndices[i]].synthesisState,
aiBuf, bufLen, cmd);
} else {
gAudioErrorFlags = (gNoteSubsEu[temp + noteIndices[i]].bankId + (i << 8)) + 0x10000000;
}
}
temp = bufLen * 2;
aSetBuffer(cmd++, 0, 0, DMEM_ADDR_TEMP, temp);
aInterleave(cmd++, DMEM_ADDR_LEFT_CH, DMEM_ADDR_RIGHT_CH);
aSetBuffer(cmd++, 0, 0, DMEM_ADDR_TEMP, temp * 2);
aSaveBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(aiBuf));
return cmd;
}
#else
u64 *synthesis_do_one_audio_update(s16 *aiBuf, s32 bufLen, u64 *cmd, s32 updateIndex) {
s16 ra;
s16 t4;
struct ReverbRingBufferItem *v1;
v1 = &gSynthesisReverb.items[gSynthesisReverb.curFrame][updateIndex];
if (gSynthesisReverb.useReverb == 0) {
aClearBuffer(cmd++, DMEM_ADDR_LEFT_CH, DEFAULT_LEN_2CH);
cmd = synthesis_process_notes(aiBuf, bufLen, cmd);
} else {
if (gReverbDownsampleRate == 1) {
// Put the oldest samples in the ring buffer into the wet channels
aSetLoadBufferPair(cmd++, 0, v1->startPos);
if (v1->lengthB != 0) {
// Ring buffer wrapped
aSetLoadBufferPair(cmd++, v1->lengthA, 0);
}
// Use the reverb sound as initial sound for this audio update
aDMEMMove(cmd++, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_LEFT_CH, DEFAULT_LEN_2CH);
// (Hopefully) lower the volume of the wet channels. New reverb will later be mixed into
// these channels.
aSetBuffer(cmd++, 0, 0, 0, DEFAULT_LEN_2CH);
// 0x8000 here is -100%
aMix(cmd++, 0, /*gain*/ 0x8000 + gSynthesisReverb.reverbGain, /*in*/ DMEM_ADDR_WET_LEFT_CH,
/*out*/ DMEM_ADDR_WET_LEFT_CH);
} else {
// Same as above but upsample the previously downsampled samples used for reverb first
t4 = (v1->startPos & 7) * 2;
ra = AUDIO_ALIGN(v1->lengthA + t4, 4);
aSetLoadBufferPair(cmd++, 0, v1->startPos - t4 / 2);
if (v1->lengthB != 0) {
// Ring buffer wrapped
aSetLoadBufferPair(cmd++, ra, 0);
}
aSetBuffer(cmd++, 0, t4 + DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_LEFT_CH, bufLen << 1);
aResample(cmd++, gSynthesisReverb.resampleFlags, (u16) gSynthesisReverb.resampleRate, VIRTUAL_TO_PHYSICAL2(gSynthesisReverb.resampleStateLeft));
aSetBuffer(cmd++, 0, t4 + DMEM_ADDR_WET_RIGHT_CH, DMEM_ADDR_RIGHT_CH, bufLen << 1);
aResample(cmd++, gSynthesisReverb.resampleFlags, (u16) gSynthesisReverb.resampleRate, VIRTUAL_TO_PHYSICAL2(gSynthesisReverb.resampleStateRight));
aSetBuffer(cmd++, 0, 0, 0, DEFAULT_LEN_2CH);
aMix(cmd++, 0, /*gain*/ 0x8000 + gSynthesisReverb.reverbGain, /*in*/ DMEM_ADDR_LEFT_CH, /*out*/ DMEM_ADDR_LEFT_CH);
aDMEMMove(cmd++, DMEM_ADDR_LEFT_CH, DMEM_ADDR_WET_LEFT_CH, DEFAULT_LEN_2CH);
}
cmd = synthesis_process_notes(aiBuf, bufLen, cmd);
if (gReverbDownsampleRate == 1) {
aSetSaveBufferPair(cmd++, 0, v1->lengthA, v1->startPos);
if (v1->lengthB != 0) {
// Ring buffer wrapped
aSetSaveBufferPair(cmd++, v1->lengthA, v1->lengthB, 0);
}
} else {
// Downsampling is done later by CPU when RSP is done, therefore we need to have double
// buffering. Left and right buffers are adjacent in memory.
aSetBuffer(cmd++, 0, 0, DMEM_ADDR_WET_LEFT_CH, DEFAULT_LEN_2CH);
aSaveBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(gSynthesisReverb.items[gSynthesisReverb.curFrame][updateIndex].toDownsampleLeft));
gSynthesisReverb.resampleFlags = 0;
}
}
return cmd;
}
#endif
#ifdef VERSION_EU
// Processes just one note, not all
u64 *synthesis_process_note(struct Note *note, struct NoteSubEu *noteSubEu, struct NoteSynthesisState *synthesisState, UNUSED s16 *aiBuf, s32 bufLen, u64 *cmd) {
UNUSED s32 pad0[3];
#else
u64 *synthesis_process_notes(s16 *aiBuf, s32 bufLen, u64 *cmd) {
s32 noteIndex; // sp174
struct Note *note; // s7
UNUSED u8 pad0[0x08];
#endif
struct AudioBankSample *audioBookSample; // sp164, sp138
struct AdpcmLoop *loopInfo; // sp160, sp134
s16 *curLoadedBook = NULL; // sp154, sp130
#ifdef VERSION_EU
UNUSED u8 padEU[0x04];
#endif
UNUSED u8 pad8[0x04];
#ifndef VERSION_EU
u16 resamplingRateFixedPoint; // sp5c, sp11A
#endif
s32 noteFinished; // 150 t2, sp124
s32 restart; // 14c t3, sp120
s32 flags; // sp148, sp11C
#ifdef VERSION_EU
u16 resamplingRateFixedPoint; // sp5c, sp11A
#endif
UNUSED u8 pad7[0x0c]; // sp100
UNUSED s32 tempBufLen;
#ifdef VERSION_EU
s32 sp130 = 0; //sp128, sp104
UNUSED u32 pad9;
#else
UNUSED u32 pad9;
s32 sp130 = 0; //sp128, sp104
#endif
s32 nAdpcmSamplesProcessed; // signed required for US
s32 t0;
#ifdef VERSION_EU
u8 *sampleAddr; // sp120, spF4
s32 s6;
#else
s32 s6;
u8 *sampleAddr; // sp120, spF4
#endif
#ifdef VERSION_EU
s32 samplesLenAdjusted; // 108, spEC
// Might have been used to store (samplesLenFixedPoint >> 0x10), but doing so causes strange
// behavior with the break near the end of the loop, causing US and JP to need a goto instead
UNUSED s32 samplesLenInt;
s32 endPos; // sp110, spE4
s32 nSamplesToProcess; // sp10c/a0, spE0
s32 s2;
#else
// Might have been used to store (samplesLenFixedPoint >> 0x10), but doing so causes strange
// behavior with the break near the end of the loop, causing US and JP to need a goto instead
UNUSED s32 samplesLenInt;
s32 samplesLenAdjusted; // 108
s32 s2;
s32 endPos; // sp110, spE4
s32 nSamplesToProcess; // sp10c/a0, spE0
#endif
s32 leftRight;
s32 s3;
s32 s5; //s4
u32 samplesLenFixedPoint; // v1_1
s32 nSamplesInThisIteration; // v1_2
u32 a3;
#ifndef VERSION_EU
s32 t9;
#endif
u8 *v0_2;
s32 nParts; // spE8, spBC
s32 curPart; // spE4, spB8
#ifndef VERSION_EU
f32 resamplingRate; // f12
#endif
s32 temp;
#ifdef VERSION_EU
s32 s5Aligned;
#endif
s32 resampledTempLen; // spD8, spAC
u16 noteSamplesDmemAddrBeforeResampling; // spD6, spAA
#ifndef VERSION_EU
for (noteIndex = 0; noteIndex < gMaxSimultaneousNotes; noteIndex++) {
note = &gNotes[noteIndex];
#ifdef VERSION_US
//! This function requires note->enabled to be volatile, but it breaks other functions like note_enable.
//! Casting to a struct with just the volatile bitfield works, but there may be a better way to match.
if (((struct vNote *)note)->enabled && IS_BANK_LOAD_COMPLETE(note->bankId) == FALSE) {
#else
if (IS_BANK_LOAD_COMPLETE(note->bankId) == FALSE) {
#endif
gAudioErrorFlags = (note->bankId << 8) + noteIndex + 0x1000000;
} else if (((struct vNote *)note)->enabled) {
#else
if (note->noteSubEu.enabled == FALSE) {
return cmd;
} else {
#endif
flags = 0;
#ifdef VERSION_EU
tempBufLen = bufLen;
#endif
#ifdef VERSION_EU
if (noteSubEu->needsInit == TRUE) {
#else
if (note->needsInit == TRUE) {
#endif
flags = A_INIT;
#ifndef VERSION_EU
note->samplePosInt = 0;
note->samplePosFrac = 0;
#else
synthesisState->restart = FALSE;
synthesisState->samplePosInt = 0;
synthesisState->samplePosFrac = 0;
synthesisState->curVolLeft = 1;
synthesisState->curVolRight = 1;
synthesisState->prevHeadsetPanRight = 0;
synthesisState->prevHeadsetPanLeft = 0;
#endif
}
#ifndef VERSION_EU
if (note->frequency < US_FLOAT(2.0)) {
nParts = 1;
if (note->frequency > US_FLOAT(1.99996)) {
note->frequency = US_FLOAT(1.99996);
}
resamplingRate = note->frequency;
} else {
// If frequency is > 2.0, the processing must be split into two parts
nParts = 2;
if (note->frequency >= US_FLOAT(3.99993)) {
note->frequency = US_FLOAT(3.99993);
}
resamplingRate = note->frequency * US_FLOAT(.5);
}
resamplingRateFixedPoint = (u16)(s32)(resamplingRate * 32768.0f);
samplesLenFixedPoint = note->samplePosFrac + (resamplingRateFixedPoint * bufLen) * 2;
note->samplePosFrac = samplesLenFixedPoint & 0xFFFF; // 16-bit store, can't reuse
#else
resamplingRateFixedPoint = noteSubEu->resamplingRateFixedPoint;
nParts = noteSubEu->hasTwoAdpcmParts + 1;
samplesLenFixedPoint = (resamplingRateFixedPoint * tempBufLen * 2) + synthesisState->samplePosFrac;
synthesisState->samplePosFrac = samplesLenFixedPoint & 0xFFFF;
#endif
#ifdef VERSION_EU
if (noteSubEu->isSyntheticWave) {
cmd = load_wave_samples(cmd, noteSubEu, synthesisState, samplesLenFixedPoint >> 0x10);
noteSamplesDmemAddrBeforeResampling = (synthesisState->samplePosInt * 2) + DMEM_ADDR_UNCOMPRESSED_NOTE;
synthesisState->samplePosInt += samplesLenFixedPoint >> 0x10;
}
#else
if (note->sound == NULL) {
// A wave synthesis note (not ADPCM)
cmd = load_wave_samples(cmd, note, samplesLenFixedPoint >> 0x10);
noteSamplesDmemAddrBeforeResampling = DMEM_ADDR_UNCOMPRESSED_NOTE + note->samplePosInt * 2;
note->samplePosInt += (samplesLenFixedPoint >> 0x10);
flags = 0;
}
#endif
else {
// ADPCM note
#ifdef VERSION_EU
audioBookSample = noteSubEu->sound.audioBankSound->sample;
#else
audioBookSample = note->sound->sample;
#endif
loopInfo = audioBookSample->loop;
endPos = loopInfo->end;
sampleAddr = audioBookSample->sampleAddr;
resampledTempLen = 0;
for (curPart = 0; curPart < nParts; curPart++) {
nAdpcmSamplesProcessed = 0; // s8
s5 = 0; // s4
if (nParts == 1) {
samplesLenAdjusted = samplesLenFixedPoint >> 0x10;
} else if ((samplesLenFixedPoint >> 0x10) & 1) {
samplesLenAdjusted = ((samplesLenFixedPoint >> 0x10) & ~1) + (curPart * 2);
}
else {
samplesLenAdjusted = (samplesLenFixedPoint >> 0x10);
}
if (curLoadedBook != audioBookSample->book->book) {
u32 nEntries; // v1
curLoadedBook = audioBookSample->book->book;
#ifdef VERSION_EU
nEntries = 16 * audioBookSample->book->order * audioBookSample->book->npredictors;
aLoadADPCM(cmd++, nEntries, VIRTUAL_TO_PHYSICAL2(curLoadedBook + noteSubEu->bookOffset));
#else
nEntries = audioBookSample->book->order * audioBookSample->book->npredictors;
aLoadADPCM(cmd++, nEntries * 16, VIRTUAL_TO_PHYSICAL2(curLoadedBook));
#endif
}
#ifdef VERSION_EU
if (noteSubEu->bookOffset) {
curLoadedBook = euUnknownData_80301950; // what's this? never read
}
#endif
while (nAdpcmSamplesProcessed != samplesLenAdjusted) {
s32 samplesRemaining; // v1
s32 s0;
noteFinished = FALSE;
restart = FALSE;
nSamplesToProcess = samplesLenAdjusted - nAdpcmSamplesProcessed;
#ifdef VERSION_EU
s2 = synthesisState->samplePosInt & 0xf;
samplesRemaining = endPos - synthesisState->samplePosInt;
#else
s2 = note->samplePosInt & 0xf;
samplesRemaining = endPos - note->samplePosInt;
#endif
#ifdef VERSION_EU
if (s2 == 0 && synthesisState->restart == FALSE) {
s2 = 16;
}
#else
if (s2 == 0 && note->restart == FALSE) {
s2 = 16;
}
#endif
s6 = 16 - s2; // a1
if (nSamplesToProcess < samplesRemaining) {
t0 = (nSamplesToProcess - s6 + 0xf) / 16;
s0 = t0 * 16;
s3 = s6 + s0 - nSamplesToProcess;
} else {
#ifndef VERSION_EU
s0 = samplesRemaining + s2 - 0x10;
#else
s0 = samplesRemaining - s6;
#endif
s3 = 0;
if (s0 <= 0) {
s0 = 0;
s6 = samplesRemaining;
}
t0 = (s0 + 0xf) / 16;
if (loopInfo->count != 0) {
// Loop around and restart
restart = 1;
} else {
noteFinished = 1;
}
}
if (t0 != 0) {
#ifdef VERSION_EU
temp = (synthesisState->samplePosInt - s2 + 0x10) / 16;
if (audioBookSample->loaded == 0x81) {
v0_2 = sampleAddr + temp * 9;
} else {
v0_2 = dma_sample_data(
(uintptr_t) (sampleAddr + temp * 9),
t0 * 9, flags, &synthesisState->sampleDmaIndex);
}
#else
temp = (note->samplePosInt - s2 + 0x10) / 16;
v0_2 = dma_sample_data(
(uintptr_t) (sampleAddr + temp * 9),
t0 * 9, flags, &note->sampleDmaIndex);
#endif
a3 = (u32)((uintptr_t) v0_2 & 0xf);
aSetBuffer(cmd++, 0, DMEM_ADDR_COMPRESSED_ADPCM_DATA, 0, t0 * 9 + a3);
aLoadBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(v0_2 - a3));
} else {
s0 = 0;
a3 = 0;
}
#ifdef VERSION_EU
if (synthesisState->restart != FALSE) {
aSetLoop(cmd++, VIRTUAL_TO_PHYSICAL2(audioBookSample->loop->state));
flags = A_LOOP; // = 2
synthesisState->restart = FALSE;
}
#else
if (note->restart != FALSE) {
aSetLoop(cmd++, VIRTUAL_TO_PHYSICAL2(audioBookSample->loop->state));
flags = A_LOOP; // = 2
note->restart = FALSE;
}
#endif
nSamplesInThisIteration = s0 + s6 - s3;
#ifdef VERSION_EU
if (nAdpcmSamplesProcessed == 0) {
aSetBuffer(cmd++, 0, DMEM_ADDR_COMPRESSED_ADPCM_DATA + a3,
DMEM_ADDR_UNCOMPRESSED_NOTE, s0 * 2);
aADPCMdec(cmd++, flags,
VIRTUAL_TO_PHYSICAL2(synthesisState->synthesisBuffers->adpcmdecState));
sp130 = s2 * 2;
} else {
s5Aligned = AUDIO_ALIGN(s5, 5);
aSetBuffer(cmd++, 0, DMEM_ADDR_COMPRESSED_ADPCM_DATA + a3,
DMEM_ADDR_UNCOMPRESSED_NOTE + s5Aligned, s0 * 2);
aADPCMdec(cmd++, flags,
VIRTUAL_TO_PHYSICAL2(synthesisState->synthesisBuffers->adpcmdecState));
aDMEMMove(cmd++, DMEM_ADDR_UNCOMPRESSED_NOTE + s5Aligned + (s2 * 2),
DMEM_ADDR_UNCOMPRESSED_NOTE + s5, (nSamplesInThisIteration) * 2);
}
#else
if (nAdpcmSamplesProcessed == 0) {
aSetBuffer(cmd++, 0, DMEM_ADDR_COMPRESSED_ADPCM_DATA + a3, DMEM_ADDR_UNCOMPRESSED_NOTE, s0 * 2);
aADPCMdec(cmd++, flags, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->adpcmdecState));
sp130 = s2 * 2;
} else {
aSetBuffer(cmd++, 0, DMEM_ADDR_COMPRESSED_ADPCM_DATA + a3, DMEM_ADDR_UNCOMPRESSED_NOTE + AUDIO_ALIGN(s5, 5), s0 * 2);
aADPCMdec(cmd++, flags, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->adpcmdecState));
aDMEMMove(cmd++, DMEM_ADDR_UNCOMPRESSED_NOTE + AUDIO_ALIGN(s5, 5) + (s2 * 2), DMEM_ADDR_UNCOMPRESSED_NOTE + s5, (nSamplesInThisIteration) * 2);
}
#endif
nAdpcmSamplesProcessed += nSamplesInThisIteration;
switch (flags) {
case A_INIT: // = 1
sp130 = 0;
s5 = s0 * 2 + s5;
break;
case A_LOOP: // = 2
s5 = nSamplesInThisIteration * 2 + s5;
break;
default:
if (s5 != 0) {
s5 = nSamplesInThisIteration * 2 + s5;
} else {
s5 = (s2 + nSamplesInThisIteration) * 2;
}
break;
}
flags = 0;
if (noteFinished) {
aClearBuffer(cmd++, DMEM_ADDR_UNCOMPRESSED_NOTE + s5,
(samplesLenAdjusted - nAdpcmSamplesProcessed) * 2);
#ifdef VERSION_EU
noteSubEu->finished = 1;
note->noteSubEu.finished = 1;
note->noteSubEu.enabled = 0;
#else
note->samplePosInt = 0;
note->finished = 1;
((struct vNote *)note)->enabled = 0;
#endif
break;
}
#ifdef VERSION_EU
if (restart) {
synthesisState->restart = TRUE;
synthesisState->samplePosInt = loopInfo->start;
} else {
synthesisState->samplePosInt += nSamplesToProcess;
}
#else
if (restart) {
note->restart = TRUE;
note->samplePosInt = loopInfo->start;
} else {
note->samplePosInt += nSamplesToProcess;
}
#endif
}
switch (nParts) {
case 1:
noteSamplesDmemAddrBeforeResampling = DMEM_ADDR_UNCOMPRESSED_NOTE + sp130;
break;
case 2:
switch (curPart) {
case 0:
aSetBuffer(cmd++, 0, DMEM_ADDR_UNCOMPRESSED_NOTE + sp130, DMEM_ADDR_RESAMPLED, samplesLenAdjusted + 4);
#ifdef VERSION_EU
aResample(cmd++, A_INIT, 0xff60, VIRTUAL_TO_PHYSICAL2(synthesisState->synthesisBuffers->dummyResampleState));
#else
aResample(cmd++, A_INIT, 0xff60, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->dummyResampleState));
#endif
resampledTempLen = samplesLenAdjusted + 4;
noteSamplesDmemAddrBeforeResampling = DMEM_ADDR_RESAMPLED + 4;
#ifdef VERSION_EU
if (noteSubEu->finished != FALSE) {
#else
if (note->finished != FALSE) {
#endif
aClearBuffer(cmd++, DMEM_ADDR_RESAMPLED + resampledTempLen, samplesLenAdjusted + 0x10);
}
break;
case 1:
aSetBuffer(cmd++, 0, DMEM_ADDR_UNCOMPRESSED_NOTE + sp130,
DMEM_ADDR_RESAMPLED2,
samplesLenAdjusted + 8);
#ifdef VERSION_EU
aResample(cmd++, A_INIT, 0xff60,
VIRTUAL_TO_PHYSICAL2(
synthesisState->synthesisBuffers->dummyResampleState));
#else
aResample(cmd++, A_INIT, 0xff60,
VIRTUAL_TO_PHYSICAL2(
note->synthesisBuffers->dummyResampleState));
#endif
aDMEMMove(cmd++, DMEM_ADDR_RESAMPLED2 + 4,
DMEM_ADDR_RESAMPLED + resampledTempLen,
samplesLenAdjusted + 4);
break;
}
}
#ifdef VERSION_EU
if (noteSubEu->finished != FALSE) {
#else
if (note->finished != FALSE) {
#endif
break;
}
}
}
flags = 0;
#ifdef VERSION_EU
if (noteSubEu->needsInit == TRUE) {
flags = A_INIT;
noteSubEu->needsInit = FALSE;
}
cmd = final_resample(cmd, synthesisState, bufLen * 2, resamplingRateFixedPoint,
noteSamplesDmemAddrBeforeResampling, flags);
#else
if (note->needsInit == TRUE) {
flags = A_INIT;
note->needsInit = FALSE;
}
cmd = final_resample(cmd, note, bufLen * 2, resamplingRateFixedPoint,
noteSamplesDmemAddrBeforeResampling, flags);
#endif
#ifdef VERSION_EU
if (noteSubEu->headsetPanRight != 0 || synthesisState->prevHeadsetPanRight != 0) {
leftRight = 1;
} else if (noteSubEu->headsetPanLeft != 0 || synthesisState->prevHeadsetPanLeft != 0) {
leftRight = 2;
#else
if (note->headsetPanRight != 0 || note->prevHeadsetPanRight != 0) {
leftRight = 1;
} else if (note->headsetPanLeft != 0 || note->prevHeadsetPanLeft != 0) {
leftRight = 2;
#endif
} else {
leftRight = 0;
}
#ifdef VERSION_EU
cmd = process_envelope(cmd, noteSubEu, synthesisState, bufLen, 0, leftRight, flags);
#else
cmd = process_envelope(cmd, note, bufLen, 0, leftRight, flags);
#endif
#ifdef VERSION_EU
if (noteSubEu->usesHeadsetPanEffects) {
cmd = note_apply_headset_pan_effects(cmd, noteSubEu, synthesisState, bufLen * 2, flags, leftRight);
}
#else
if (note->usesHeadsetPanEffects) {
cmd = note_apply_headset_pan_effects(cmd, note, bufLen * 2, flags, leftRight);
}
#endif
}
#ifndef VERSION_EU
}
t9 = bufLen * 2;
aSetBuffer(cmd++, 0, 0, DMEM_ADDR_TEMP, t9);
aInterleave(cmd++, DMEM_ADDR_LEFT_CH, DMEM_ADDR_RIGHT_CH);
t9 *= 2;
aSetBuffer(cmd++, 0, 0, DMEM_ADDR_TEMP, t9);
aSaveBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(aiBuf));
#endif
return cmd;
}
#ifdef VERSION_EU
u64 *load_wave_samples(u64 *cmd, struct NoteSubEu *noteSubEu, struct NoteSynthesisState *synthesisState, s32 nSamplesToLoad) {
s32 a3;
s32 repeats;
s32 i;
aSetBuffer(cmd++, /*flags*/ 0, /*dmemin*/ DMEM_ADDR_UNCOMPRESSED_NOTE, /*dmemout*/ 0, /*count*/ 128);
aLoadBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(noteSubEu->sound.samples));
synthesisState->samplePosInt &= 0x3f;
a3 = 64 - synthesisState->samplePosInt;
if (a3 < nSamplesToLoad) {
repeats = (nSamplesToLoad - a3 + 63) / 64;
for (i = 0; i < repeats; i++) {
aDMEMMove(cmd++,
/*dmemin*/ DMEM_ADDR_UNCOMPRESSED_NOTE,
/*dmemout*/ DMEM_ADDR_UNCOMPRESSED_NOTE + (1 + i) * 128,
/*count*/ 128);
}
}
return cmd;
}
#else
u64 *load_wave_samples(u64 *cmd, struct Note *note, s32 nSamplesToLoad) {
s32 a3;
s32 i;
aSetBuffer(cmd++, /*flags*/ 0, /*dmemin*/ DMEM_ADDR_UNCOMPRESSED_NOTE, /*dmemout*/ 0,
/*count*/ sizeof(note->synthesisBuffers->samples));
aLoadBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->samples));
note->samplePosInt &= (note->sampleCount - 1);
a3 = 64 - note->samplePosInt;
if (a3 < nSamplesToLoad) {
for (i = 0; i <= (nSamplesToLoad - a3 + 63) / 64 - 1; i++) {
aDMEMMove(cmd++, /*dmemin*/ DMEM_ADDR_UNCOMPRESSED_NOTE, /*dmemout*/ DMEM_ADDR_UNCOMPRESSED_NOTE + (1 + i) * sizeof(note->synthesisBuffers->samples), /*count*/ sizeof(note->synthesisBuffers->samples));
}
}
return cmd;
}
#endif
#ifdef VERSION_EU
u64 *final_resample(u64 *cmd, struct NoteSynthesisState *synthesisState, s32 count, u16 pitch, u16 dmemIn, u32 flags) {
aSetBuffer(cmd++, /*flags*/ 0, dmemIn, /*dmemout*/ DMEM_ADDR_TEMP, count);
aResample(cmd++, flags, pitch, VIRTUAL_TO_PHYSICAL2(synthesisState->synthesisBuffers->finalResampleState));
return cmd;
}
#else
u64 *final_resample(u64 *cmd, struct Note *note, s32 count, u16 pitch, u16 dmemIn, u32 flags) {
aSetBuffer(cmd++, /*flags*/ 0, dmemIn, /*dmemout*/ DMEM_ADDR_TEMP, count);
aResample(cmd++, flags, pitch, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->finalResampleState));
return cmd;
}
#endif
#ifndef VERSION_EU
u64 *process_envelope(u64 *cmd, struct Note *note, s32 nSamples, u16 inBuf, s32 headsetPanSettings,
UNUSED u32 flags) {
UNUSED u8 pad[16];
struct VolumeChange vol;
vol.sourceLeft = note->curVolLeft;
vol.sourceRight = note->curVolRight;
vol.targetLeft = note->targetVolLeft;
vol.targetRight = note->targetVolRight;
note->curVolLeft = vol.targetLeft;
note->curVolRight = vol.targetRight;
return process_envelope_inner(cmd, note, nSamples, inBuf, headsetPanSettings, &vol);
}
u64 *process_envelope_inner(u64 *cmd, struct Note *note, s32 nSamples, u16 inBuf,
s32 headsetPanSettings, struct VolumeChange *vol) {
UNUSED u8 pad[3];
u8 mixerFlags;
UNUSED u8 pad2[8];
s32 rampLeft, rampRight;
#elif defined(VERSION_EU)
u64 *process_envelope(u64 *cmd, struct NoteSubEu *note, struct NoteSynthesisState *synthesisState, s32 nSamples, u16 inBuf, s32 headsetPanSettings, UNUSED u32 flags) {
UNUSED u8 pad1[20];
u16 sourceRight;
u16 sourceLeft;
UNUSED u8 pad2[4];
u16 targetLeft;
u16 targetRight;
s32 mixerFlags;
s32 rampLeft;
s32 rampRight;
sourceLeft = synthesisState->curVolLeft;
sourceRight = synthesisState->curVolRight;
targetLeft = (note->targetVolLeft << 5);
targetRight = (note->targetVolRight << 5);
if (targetLeft == 0) {
targetLeft++;
}
if (targetRight == 0) {
targetRight++;
}
synthesisState->curVolLeft = targetLeft;
synthesisState->curVolRight = targetRight;
#endif
// For aEnvMixer, five buffers and count are set using aSetBuffer.
// in, dry left, count without A_AUX flag.
// dry right, wet left, wet right with A_AUX flag.
if (note->usesHeadsetPanEffects) {
aClearBuffer(cmd++, DMEM_ADDR_NOTE_PAN_TEMP, DEFAULT_LEN_1CH);
switch (headsetPanSettings) {
case 1:
aSetBuffer(cmd++, 0, inBuf, DMEM_ADDR_NOTE_PAN_TEMP, nSamples * 2);
aSetBuffer(cmd++, A_AUX, DMEM_ADDR_RIGHT_CH, DMEM_ADDR_WET_LEFT_CH,
DMEM_ADDR_WET_RIGHT_CH);
break;
case 2:
aSetBuffer(cmd++, 0, inBuf, DMEM_ADDR_LEFT_CH, nSamples * 2);
aSetBuffer(cmd++, A_AUX, DMEM_ADDR_NOTE_PAN_TEMP, DMEM_ADDR_WET_LEFT_CH,
DMEM_ADDR_WET_RIGHT_CH);
break;
default:
aSetBuffer(cmd++, 0, inBuf, DMEM_ADDR_LEFT_CH, nSamples * 2);
aSetBuffer(cmd++, A_AUX, DMEM_ADDR_RIGHT_CH, DMEM_ADDR_WET_LEFT_CH,
DMEM_ADDR_WET_RIGHT_CH);
break;
}
} else {
// It's a bit unclear what the "stereo strong" concept does.
// Instead of mixing the opposite channel to the normal buffers, the sound is first
// mixed into a temporary buffer and then subtracted from the normal buffer.
if (note->stereoStrongRight) {
aClearBuffer(cmd++, DMEM_ADDR_STEREO_STRONG_TEMP_DRY, DEFAULT_LEN_2CH);
aSetBuffer(cmd++, 0, inBuf, DMEM_ADDR_STEREO_STRONG_TEMP_DRY, nSamples * 2);
aSetBuffer(cmd++, A_AUX, DMEM_ADDR_RIGHT_CH, DMEM_ADDR_STEREO_STRONG_TEMP_WET,
DMEM_ADDR_WET_RIGHT_CH);
} else if (note->stereoStrongLeft) {
aClearBuffer(cmd++, DMEM_ADDR_STEREO_STRONG_TEMP_DRY, DEFAULT_LEN_2CH);
aSetBuffer(cmd++, 0, inBuf, DMEM_ADDR_LEFT_CH, nSamples * 2);
aSetBuffer(cmd++, A_AUX, DMEM_ADDR_STEREO_STRONG_TEMP_DRY, DMEM_ADDR_WET_LEFT_CH,
DMEM_ADDR_STEREO_STRONG_TEMP_WET);
} else {
aSetBuffer(cmd++, 0, inBuf, DMEM_ADDR_LEFT_CH, nSamples * 2);
aSetBuffer(cmd++, A_AUX, DMEM_ADDR_RIGHT_CH, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_WET_RIGHT_CH);
}
}
#ifdef VERSION_EU
if (targetLeft == sourceLeft && targetRight == sourceRight && !note->envMixerNeedsInit) {
#else
if (vol->targetLeft == vol->sourceLeft && vol->targetRight == vol->sourceRight
&& !note->envMixerNeedsInit) {
#endif
mixerFlags = A_CONTINUE;
} else {
mixerFlags = A_INIT;
#ifdef VERSION_EU
rampLeft = gCurrentLeftVolRamping[targetLeft >> 5] * gCurrentRightVolRamping[sourceLeft >> 5];
rampRight = gCurrentLeftVolRamping[targetRight >> 5] * gCurrentRightVolRamping[sourceRight >> 5];
#else
rampLeft = get_volume_ramping(vol->sourceLeft, vol->targetLeft, nSamples);
rampRight = get_volume_ramping(vol->sourceRight, vol->targetRight, nSamples);
#endif
// The operation's parameters change meanings depending on flags
#ifdef VERSION_EU
aSetVolume(cmd++, A_VOL | A_LEFT, sourceLeft, 0, 0);
aSetVolume(cmd++, A_VOL | A_RIGHT, sourceRight, 0, 0);
aSetVolume32(cmd++, A_RATE | A_LEFT, targetLeft, rampLeft);
aSetVolume32(cmd++, A_RATE | A_RIGHT, targetRight, rampRight);
aSetVolume(cmd++, A_AUX, gVolume, 0, note->reverbVol << 8);
#else
aSetVolume(cmd++, A_VOL | A_LEFT, vol->sourceLeft, 0, 0);
aSetVolume(cmd++, A_VOL | A_RIGHT, vol->sourceRight, 0, 0);
aSetVolume32(cmd++, A_RATE | A_LEFT, vol->targetLeft, rampLeft);
aSetVolume32(cmd++, A_RATE | A_RIGHT, vol->targetRight, rampRight);
aSetVolume(cmd++, A_AUX, gVolume, 0, note->reverbVolShifted);
#endif
}
#ifdef VERSION_EU
if (gUseReverb && note->reverbVol != 0) {
aEnvMixer(cmd++, mixerFlags | A_AUX,
VIRTUAL_TO_PHYSICAL2(synthesisState->synthesisBuffers->mixEnvelopeState));
#else
if (gSynthesisReverb.useReverb && note->reverbVol != 0) {
aEnvMixer(cmd++, mixerFlags | A_AUX,
VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->mixEnvelopeState));
#endif
if (note->stereoStrongRight) {
aSetBuffer(cmd++, 0, 0, 0, nSamples * 2);
// 0x8000 is -100%, so subtract sound instead of adding...
aMix(cmd++, 0, /*gain*/ 0x8000, /*in*/ DMEM_ADDR_STEREO_STRONG_TEMP_DRY,
/*out*/ DMEM_ADDR_LEFT_CH);
aMix(cmd++, 0, /*gain*/ 0x8000, /*in*/ DMEM_ADDR_STEREO_STRONG_TEMP_WET,
/*out*/ DMEM_ADDR_WET_LEFT_CH);
} else if (note->stereoStrongLeft) {
aSetBuffer(cmd++, 0, 0, 0, nSamples * 2);
aMix(cmd++, 0, /*gain*/ 0x8000, /*in*/ DMEM_ADDR_STEREO_STRONG_TEMP_DRY,
/*out*/ DMEM_ADDR_RIGHT_CH);
aMix(cmd++, 0, /*gain*/ 0x8000, /*in*/ DMEM_ADDR_STEREO_STRONG_TEMP_WET,
/*out*/ DMEM_ADDR_WET_RIGHT_CH);
}
} else {
#ifdef VERSION_EU
aEnvMixer(cmd++, mixerFlags, VIRTUAL_TO_PHYSICAL2(synthesisState->synthesisBuffers->mixEnvelopeState));
#else
aEnvMixer(cmd++, mixerFlags, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->mixEnvelopeState));
#endif
if (note->stereoStrongRight) {
aSetBuffer(cmd++, 0, 0, 0, nSamples * 2);
aMix(cmd++, 0, /*gain*/ 0x8000, /*in*/ DMEM_ADDR_STEREO_STRONG_TEMP_DRY,
/*out*/ DMEM_ADDR_LEFT_CH);
} else if (note->stereoStrongLeft) {
aSetBuffer(cmd++, 0, 0, 0, nSamples * 2);
aMix(cmd++, 0, /*gain*/ 0x8000, /*in*/ DMEM_ADDR_STEREO_STRONG_TEMP_DRY,
/*out*/ DMEM_ADDR_RIGHT_CH);
}
}
return cmd;
}
#ifdef VERSION_EU
u64 *note_apply_headset_pan_effects(u64 *cmd, struct NoteSubEu *noteSubEu, struct NoteSynthesisState *note, s32 bufLen, s32 flags, s32 leftRight) {
#else
u64 *note_apply_headset_pan_effects(u64 *cmd, struct Note *note, s32 bufLen, s32 flags, s32 leftRight) {
#endif
u16 dest;
u16 pitch;
#ifdef VERSION_EU
u8 prevPanShift;
u8 panShift;
UNUSED u8 unkDebug;
#else
u16 prevPanShift;
u16 panShift;
#endif
switch (leftRight) {
case 1:
dest = DMEM_ADDR_LEFT_CH;
#ifdef VERSION_EU
panShift = noteSubEu->headsetPanRight;
#else
panShift = note->headsetPanRight;
#endif
note->prevHeadsetPanLeft = 0;
prevPanShift = note->prevHeadsetPanRight;
note->prevHeadsetPanRight = panShift;
break;
case 2:
dest = DMEM_ADDR_RIGHT_CH;
#ifdef VERSION_EU
panShift = noteSubEu->headsetPanLeft;
#else
panShift = note->headsetPanLeft;
#endif
note->prevHeadsetPanRight = 0;
prevPanShift = note->prevHeadsetPanLeft;
note->prevHeadsetPanLeft = panShift;
break;
default:
return cmd;
}
if (flags != 1) { // A_INIT?
// Slightly adjust the sample rate in order to fit a change in pan shift
if (prevPanShift == 0) {
// Kind of a hack that moves the first samples into the resample state
aDMEMMove(cmd++, DMEM_ADDR_NOTE_PAN_TEMP, DMEM_ADDR_TEMP, 8);
aClearBuffer(cmd++, 8, 8); // Set pitch accumulator to 0 in the resample state
aDMEMMove(cmd++, DMEM_ADDR_NOTE_PAN_TEMP, DMEM_ADDR_TEMP + 0x10,
0x10); // No idea, result seems to be overwritten later
aSetBuffer(cmd++, 0, 0, DMEM_ADDR_TEMP, 32);
aSaveBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->panResampleState));
#ifdef VERSION_EU
pitch = (bufLen << 0xf) / (bufLen + panShift - prevPanShift + 8);
if (pitch) {
}
#else
pitch = (bufLen << 0xf) / (panShift + bufLen - prevPanShift + 8);
#endif
aSetBuffer(cmd++, 0, DMEM_ADDR_NOTE_PAN_TEMP + 8, DMEM_ADDR_TEMP, panShift + bufLen - prevPanShift);
aResample(cmd++, 0, pitch, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->panResampleState));
} else {
if (panShift == 0) {
pitch = (bufLen << 0xf) / (bufLen - prevPanShift - 4);
} else {
pitch = (bufLen << 0xf) / (bufLen + panShift - prevPanShift);
}
aSetBuffer(cmd++, 0, DMEM_ADDR_NOTE_PAN_TEMP, DMEM_ADDR_TEMP, panShift + bufLen - prevPanShift);
aResample(cmd++, 0, pitch, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->panResampleState));
}
if (prevPanShift != 0) {
aSetBuffer(cmd++, 0, DMEM_ADDR_NOTE_PAN_TEMP, 0, prevPanShift);
aLoadBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->panSamplesBuffer));
aDMEMMove(cmd++, DMEM_ADDR_TEMP, DMEM_ADDR_NOTE_PAN_TEMP + prevPanShift, panShift + bufLen - prevPanShift);
} else {
aDMEMMove(cmd++, DMEM_ADDR_TEMP, DMEM_ADDR_NOTE_PAN_TEMP, panShift + bufLen - prevPanShift);
}
} else {
// Just shift right
aDMEMMove(cmd++, DMEM_ADDR_NOTE_PAN_TEMP, DMEM_ADDR_TEMP, bufLen);
aDMEMMove(cmd++, DMEM_ADDR_TEMP, DMEM_ADDR_NOTE_PAN_TEMP + panShift, bufLen);
aClearBuffer(cmd++, DMEM_ADDR_NOTE_PAN_TEMP, panShift);
}
if (panShift) {
// Save excessive samples for next iteration
aSetBuffer(cmd++, 0, 0, DMEM_ADDR_NOTE_PAN_TEMP + bufLen, panShift);
aSaveBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->panSamplesBuffer));
}
aSetBuffer(cmd++, 0, 0, 0, bufLen);
aMix(cmd++, 0, /*gain*/ 0x7fff, /*in*/ DMEM_ADDR_NOTE_PAN_TEMP, /*out*/ dest);
return cmd;
}
#ifndef VERSION_EU
// Moved to playback.c in EU
void note_init_volume(struct Note *note) {
note->targetVolLeft = 0;
note->targetVolRight = 0;
note->reverbVol = 0;
note->reverbVolShifted = 0;
note->unused2 = 0;
note->curVolLeft = 1;
note->curVolRight = 1;
note->frequency = 0.0f;
}
void note_set_vel_pan_reverb(struct Note *note, f32 velocity, f32 pan, u8 reverbVol) {
s32 panIndex;
f32 volLeft;
f32 volRight;
// Anding with 127 avoids out-of-bounds reads when pan is outside of [0, 1].
// This can occur during PU movement -- see the bug comment in get_sound_pan
// in external.c. An out-of-bounds read by itself doesn't crash, but if the
// resulting value is a nan or denormal, performing arithmetic on it crashes
// on console.
#ifdef VERSION_JP
panIndex = MIN((s32)(pan * 127.5), 127);
#else
panIndex = (s32)(pan * 127.5f) & 127;
#endif
if (note->stereoHeadsetEffects && gSoundMode == SOUND_MODE_HEADSET) {
s8 smallPanIndex;
s8 temp = (s8)(pan * 10.0f);
if (temp < 9) {
smallPanIndex = temp;
} else {
smallPanIndex = 9;
}
note->headsetPanLeft = gHeadsetPanQuantization[smallPanIndex];
note->headsetPanRight = gHeadsetPanQuantization[9 - smallPanIndex];
note->stereoStrongRight = FALSE;
note->stereoStrongLeft = FALSE;
note->usesHeadsetPanEffects = TRUE;
volLeft = gHeadsetPanVolume[panIndex];
volRight = gHeadsetPanVolume[127 - panIndex];
} else if (note->stereoHeadsetEffects && gSoundMode == SOUND_MODE_STEREO) {
u8 strongLeft;
u8 strongRight;
strongLeft = FALSE;
strongRight = FALSE;
note->headsetPanLeft = 0;
note->headsetPanRight = 0;
note->usesHeadsetPanEffects = FALSE;
volLeft = gStereoPanVolume[panIndex];
volRight = gStereoPanVolume[127 - panIndex];
if (panIndex < 0x20) {
strongLeft = TRUE;
} else if (panIndex > 0x60) {
strongRight = TRUE;
}
note->stereoStrongRight = strongRight;
note->stereoStrongLeft = strongLeft;
} else if (gSoundMode == SOUND_MODE_MONO) {
volLeft = .707f;
volRight = .707f;
} else {
volLeft = gDefaultPanVolume[panIndex];
volRight = gDefaultPanVolume[127 - panIndex];
}
if (velocity < 0) {
velocity = 0;
}
#ifdef VERSION_JP
note->targetVolLeft = (u16)(velocity * volLeft) & ~0x80FF; // 0x7F00, but that doesn't match
note->targetVolRight = (u16)(velocity * volRight) & ~0x80FF;
#else
note->targetVolLeft = (u16)(s32)(velocity * volLeft) & ~0x80FF;
note->targetVolRight = (u16)(s32)(velocity * volRight) & ~0x80FF;
#endif
if (note->targetVolLeft == 0) {
note->targetVolLeft++;
}
if (note->targetVolRight == 0) {
note->targetVolRight++;
}
if (note->reverbVol != reverbVol) {
note->reverbVol = reverbVol;
note->reverbVolShifted = reverbVol << 8;
note->envMixerNeedsInit = TRUE;
return;
}
if (note->needsInit) {
note->envMixerNeedsInit = TRUE;
} else {
note->envMixerNeedsInit = FALSE;
}
}
void note_set_frequency(struct Note *note, f32 frequency) {
note->frequency = frequency;
}
void note_enable(struct Note *note) {
note->enabled = TRUE;
note->needsInit = TRUE;
note->restart = FALSE;
note->finished = FALSE;
note->stereoStrongRight = FALSE;
note->stereoStrongLeft = FALSE;
note->usesHeadsetPanEffects = FALSE;
note->headsetPanLeft = 0;
note->headsetPanRight = 0;
note->prevHeadsetPanRight = 0;
note->prevHeadsetPanLeft = 0;
}
void note_disable(struct Note *note) {
if (note->needsInit == TRUE) {
note->needsInit = FALSE;
} else {
note_set_vel_pan_reverb(note, 0, .5, 0);
}
note->priority = NOTE_PRIORITY_DISABLED;
note->enabled = FALSE;
note->finished = FALSE;
note->parentLayer = NO_LAYER;
note->prevParentLayer = NO_LAYER;
}
#endif
#endif