mirror of
https://github.com/zeldaret/mm.git
synced 2026-07-09 13:45:23 -04:00
3e32379c2b
* import synth docs * cleanup * small followup cleanup * PR Suggestions, small cleanup * fix bss * PR suggestion * fix enum * PR Suggestions
1699 lines
70 KiB
C
1699 lines
70 KiB
C
#include "global.h"
|
|
|
|
// DMEM Addresses for the RSP
|
|
#define DMEM_TEMP 0x3B0
|
|
#define DMEM_TEMP2 0x3C0
|
|
#define DMEM_SURROUND_TEMP 0x4B0
|
|
#define DMEM_UNCOMPRESSED_NOTE 0x570
|
|
#define DMEM_HAAS_TEMP 0x5B0
|
|
#define DMEM_COMB_TEMP 0x750 // = DMEM_TEMP + DMEM_2CH_SIZE + a bit more
|
|
#define DMEM_COMPRESSED_ADPCM_DATA 0x930 // = DMEM_LEFT_CH
|
|
#define DMEM_LEFT_CH 0x930
|
|
#define DMEM_RIGHT_CH 0xAD0
|
|
#define DMEM_WET_TEMP 0x3D0
|
|
#define DMEM_WET_SCRATCH 0x710 // = DMEM_WET_TEMP + DMEM_2CH_SIZE
|
|
#define DMEM_WET_LEFT_CH 0xC70
|
|
#define DMEM_WET_RIGHT_CH 0xE10 // = DMEM_WET_LEFT_CH + DMEM_1CH_SIZE
|
|
|
|
typedef enum {
|
|
/* 0 */ HAAS_EFFECT_DELAY_NONE,
|
|
/* 1 */ HAAS_EFFECT_DELAY_LEFT, // Delay left channel so that right channel is heard first
|
|
/* 2 */ HAAS_EFFECT_DELAY_RIGHT // Delay right channel so that left channel is heard first
|
|
} HaasEffectDelaySide;
|
|
|
|
Acmd* AudioSynth_SaveResampledReverbSamplesImpl(Acmd* cmd, u16 dmem, u16 arg2, uintptr_t arg3);
|
|
Acmd* AudioSynth_LoadReverbSamplesImpl(Acmd* cmd, u16 dmem, u16 startPos, s32 size, SynthesisReverb* reverb);
|
|
Acmd* AudioSynth_SaveReverbSamplesImpl(Acmd* cmd, u16 dmem, u16 startPos, s32 size, SynthesisReverb* reverb);
|
|
Acmd* AudioSynth_ProcessSamples(s16* aiBuf, s32 numSamplesPerUpdate, Acmd* cmd, s32 updateIndex);
|
|
Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, NoteSynthesisState* synthState, s16* aiBuf,
|
|
s32 numSamplesPerUpdate, Acmd* cmd, s32 updateIndex);
|
|
Acmd* AudioSynth_ApplySurroundEffect(Acmd* cmd, NoteSampleState* sampleState, NoteSynthesisState* synthState, s32 size,
|
|
s32 dmem, s32 flags);
|
|
Acmd* AudioSynth_FinalResample(Acmd* cmd, NoteSynthesisState* synthState, s32 size, u16 pitch, u16 inpDmem,
|
|
s32 resampleFlags);
|
|
Acmd* AudioSynth_ProcessEnvelope(Acmd* cmd, NoteSampleState* sampleState, NoteSynthesisState* synthState,
|
|
s32 numSamplesPerUpdate, u16 dmemSrc, s32 haasEffectDelaySide, s32 flags);
|
|
Acmd* AudioSynth_LoadWaveSamples(Acmd* cmd, NoteSampleState* sampleState, NoteSynthesisState* synthState,
|
|
s32 numSamplesToLoad);
|
|
Acmd* AudioSynth_ApplyHaasEffect(Acmd* cmd, NoteSampleState* sampleState, NoteSynthesisState* synthState, s32 size,
|
|
s32 flags, s32 haasEffectDelaySide);
|
|
|
|
s32 D_801D5FB0 = 0;
|
|
|
|
u32 sEnvMixerOp = _SHIFTL(A_ENVMIXER, 24, 8);
|
|
|
|
// Store the left dry channel in a temp space to be delayed to produce the haas effect
|
|
u32 sEnvMixerLeftHaasDmemDests =
|
|
AUDIO_MK_CMD(DMEM_HAAS_TEMP >> 4, DMEM_RIGHT_CH >> 4, DMEM_WET_LEFT_CH >> 4, DMEM_WET_RIGHT_CH >> 4);
|
|
|
|
// Store the right dry channel in a temp space to be delayed to produce the haas effect
|
|
u32 sEnvMixerRightHaasDmemDests =
|
|
AUDIO_MK_CMD(DMEM_LEFT_CH >> 4, DMEM_HAAS_TEMP >> 4, DMEM_WET_LEFT_CH >> 4, DMEM_WET_RIGHT_CH >> 4);
|
|
|
|
u32 sEnvMixerDefaultDmemDests =
|
|
AUDIO_MK_CMD(DMEM_LEFT_CH >> 4, DMEM_RIGHT_CH >> 4, DMEM_WET_LEFT_CH >> 4, DMEM_WET_RIGHT_CH >> 4);
|
|
|
|
// Unused Data
|
|
u16 D_801D5FC4[] = {
|
|
0x7FFF, 0xD001, 0x3FFF, 0xF001, 0x5FFF, 0x9001, 0x7FFF, 0x8001,
|
|
};
|
|
|
|
u8 sNumSamplesPerWavePeriod[] = {
|
|
WAVE_SAMPLE_COUNT / 1, // 1st harmonic
|
|
WAVE_SAMPLE_COUNT / 2, // 2nd harmonic
|
|
WAVE_SAMPLE_COUNT / 4, // 4th harmonic
|
|
WAVE_SAMPLE_COUNT / 8, // 8th harmonic
|
|
};
|
|
|
|
/**
|
|
* Add a collection of s16-samples as a single entry to the reverb buffer
|
|
*/
|
|
void AudioSynth_AddReverbBufferEntry(s32 numSamples, s32 updateIndex, s32 reverbIndex) {
|
|
SynthesisReverb* reverb;
|
|
ReverbBufferEntry* entry;
|
|
s32 extraSamples;
|
|
s32 numSamplesAfterDownsampling;
|
|
s32 reverbBufPos;
|
|
s32 temp_t2;
|
|
s32 temp_t4;
|
|
s32 count1;
|
|
s32 count2;
|
|
s32 nextReverbSubBufPos;
|
|
|
|
reverb = &gAudioContext.synthesisReverbs[reverbIndex];
|
|
entry = &reverb->bufEntry[reverb->curFrame][updateIndex];
|
|
|
|
numSamplesAfterDownsampling = numSamples / gAudioContext.synthesisReverbs[reverbIndex].downsampleRate;
|
|
|
|
// Apply resampling effect
|
|
if (gAudioContext.synthesisReverbs[reverbIndex].resampleEffectOn) {
|
|
if (reverb->downsampleRate == 1) {
|
|
count1 = 0;
|
|
count2 = 0;
|
|
|
|
numSamplesAfterDownsampling += reverb->resampleEffectExtraSamples;
|
|
|
|
entry->saveResampleNumSamples = numSamplesAfterDownsampling;
|
|
entry->loadResamplePitch = ((u16)numSamplesAfterDownsampling << 0xF) / numSamples;
|
|
entry->saveResamplePitch = (numSamples << 0xF) / (u16)numSamplesAfterDownsampling;
|
|
|
|
while (true) {
|
|
temp_t2 = (entry->loadResamplePitch * numSamples * 2) + reverb->resampleEffectLoadUnk;
|
|
temp_t4 = temp_t2 >> 0x10;
|
|
|
|
if ((temp_t4 != numSamplesAfterDownsampling) && (count1 == 0)) {
|
|
entry->loadResamplePitch =
|
|
((numSamplesAfterDownsampling << 0x10) - reverb->resampleEffectLoadUnk) / (numSamples * 2);
|
|
count1++;
|
|
} else {
|
|
count1++;
|
|
if (temp_t4 > numSamplesAfterDownsampling) {
|
|
entry->loadResamplePitch--;
|
|
} else if (temp_t4 < numSamplesAfterDownsampling) {
|
|
entry->loadResamplePitch++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
reverb->resampleEffectLoadUnk = temp_t2 & 0xFFFF;
|
|
|
|
while (true) {
|
|
temp_t2 = (entry->saveResamplePitch * numSamplesAfterDownsampling * 2) + reverb->resampleEffectSaveUnk;
|
|
temp_t4 = temp_t2 >> 0x10;
|
|
|
|
if ((temp_t4 != numSamples) && (count2 == 0)) {
|
|
entry->saveResamplePitch =
|
|
((numSamples << 0x10) - reverb->resampleEffectSaveUnk) / (numSamplesAfterDownsampling * 2);
|
|
count2++;
|
|
} else {
|
|
count2++;
|
|
if (temp_t4 > numSamples) {
|
|
entry->saveResamplePitch--;
|
|
} else if (temp_t4 < numSamples) {
|
|
entry->saveResamplePitch++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
reverb->resampleEffectSaveUnk = temp_t2 & 0xFFFF;
|
|
}
|
|
}
|
|
|
|
extraSamples = (reverb->nextReverbBufPos + numSamplesAfterDownsampling) - reverb->delayNumSamples;
|
|
reverbBufPos = reverb->nextReverbBufPos;
|
|
|
|
// Add a reverb entry
|
|
if (extraSamples < 0) {
|
|
entry->size = numSamplesAfterDownsampling * SAMPLE_SIZE;
|
|
entry->wrappedSize = 0;
|
|
entry->startPos = reverb->nextReverbBufPos;
|
|
reverb->nextReverbBufPos += numSamplesAfterDownsampling;
|
|
} else {
|
|
// End of the buffer is reached. Loop back around
|
|
entry->size = (numSamplesAfterDownsampling - extraSamples) * SAMPLE_SIZE;
|
|
entry->wrappedSize = extraSamples * SAMPLE_SIZE;
|
|
entry->startPos = reverb->nextReverbBufPos;
|
|
reverb->nextReverbBufPos = extraSamples;
|
|
}
|
|
|
|
entry->numSamplesAfterDownsampling = numSamplesAfterDownsampling;
|
|
entry->numSamples = numSamples;
|
|
|
|
// Add a sub-reverb entry
|
|
if (reverb->subDelay != 0) {
|
|
nextReverbSubBufPos = reverb->subDelay + reverbBufPos;
|
|
if (nextReverbSubBufPos >= reverb->delayNumSamples) {
|
|
nextReverbSubBufPos -= reverb->delayNumSamples;
|
|
}
|
|
|
|
entry = &reverb->subBufEntry[reverb->curFrame][updateIndex];
|
|
numSamplesAfterDownsampling = numSamples / reverb->downsampleRate;
|
|
extraSamples = (nextReverbSubBufPos + numSamplesAfterDownsampling) - reverb->delayNumSamples;
|
|
|
|
if (extraSamples < 0) {
|
|
entry->size = numSamplesAfterDownsampling * SAMPLE_SIZE;
|
|
entry->wrappedSize = 0;
|
|
entry->startPos = nextReverbSubBufPos;
|
|
} else {
|
|
// End of the buffer is reached. Loop back around
|
|
entry->size = (numSamplesAfterDownsampling - extraSamples) * SAMPLE_SIZE;
|
|
entry->wrappedSize = extraSamples * SAMPLE_SIZE;
|
|
entry->startPos = nextReverbSubBufPos;
|
|
}
|
|
|
|
entry->numSamplesAfterDownsampling = numSamplesAfterDownsampling;
|
|
entry->numSamples = numSamples;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sync the sample states between the notes and the list
|
|
*/
|
|
void AudioSynth_SyncSampleStates(s32 updateIndex) {
|
|
NoteSampleState* noteSampleState;
|
|
NoteSampleState* sampleState;
|
|
s32 sampleStateBaseIndex;
|
|
s32 i;
|
|
|
|
sampleStateBaseIndex = gAudioContext.numNotes * updateIndex;
|
|
for (i = 0; i < gAudioContext.numNotes; i++) {
|
|
noteSampleState = &gAudioContext.notes[i].sampleState;
|
|
sampleState = &gAudioContext.sampleStateList[sampleStateBaseIndex + i];
|
|
if (noteSampleState->bitField0.enabled) {
|
|
noteSampleState->bitField0.needsInit = false;
|
|
} else {
|
|
sampleState->bitField0.enabled = false;
|
|
}
|
|
|
|
noteSampleState->harmonicIndexCurAndPrev = 0;
|
|
}
|
|
}
|
|
|
|
Acmd* AudioSynth_Update(Acmd* abiCmdStart, s32* numAbiCmds, s16* aiBufStart, s32 numSamplesPerFrame) {
|
|
s32 numSamplesPerUpdate;
|
|
s16* curAiBufPos;
|
|
Acmd* curCmd = abiCmdStart;
|
|
s32 reverseUpdateIndex;
|
|
s32 reverbIndex;
|
|
SynthesisReverb* reverb;
|
|
|
|
for (reverseUpdateIndex = gAudioContext.audioBufferParameters.updatesPerFrame; reverseUpdateIndex > 0;
|
|
reverseUpdateIndex--) {
|
|
AudioSeq_ProcessSequences(reverseUpdateIndex - 1);
|
|
AudioSynth_SyncSampleStates(gAudioContext.audioBufferParameters.updatesPerFrame - reverseUpdateIndex);
|
|
}
|
|
|
|
curAiBufPos = aiBufStart;
|
|
gAudioContext.adpcmCodeBook = NULL;
|
|
|
|
// Process/Update all samples multiple times in a single frame
|
|
for (reverseUpdateIndex = gAudioContext.audioBufferParameters.updatesPerFrame; reverseUpdateIndex > 0;
|
|
reverseUpdateIndex--) {
|
|
if (reverseUpdateIndex == 1) {
|
|
// Final Update
|
|
numSamplesPerUpdate = numSamplesPerFrame;
|
|
} else if ((numSamplesPerFrame / reverseUpdateIndex) >=
|
|
gAudioContext.audioBufferParameters.numSamplesPerUpdateMax) {
|
|
numSamplesPerUpdate = gAudioContext.audioBufferParameters.numSamplesPerUpdateMax;
|
|
} else if ((numSamplesPerFrame / reverseUpdateIndex) <=
|
|
gAudioContext.audioBufferParameters.numSamplesPerUpdateMin) {
|
|
numSamplesPerUpdate = gAudioContext.audioBufferParameters.numSamplesPerUpdateMin;
|
|
} else {
|
|
numSamplesPerUpdate = gAudioContext.audioBufferParameters.numSamplesPerUpdate;
|
|
}
|
|
|
|
for (reverbIndex = 0; reverbIndex < gAudioContext.numSynthesisReverbs; reverbIndex++) {
|
|
if (gAudioContext.synthesisReverbs[reverbIndex].useReverb) {
|
|
AudioSynth_AddReverbBufferEntry(
|
|
numSamplesPerUpdate, gAudioContext.audioBufferParameters.updatesPerFrame - reverseUpdateIndex,
|
|
reverbIndex);
|
|
}
|
|
}
|
|
|
|
curCmd = AudioSynth_ProcessSamples(curAiBufPos, numSamplesPerUpdate, curCmd,
|
|
gAudioContext.audioBufferParameters.updatesPerFrame - reverseUpdateIndex);
|
|
numSamplesPerFrame -= numSamplesPerUpdate;
|
|
curAiBufPos += numSamplesPerUpdate * SAMPLE_SIZE;
|
|
}
|
|
|
|
// Update reverb frame info
|
|
for (reverbIndex = 0; reverbIndex < gAudioContext.numSynthesisReverbs; reverbIndex++) {
|
|
if (gAudioContext.synthesisReverbs[reverbIndex].framesToIgnore != 0) {
|
|
gAudioContext.synthesisReverbs[reverbIndex].framesToIgnore--;
|
|
}
|
|
gAudioContext.synthesisReverbs[reverbIndex].curFrame ^= 1;
|
|
}
|
|
|
|
*numAbiCmds = curCmd - abiCmdStart;
|
|
return curCmd;
|
|
}
|
|
|
|
void AudioSynth_DisableSampleStates(s32 updateIndex, s32 noteIndex) {
|
|
NoteSampleState* sampleState;
|
|
s32 i;
|
|
|
|
for (i = updateIndex + 1; i < gAudioContext.audioBufferParameters.updatesPerFrame; i++) {
|
|
sampleState = &gAudioContext.sampleStateList[(gAudioContext.numNotes * i) + noteIndex];
|
|
if (sampleState->bitField0.needsInit) {
|
|
break;
|
|
}
|
|
sampleState->bitField0.enabled = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load reverb samples from a different reverb index
|
|
*/
|
|
Acmd* AudioSynth_LoadMixedReverbSamples(Acmd* cmd, SynthesisReverb* reverb, s16 updateIndex) {
|
|
ReverbBufferEntry* entry = &reverb->bufEntry[reverb->curFrame][updateIndex];
|
|
|
|
cmd = AudioSynth_LoadReverbSamplesImpl(cmd, DMEM_WET_TEMP, entry->startPos, entry->size, reverb);
|
|
if (entry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd = AudioSynth_LoadReverbSamplesImpl(cmd, DMEM_WET_TEMP + entry->size, 0, entry->wrappedSize, reverb);
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
* Save reverb samples from a different reverb index
|
|
*/
|
|
Acmd* AudioSynth_SaveMixedReverbSamples(Acmd* cmd, SynthesisReverb* reverb, s16 updateIndex) {
|
|
ReverbBufferEntry* entry = &reverb->bufEntry[reverb->curFrame][updateIndex];
|
|
|
|
cmd = AudioSynth_SaveReverbSamplesImpl(cmd, DMEM_WET_TEMP, entry->startPos, entry->size, reverb);
|
|
if (entry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd = AudioSynth_SaveReverbSamplesImpl(cmd, DMEM_WET_TEMP + entry->size, 0, entry->wrappedSize, reverb);
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
void AudioSynth_Noop1(void) {
|
|
}
|
|
|
|
void AudioSynth_ClearBuffer(Acmd* cmd, s32 dmem, s32 size) {
|
|
aClearBuffer(cmd, dmem, size);
|
|
}
|
|
|
|
void AudioSynth_Noop2(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop3(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop4(void) {
|
|
}
|
|
|
|
void AudioSynth_Mix(Acmd* cmd, size_t size, s32 gain, s32 dmemIn, s32 dmemOut) {
|
|
aMix(cmd, size, gain, dmemIn, dmemOut);
|
|
}
|
|
|
|
void AudioSynth_Noop5(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop6(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop7(void) {
|
|
}
|
|
|
|
void AudioSynth_SetBuffer(Acmd* cmd, s32 flags, s32 dmemIn, s32 dmemOut, size_t size) {
|
|
aSetBuffer(cmd, flags, dmemIn, dmemOut, size);
|
|
}
|
|
|
|
void AudioSynth_Noop8(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop9(void) {
|
|
}
|
|
|
|
void AudioSynth_DMemMove(Acmd* cmd, s32 dmemIn, s32 dmemOut, size_t size) {
|
|
// aDMEMMove(cmd, dmemIn, dmemOut, size);
|
|
cmd->words.w0 = _SHIFTL(A_DMEMMOVE, 24, 8) | _SHIFTL(dmemIn, 0, 24);
|
|
cmd->words.w1 = _SHIFTL(dmemOut, 16, 16) | _SHIFTL(size, 0, 16);
|
|
}
|
|
|
|
void AudioSynth_Noop10(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop11(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop12(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop13(void) {
|
|
}
|
|
|
|
void AudioSynth_InterL(Acmd* cmd, s32 dmemIn, s32 dmemOut, s32 numSamples) {
|
|
// aInterl(cmd, dmemIn, dmemOut, numSamples);
|
|
cmd->words.w0 = _SHIFTL(A_INTERL, 24, 8) | _SHIFTL(numSamples, 0, 16);
|
|
cmd->words.w1 = _SHIFTL(dmemIn, 16, 16) | _SHIFTL(dmemOut, 0, 16);
|
|
}
|
|
|
|
void AudioSynth_EnvSetup1(Acmd* cmd, s32 reverbVol, s32 rampReverb, s32 rampLeft, s32 rampRight) {
|
|
aEnvSetup1(cmd, reverbVol, rampReverb, rampLeft, rampRight);
|
|
}
|
|
|
|
void AudioSynth_Noop14(void) {
|
|
}
|
|
|
|
void AudioSynth_LoadBuffer(Acmd* cmd, s32 dmemDest, s32 size, void* addrSrc) {
|
|
aLoadBuffer(cmd, addrSrc, dmemDest, size);
|
|
}
|
|
|
|
void AudioSynth_SaveBuffer(Acmd* cmd, s32 dmemSrc, s32 size, void* addrDest) {
|
|
aSaveBuffer(cmd, dmemSrc, addrDest, size);
|
|
}
|
|
|
|
void AudioSynth_EnvSetup2(Acmd* cmd, s32 volLeft, s32 volRight) {
|
|
// aEnvSetup2(cmd, volLeft, volRight);
|
|
cmd->words.w0 = _SHIFTL(A_ENVSETUP2, 24, 8);
|
|
cmd->words.w1 = _SHIFTL(volLeft, 16, 16) | _SHIFTL(volRight, 0, 16);
|
|
}
|
|
|
|
void AudioSynth_Noop15(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop16(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop17(void) {
|
|
}
|
|
|
|
void AudioSynth_S8Dec(Acmd* cmd, s32 flags, s16* state) {
|
|
aS8Dec(cmd, flags, state);
|
|
}
|
|
|
|
void AudioSynth_HiLoGain(Acmd* cmd, s32 gain, s32 dmemIn, s32 dmemOut, s32 size) {
|
|
// aHiLoGain(cmd, gain, size, dmemIn, dmemOut);
|
|
cmd->words.w0 = _SHIFTL(A_HILOGAIN, 24, 8) | _SHIFTL(gain, 16, 8) | _SHIFTL(size, 0, 16);
|
|
cmd->words.w1 = _SHIFTL(dmemIn, 16, 16) | _SHIFTL(dmemOut, 0, 16);
|
|
}
|
|
|
|
// Remnant of OoT
|
|
void AudioSynth_UnkCmd19(Acmd* cmd, s32 dmem1, s32 dmem2, s32 size, s32 arg4) {
|
|
cmd->words.w0 = _SHIFTL(A_SPNOOP, 24, 8) | _SHIFTL(arg4, 16, 8) | _SHIFTL(size, 0, 16);
|
|
cmd->words.w1 = _SHIFTL(dmem1, 16, 16) | _SHIFTL(dmem2, 0, 16);
|
|
}
|
|
|
|
void AudioSynth_Noop18(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop19(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop20(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop21(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop22(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop23(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop24(void) {
|
|
}
|
|
|
|
void AudioSynth_Noop25(void) {
|
|
}
|
|
|
|
void AudioSynth_LoadFilterBuffer(Acmd* cmd, s32 flags, s32 buf, s16* addr) {
|
|
aFilter(cmd, flags, buf, addr);
|
|
}
|
|
|
|
void AudioSynth_LoadFilterSize(Acmd* cmd, size_t size, s16* addr) {
|
|
aFilter(cmd, 2, size, addr);
|
|
}
|
|
|
|
/**
|
|
* Leak some audio from the left reverb channel into the right reverb channel and vice versa (pan)
|
|
*/
|
|
Acmd* AudioSynth_LeakReverb(Acmd* cmd, SynthesisReverb* reverb) {
|
|
aDMEMMove(cmd++, DMEM_WET_LEFT_CH, DMEM_WET_SCRATCH, DMEM_1CH_SIZE);
|
|
aMix(cmd++, DMEM_1CH_SIZE >> 4, reverb->leakRtl, DMEM_WET_RIGHT_CH, DMEM_WET_LEFT_CH);
|
|
aMix(cmd++, DMEM_1CH_SIZE >> 4, reverb->leakLtr, DMEM_WET_SCRATCH, DMEM_WET_RIGHT_CH);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_LoadDownsampledReverbSamples(Acmd* cmd, s32 numSamplesPerUpdate, SynthesisReverb* reverb,
|
|
s16 updateIndex) {
|
|
ReverbBufferEntry* entry = &reverb->bufEntry[reverb->curFrame][updateIndex];
|
|
s16 offsetSize = (entry->startPos & 7) * SAMPLE_SIZE;
|
|
s16 wrappedOffsetSize = ALIGN16(offsetSize + entry->size);
|
|
|
|
cmd = AudioSynth_LoadReverbSamplesImpl(cmd, DMEM_WET_TEMP, entry->startPos - (offsetSize / (s32)SAMPLE_SIZE),
|
|
DMEM_1CH_SIZE, reverb);
|
|
|
|
if (entry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd = AudioSynth_LoadReverbSamplesImpl(cmd, DMEM_WET_TEMP + wrappedOffsetSize, 0,
|
|
DMEM_1CH_SIZE - wrappedOffsetSize, reverb);
|
|
}
|
|
|
|
aSetBuffer(cmd++, 0, DMEM_WET_TEMP + offsetSize, DMEM_WET_LEFT_CH, numSamplesPerUpdate * SAMPLE_SIZE);
|
|
aResample(cmd++, reverb->resampleFlags, reverb->downsamplePitch, reverb->leftLoadResampleBuf);
|
|
aSetBuffer(cmd++, 0, DMEM_WET_TEMP + DMEM_1CH_SIZE + offsetSize, DMEM_WET_RIGHT_CH,
|
|
numSamplesPerUpdate * SAMPLE_SIZE);
|
|
aResample(cmd++, reverb->resampleFlags, reverb->downsamplePitch, reverb->rightLoadResampleBuf);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_SaveResampledReverbSamples(Acmd* cmd, SynthesisReverb* reverb, s16 updateIndex) {
|
|
ReverbBufferEntry* entry = &reverb->bufEntry[reverb->curFrame][updateIndex];
|
|
s16 numSamples = entry->numSamples;
|
|
u32 size = numSamples * SAMPLE_SIZE;
|
|
|
|
// Left Resample
|
|
aDMEMMove(cmd++, DMEM_WET_LEFT_CH, DMEM_WET_TEMP, size);
|
|
aSetBuffer(cmd++, 0, DMEM_WET_TEMP, DMEM_WET_SCRATCH, entry->saveResampleNumSamples * SAMPLE_SIZE);
|
|
aResample(cmd++, reverb->resampleFlags, entry->saveResamplePitch, reverb->leftSaveResampleBuf);
|
|
|
|
cmd = AudioSynth_SaveResampledReverbSamplesImpl(cmd, DMEM_WET_SCRATCH, entry->size,
|
|
&reverb->leftReverbBuf[entry->startPos]);
|
|
|
|
if (entry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd = AudioSynth_SaveResampledReverbSamplesImpl(cmd, entry->size + DMEM_WET_SCRATCH, entry->wrappedSize,
|
|
reverb->leftReverbBuf);
|
|
}
|
|
|
|
// Right Resample
|
|
aDMEMMove(cmd++, DMEM_WET_RIGHT_CH, DMEM_WET_TEMP, size);
|
|
aSetBuffer(cmd++, 0, DMEM_WET_TEMP, DMEM_WET_SCRATCH, entry->saveResampleNumSamples * SAMPLE_SIZE);
|
|
aResample(cmd++, reverb->resampleFlags, entry->saveResamplePitch, reverb->rightSaveResampleBuf);
|
|
|
|
cmd = AudioSynth_SaveResampledReverbSamplesImpl(cmd, DMEM_WET_SCRATCH, entry->size,
|
|
&reverb->rightReverbBuf[entry->startPos]);
|
|
|
|
if (entry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd = AudioSynth_SaveResampledReverbSamplesImpl(cmd, entry->size + DMEM_WET_SCRATCH, entry->wrappedSize,
|
|
reverb->rightReverbBuf);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_LoadResampledReverbSamples(Acmd* cmd, s32 numSamplesPerUpdate, SynthesisReverb* reverb,
|
|
s16 updateIndex) {
|
|
ReverbBufferEntry* entry = &reverb->bufEntry[reverb->curFrame][updateIndex];
|
|
s16 offsetSize = (entry->startPos & 7) * SAMPLE_SIZE;
|
|
s16 wrappedOffsetSize = ALIGN16(offsetSize + entry->size);
|
|
|
|
cmd = AudioSynth_LoadReverbSamplesImpl(cmd, DMEM_WET_TEMP, entry->startPos - (offsetSize / (s32)SAMPLE_SIZE),
|
|
DMEM_1CH_SIZE, reverb);
|
|
|
|
if (entry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd = AudioSynth_LoadReverbSamplesImpl(cmd, wrappedOffsetSize + DMEM_WET_TEMP, 0,
|
|
DMEM_1CH_SIZE - wrappedOffsetSize, reverb);
|
|
}
|
|
|
|
aSetBuffer(cmd++, 0, DMEM_WET_TEMP + offsetSize, DMEM_WET_LEFT_CH, numSamplesPerUpdate * SAMPLE_SIZE);
|
|
aResample(cmd++, reverb->resampleFlags, entry->loadResamplePitch, reverb->leftLoadResampleBuf);
|
|
aSetBuffer(cmd++, 0, DMEM_WET_TEMP + DMEM_1CH_SIZE + offsetSize, DMEM_WET_RIGHT_CH,
|
|
numSamplesPerUpdate * SAMPLE_SIZE);
|
|
aResample(cmd++, reverb->resampleFlags, entry->loadResamplePitch, reverb->rightLoadResampleBuf);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
* Apply a filter (convolution) to each reverb channel.
|
|
*/
|
|
Acmd* AudioSynth_FilterReverb(Acmd* cmd, s32 size, SynthesisReverb* reverb) {
|
|
if (reverb->filterLeft != NULL) {
|
|
aFilter(cmd++, 2, size, reverb->filterLeft);
|
|
aFilter(cmd++, reverb->resampleFlags, DMEM_WET_LEFT_CH, reverb->filterLeftState);
|
|
}
|
|
|
|
if (reverb->filterRight != NULL) {
|
|
aFilter(cmd++, 2, size, reverb->filterRight);
|
|
aFilter(cmd++, reverb->resampleFlags, DMEM_WET_RIGHT_CH, reverb->filterRightState);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
* Mix in reverb from a different reverb index
|
|
*/
|
|
Acmd* AudioSynth_MixOtherReverbIndex(Acmd* cmd, SynthesisReverb* reverb, s32 updateIndex) {
|
|
SynthesisReverb* mixReverb;
|
|
|
|
if (reverb->mixReverbIndex >= gAudioContext.numSynthesisReverbs) {
|
|
return cmd;
|
|
}
|
|
|
|
mixReverb = &gAudioContext.synthesisReverbs[reverb->mixReverbIndex];
|
|
if (mixReverb->downsampleRate == 1) {
|
|
cmd = AudioSynth_LoadMixedReverbSamples(cmd, mixReverb, updateIndex);
|
|
aMix(cmd++, DMEM_2CH_SIZE >> 4, reverb->mixReverbStrength, DMEM_WET_LEFT_CH, DMEM_WET_TEMP);
|
|
cmd = AudioSynth_SaveMixedReverbSamples(cmd, mixReverb, updateIndex);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_LoadDefaultReverbSamples(Acmd* cmd, s32 numSamplesPerUpdate, SynthesisReverb* reverb,
|
|
s16 updateIndex) {
|
|
ReverbBufferEntry* entry = &reverb->bufEntry[reverb->curFrame][updateIndex];
|
|
|
|
cmd = AudioSynth_LoadReverbSamplesImpl(cmd, DMEM_WET_LEFT_CH, entry->startPos, entry->size, reverb);
|
|
if (entry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd = AudioSynth_LoadReverbSamplesImpl(cmd, DMEM_WET_LEFT_CH + entry->size, 0, entry->wrappedSize, reverb);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_LoadSubReverbSamples(Acmd* cmd, s32 numSamplesPerUpdate, SynthesisReverb* reverb, s16 updateIndex) {
|
|
ReverbBufferEntry* subEntry = &reverb->subBufEntry[reverb->curFrame][updateIndex];
|
|
|
|
cmd = AudioSynth_LoadReverbSamplesImpl(cmd, DMEM_WET_LEFT_CH, subEntry->startPos, subEntry->size, reverb);
|
|
if (subEntry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd =
|
|
AudioSynth_LoadReverbSamplesImpl(cmd, DMEM_WET_LEFT_CH + subEntry->size, 0, subEntry->wrappedSize, reverb);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_SaveResampledReverbSamplesImpl(Acmd* cmd, u16 dmem, u16 size, uintptr_t startAddr) {
|
|
s32 startAddrAlignDropped;
|
|
u32 endAddr;
|
|
s32 endAddrAlignDropped;
|
|
|
|
endAddr = startAddr + size;
|
|
|
|
endAddrAlignDropped = endAddr & 0xF;
|
|
if (endAddrAlignDropped != 0) {
|
|
aLoadBuffer(cmd++, (endAddr - endAddrAlignDropped), DMEM_TEMP, 0x10);
|
|
aDMEMMove(cmd++, dmem, DMEM_TEMP2, size);
|
|
aDMEMMove(cmd++, DMEM_TEMP + endAddrAlignDropped, size + DMEM_TEMP2, 0x10 - endAddrAlignDropped);
|
|
|
|
size += (0x10 - endAddrAlignDropped);
|
|
dmem = DMEM_TEMP2;
|
|
}
|
|
|
|
startAddrAlignDropped = startAddr & 0xF;
|
|
if (startAddrAlignDropped != 0) {
|
|
aLoadBuffer(cmd++, startAddr - startAddrAlignDropped, DMEM_TEMP, 0x10);
|
|
aDMEMMove(cmd++, dmem, startAddrAlignDropped + DMEM_TEMP, size);
|
|
|
|
size += startAddrAlignDropped;
|
|
dmem = DMEM_TEMP;
|
|
}
|
|
|
|
aSaveBuffer(cmd++, dmem, startAddr - startAddrAlignDropped, size);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_LoadReverbSamplesImpl(Acmd* cmd, u16 dmem, u16 startPos, s32 size, SynthesisReverb* reverb) {
|
|
aLoadBuffer(cmd++, &reverb->leftReverbBuf[startPos], dmem, size);
|
|
aLoadBuffer(cmd++, &reverb->rightReverbBuf[startPos], dmem + DMEM_1CH_SIZE, size);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_SaveReverbSamplesImpl(Acmd* cmd, u16 dmem, u16 startPos, s32 size, SynthesisReverb* reverb) {
|
|
aSaveBuffer(cmd++, dmem, &reverb->leftReverbBuf[startPos], size);
|
|
aSaveBuffer(cmd++, dmem + DMEM_1CH_SIZE, &reverb->rightReverbBuf[startPos], size);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
void AudioSynth_Noop26(void) {
|
|
}
|
|
|
|
Acmd* AudioSynth_LoadSubReverbSamplesWithoutDownsample(Acmd* cmd, s32 numSamplesPerUpdate, SynthesisReverb* reverb,
|
|
s16 updateIndex) {
|
|
if (reverb->downsampleRate == 1) {
|
|
cmd = AudioSynth_LoadSubReverbSamples(cmd, numSamplesPerUpdate, reverb, updateIndex);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_LoadReverbSamples(Acmd* cmd, s32 numSamplesPerUpdate, SynthesisReverb* reverb, s16 updateIndex) {
|
|
if (reverb->downsampleRate == 1) {
|
|
if (reverb->resampleEffectOn) {
|
|
cmd = AudioSynth_LoadResampledReverbSamples(cmd, numSamplesPerUpdate, reverb, updateIndex);
|
|
} else {
|
|
cmd = AudioSynth_LoadDefaultReverbSamples(cmd, numSamplesPerUpdate, reverb, updateIndex);
|
|
}
|
|
} else {
|
|
cmd = AudioSynth_LoadDownsampledReverbSamples(cmd, numSamplesPerUpdate, reverb, updateIndex);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_SaveReverbSamples(Acmd* cmd, SynthesisReverb* reverb, s16 updateIndex) {
|
|
ReverbBufferEntry* entry = &reverb->bufEntry[reverb->curFrame][updateIndex];
|
|
s32 downsampleRate;
|
|
s32 numSamples;
|
|
|
|
if (reverb->downsampleRate == 1) {
|
|
if (reverb->resampleEffectOn) {
|
|
cmd = AudioSynth_SaveResampledReverbSamples(cmd, reverb, updateIndex);
|
|
} else {
|
|
// Put the oldest samples in the ring buffer into the wet channels
|
|
cmd = AudioSynth_SaveReverbSamplesImpl(cmd, DMEM_WET_LEFT_CH, entry->startPos, entry->size, reverb);
|
|
if (entry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd = AudioSynth_SaveReverbSamplesImpl(cmd, DMEM_WET_LEFT_CH + entry->size, 0, entry->wrappedSize,
|
|
reverb);
|
|
}
|
|
}
|
|
} else {
|
|
//! FAKE:
|
|
if (1) {}
|
|
|
|
downsampleRate = reverb->downsampleRate;
|
|
numSamples = 13 * SAMPLES_PER_FRAME;
|
|
|
|
while (downsampleRate >= 2) {
|
|
aInterl(cmd++, DMEM_WET_LEFT_CH, DMEM_WET_LEFT_CH, numSamples);
|
|
aInterl(cmd++, DMEM_WET_RIGHT_CH, DMEM_WET_RIGHT_CH, numSamples);
|
|
downsampleRate >>= 1;
|
|
numSamples >>= 1;
|
|
}
|
|
|
|
if (entry->size != 0) {
|
|
cmd = AudioSynth_SaveResampledReverbSamplesImpl(cmd, DMEM_WET_LEFT_CH, entry->size,
|
|
&reverb->leftReverbBuf[entry->startPos]);
|
|
cmd = AudioSynth_SaveResampledReverbSamplesImpl(cmd, DMEM_WET_RIGHT_CH, entry->size,
|
|
&reverb->rightReverbBuf[entry->startPos]);
|
|
}
|
|
|
|
if (entry->wrappedSize != 0) {
|
|
cmd = AudioSynth_SaveResampledReverbSamplesImpl(cmd, entry->size + DMEM_WET_LEFT_CH, entry->wrappedSize,
|
|
reverb->leftReverbBuf);
|
|
cmd = AudioSynth_SaveResampledReverbSamplesImpl(cmd, entry->size + DMEM_WET_RIGHT_CH, entry->wrappedSize,
|
|
reverb->rightReverbBuf);
|
|
}
|
|
}
|
|
|
|
reverb->resampleFlags = 0;
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_SaveSubReverbSamples(Acmd* cmd, SynthesisReverb* reverb, s16 updateIndex) {
|
|
ReverbBufferEntry* subEntry = &reverb->subBufEntry[reverb->curFrame][updateIndex];
|
|
|
|
cmd = AudioSynth_SaveReverbSamplesImpl(cmd, DMEM_WET_LEFT_CH, subEntry->startPos, subEntry->size, reverb);
|
|
if (subEntry->wrappedSize != 0) {
|
|
// Ring buffer wrapped
|
|
cmd =
|
|
AudioSynth_SaveReverbSamplesImpl(cmd, DMEM_WET_LEFT_CH + subEntry->size, 0, subEntry->wrappedSize, reverb);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
* Process all samples embedded in a note. Every sample has numSamplesPerUpdate processed,
|
|
* and each of those are mixed together into both DMEM_LEFT_CH and DMEM_RIGHT_CH
|
|
*/
|
|
Acmd* AudioSynth_ProcessSamples(s16* aiBuf, s32 numSamplesPerUpdate, Acmd* cmd, s32 updateIndex) {
|
|
s32 size;
|
|
u8 noteIndices[0x58];
|
|
s16 noteCount = 0;
|
|
s16 reverbIndex;
|
|
SynthesisReverb* reverb;
|
|
s32 useReverb;
|
|
s32 sampleStateOffset = gAudioContext.numNotes * updateIndex;
|
|
s32 i;
|
|
|
|
if (gAudioContext.numSynthesisReverbs == 0) {
|
|
for (i = 0; i < gAudioContext.numNotes; i++) {
|
|
if (gAudioContext.sampleStateList[sampleStateOffset + i].bitField0.enabled) {
|
|
noteIndices[noteCount++] = i;
|
|
}
|
|
}
|
|
} else {
|
|
NoteSampleState* sampleState;
|
|
|
|
for (reverbIndex = 0; reverbIndex < gAudioContext.numSynthesisReverbs; reverbIndex++) {
|
|
for (i = 0; i < gAudioContext.numNotes; i++) {
|
|
sampleState = &gAudioContext.sampleStateList[sampleStateOffset + i];
|
|
if (sampleState->bitField0.enabled && (sampleState->bitField1.reverbIndex == reverbIndex)) {
|
|
noteIndices[noteCount++] = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < gAudioContext.numNotes; i++) {
|
|
sampleState = &gAudioContext.sampleStateList[sampleStateOffset + i];
|
|
if (sampleState->bitField0.enabled &&
|
|
(sampleState->bitField1.reverbIndex >= gAudioContext.numSynthesisReverbs)) {
|
|
noteIndices[noteCount++] = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
aClearBuffer(cmd++, DMEM_LEFT_CH, DMEM_2CH_SIZE);
|
|
|
|
i = 0;
|
|
for (reverbIndex = 0; reverbIndex < gAudioContext.numSynthesisReverbs; reverbIndex++) {
|
|
s32 subDelay;
|
|
NoteSampleState* sampleState;
|
|
|
|
reverb = &gAudioContext.synthesisReverbs[reverbIndex];
|
|
useReverb = reverb->useReverb;
|
|
if (useReverb) {
|
|
|
|
// Loads reverb samples from DRAM (ringBuffer) into DMEM (DMEM_WET_LEFT_CH)
|
|
cmd = AudioSynth_LoadReverbSamples(cmd, numSamplesPerUpdate, reverb, updateIndex);
|
|
|
|
// Mixes reverb sample into the main dry channel
|
|
// reverb->volume is always set to 0x7FFF (audio spec), and DMEM_LEFT_CH is cleared before reverbs.
|
|
// So this is essentially a DMEMmove from DMEM_WET_LEFT_CH to DMEM_LEFT_CH
|
|
aMix(cmd++, DMEM_2CH_SIZE >> 4, reverb->volume, DMEM_WET_LEFT_CH, DMEM_LEFT_CH);
|
|
|
|
subDelay = reverb->subDelay;
|
|
if (subDelay != 0) {
|
|
aDMEMMove(cmd++, DMEM_WET_LEFT_CH, DMEM_WET_TEMP, DMEM_2CH_SIZE);
|
|
}
|
|
|
|
// Decays reverb over time. The (+ 0x8000) here is -100%
|
|
aMix(cmd++, DMEM_2CH_SIZE >> 4, reverb->decayRatio + 0x8000, DMEM_WET_LEFT_CH, DMEM_WET_LEFT_CH);
|
|
|
|
if (((reverb->leakRtl != 0) || (reverb->leakLtr != 0)) && (gAudioContext.soundMode != SOUNDMODE_MONO)) {
|
|
cmd = AudioSynth_LeakReverb(cmd, reverb);
|
|
}
|
|
|
|
if (subDelay != 0) {
|
|
if (reverb->mixReverbIndex != REVERB_INDEX_NONE) {
|
|
cmd = AudioSynth_MixOtherReverbIndex(cmd, reverb, updateIndex);
|
|
}
|
|
cmd = AudioSynth_SaveReverbSamples(cmd, reverb, updateIndex);
|
|
cmd = AudioSynth_LoadSubReverbSamplesWithoutDownsample(cmd, numSamplesPerUpdate, reverb, updateIndex);
|
|
aMix(cmd++, DMEM_2CH_SIZE >> 4, reverb->subVolume, DMEM_WET_TEMP, DMEM_WET_LEFT_CH);
|
|
}
|
|
}
|
|
|
|
while (i < noteCount) {
|
|
sampleState = &gAudioContext.sampleStateList[sampleStateOffset + noteIndices[i]];
|
|
if (sampleState->bitField1.reverbIndex != reverbIndex) {
|
|
break;
|
|
}
|
|
cmd = AudioSynth_ProcessSample(noteIndices[i], sampleState,
|
|
&gAudioContext.notes[noteIndices[i]].synthesisState, aiBuf,
|
|
numSamplesPerUpdate, cmd, updateIndex);
|
|
i++;
|
|
}
|
|
|
|
if (useReverb) {
|
|
if ((reverb->filterLeft != NULL) || (reverb->filterRight != NULL)) {
|
|
cmd = AudioSynth_FilterReverb(cmd, numSamplesPerUpdate * SAMPLE_SIZE, reverb);
|
|
}
|
|
|
|
// Saves the wet channel sample from DMEM (DMEM_WET_LEFT_CH) into (ringBuffer) DRAM for future use
|
|
if (subDelay != 0) {
|
|
cmd = AudioSynth_SaveSubReverbSamples(cmd, reverb, updateIndex);
|
|
} else {
|
|
if (reverb->mixReverbIndex != REVERB_INDEX_NONE) {
|
|
cmd = AudioSynth_MixOtherReverbIndex(cmd, reverb, updateIndex);
|
|
}
|
|
cmd = AudioSynth_SaveReverbSamples(cmd, reverb, updateIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (i < noteCount) {
|
|
cmd = AudioSynth_ProcessSample(
|
|
noteIndices[i], &gAudioContext.sampleStateList[sampleStateOffset + noteIndices[i]],
|
|
&gAudioContext.notes[noteIndices[i]].synthesisState, aiBuf, numSamplesPerUpdate, cmd, updateIndex);
|
|
i++;
|
|
}
|
|
|
|
size = numSamplesPerUpdate * SAMPLE_SIZE;
|
|
aInterleave(cmd++, DMEM_TEMP, DMEM_LEFT_CH, DMEM_RIGHT_CH, size);
|
|
|
|
if (gCustomAudioSynthFunction != NULL) {
|
|
cmd = gCustomAudioSynthFunction(cmd, 2 * size, updateIndex);
|
|
}
|
|
aSaveBuffer(cmd++, DMEM_TEMP, aiBuf, 2 * size);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, NoteSynthesisState* synthState, s16* aiBuf,
|
|
s32 numSamplesPerUpdate, Acmd* cmd, s32 updateIndex) {
|
|
s32 pad1[2];
|
|
void* reverbAddrSrc;
|
|
Sample* sample;
|
|
AdpcmLoop* loopInfo;
|
|
s32 numSamplesUntilEnd;
|
|
s32 numSamplesInThisIteration;
|
|
s32 sampleFinished;
|
|
s32 loopToPoint;
|
|
s32 flags;
|
|
u16 frequencyFixedPoint;
|
|
s32 gain;
|
|
s32 frameIndex;
|
|
s32 skipBytes;
|
|
void* combFilterState;
|
|
s32 numSamplesToDecode;
|
|
s32 numFirstFrameSamplesToIgnore;
|
|
u8* sampleAddr;
|
|
u32 numSamplesToLoadFixedPoint;
|
|
s32 numSamplesToLoadAdj;
|
|
s32 numSamplesProcessed;
|
|
s32 sampleEndPos;
|
|
s32 numSamplesToProcess;
|
|
s32 dmemUncompressedAddrOffset2;
|
|
s32 pad2[3];
|
|
s32 numSamplesInFirstFrame;
|
|
s32 numTrailingSamplesToIgnore;
|
|
s32 pad3[3];
|
|
s32 frameSize;
|
|
s32 numFramesToDecode;
|
|
s32 skipInitialSamples;
|
|
s32 zeroOffset;
|
|
u8* samplesToLoadAddr;
|
|
s32 numParts;
|
|
s32 curPart;
|
|
s32 sampleDataChunkAlignPad;
|
|
s32 haasEffectDelaySide;
|
|
s32 numSamplesToLoadFirstPart;
|
|
u16 sampleDmemBeforeResampling;
|
|
s32 sampleAddrOffset;
|
|
s32 combFilterDmem;
|
|
s32 dmemUncompressedAddrOffset1;
|
|
Note* note;
|
|
u32 numSamplesToLoad;
|
|
u16 combFilterSize;
|
|
u16 combFilterGain;
|
|
s16* filter;
|
|
s32 bookOffset = sampleState->bitField1.bookOffset;
|
|
s32 finished = sampleState->bitField0.finished;
|
|
s32 sampleDataChunkSize;
|
|
s16 sampleDataDmemAddr;
|
|
|
|
note = &gAudioContext.notes[noteIndex];
|
|
flags = A_CONTINUE;
|
|
|
|
// Initialize the synthesis state
|
|
if (sampleState->bitField0.needsInit == true) {
|
|
flags = A_INIT;
|
|
synthState->atLoopPoint = false;
|
|
synthState->stopLoop = false;
|
|
synthState->samplePosInt = note->playbackState.startSamplePos;
|
|
synthState->samplePosFrac = 0;
|
|
synthState->curVolLeft = 0;
|
|
synthState->curVolRight = 0;
|
|
synthState->prevHaasEffectLeftDelaySize = 0;
|
|
synthState->prevHaasEffectRightDelaySize = 0;
|
|
synthState->curReverbVol = sampleState->targetReverbVol;
|
|
synthState->numParts = 0;
|
|
synthState->combFilterNeedsInit = true;
|
|
note->sampleState.bitField0.finished = false;
|
|
synthState->unk_1F = note->playbackState.unk_80; // Never set, never used
|
|
finished = false;
|
|
}
|
|
|
|
// Process the sample in either one or two parts
|
|
numParts = sampleState->bitField1.hasTwoParts + 1;
|
|
|
|
// Determine number of samples to load based on numSamplesPerUpdate and relative frequency
|
|
frequencyFixedPoint = sampleState->frequencyFixedPoint;
|
|
numSamplesToLoadFixedPoint = (frequencyFixedPoint * numSamplesPerUpdate * 2) + synthState->samplePosFrac;
|
|
numSamplesToLoad = numSamplesToLoadFixedPoint >> 16;
|
|
|
|
if (numSamplesToLoad == 0) {
|
|
skipBytes = false;
|
|
}
|
|
|
|
synthState->samplePosFrac = numSamplesToLoadFixedPoint & 0xFFFF;
|
|
|
|
// Partially-optimized out no-op ifs required for matching. SM64 decomp
|
|
// makes it clear that this is how it should look.
|
|
if ((synthState->numParts == 1) && (numParts == 2)) {
|
|
} else if ((synthState->numParts == 2) && (numParts == 1)) {
|
|
} else {
|
|
}
|
|
|
|
synthState->numParts = numParts;
|
|
|
|
if (sampleState->bitField1.isSyntheticWave) {
|
|
cmd = AudioSynth_LoadWaveSamples(cmd, sampleState, synthState, numSamplesToLoad);
|
|
sampleDmemBeforeResampling = DMEM_UNCOMPRESSED_NOTE + (synthState->samplePosInt * 2);
|
|
synthState->samplePosInt += numSamplesToLoad;
|
|
} else {
|
|
sample = sampleState->tunedSample->sample;
|
|
loopInfo = sample->loop;
|
|
|
|
if (note->playbackState.status != PLAYBACK_STATUS_0) {
|
|
synthState->stopLoop = true;
|
|
}
|
|
|
|
if ((loopInfo->count == 2) && synthState->stopLoop) {
|
|
sampleEndPos = loopInfo->sampleEnd;
|
|
} else {
|
|
sampleEndPos = loopInfo->loopEnd;
|
|
}
|
|
|
|
sampleAddr = sample->sampleAddr;
|
|
numSamplesToLoadFirstPart = 0;
|
|
|
|
// If the frequency requested is more than double that of the raw sample,
|
|
// then the sample processing is split into two parts.
|
|
for (curPart = 0; curPart < numParts; curPart++) {
|
|
numSamplesProcessed = 0;
|
|
dmemUncompressedAddrOffset1 = 0;
|
|
|
|
// Adjust the number of samples to load only if there are two parts and an odd number of samples
|
|
if (numParts == 1) {
|
|
numSamplesToLoadAdj = numSamplesToLoad;
|
|
} else if (numSamplesToLoad & 1) {
|
|
// round down for the first part
|
|
// round up for the second part
|
|
numSamplesToLoadAdj = (numSamplesToLoad & ~1) + (curPart * 2);
|
|
} else {
|
|
numSamplesToLoadAdj = numSamplesToLoad;
|
|
}
|
|
|
|
// Load the ADPCM codeBook
|
|
if ((sample->codec == CODEC_ADPCM) || (sample->codec == CODEC_SMALL_ADPCM)) {
|
|
if (gAudioContext.adpcmCodeBook != sample->book->codeBook) {
|
|
u32 numEntries;
|
|
|
|
switch (bookOffset) {
|
|
case 1:
|
|
gAudioContext.adpcmCodeBook = &gInvalidAdpcmCodeBook[1];
|
|
break;
|
|
|
|
case 2:
|
|
case 3:
|
|
default:
|
|
gAudioContext.adpcmCodeBook = sample->book->codeBook;
|
|
break;
|
|
}
|
|
|
|
numEntries = SAMPLES_PER_FRAME * sample->book->order * sample->book->numPredictors;
|
|
aLoadADPCM(cmd++, numEntries, gAudioContext.adpcmCodeBook);
|
|
}
|
|
}
|
|
|
|
// Continue processing samples until the number of samples needed to load is reached
|
|
while (numSamplesProcessed != numSamplesToLoadAdj) {
|
|
sampleFinished = false;
|
|
loopToPoint = false;
|
|
dmemUncompressedAddrOffset2 = 0;
|
|
|
|
numFirstFrameSamplesToIgnore = synthState->samplePosInt & 0xF;
|
|
numSamplesUntilEnd = sampleEndPos - synthState->samplePosInt;
|
|
|
|
// Calculate number of samples to process this loop
|
|
numSamplesToProcess = numSamplesToLoadAdj - numSamplesProcessed;
|
|
|
|
if ((numFirstFrameSamplesToIgnore == 0) && !synthState->atLoopPoint) {
|
|
numFirstFrameSamplesToIgnore = SAMPLES_PER_FRAME;
|
|
}
|
|
numSamplesInFirstFrame = SAMPLES_PER_FRAME - numFirstFrameSamplesToIgnore;
|
|
|
|
// Determine the number of samples to decode based on whether the end will be reached or not.
|
|
if (numSamplesToProcess < numSamplesUntilEnd) {
|
|
// The end will not be reached.
|
|
numFramesToDecode =
|
|
(s32)(numSamplesToProcess - numSamplesInFirstFrame + SAMPLES_PER_FRAME - 1) / SAMPLES_PER_FRAME;
|
|
numSamplesToDecode = numFramesToDecode * SAMPLES_PER_FRAME;
|
|
numTrailingSamplesToIgnore = numSamplesInFirstFrame + numSamplesToDecode - numSamplesToProcess;
|
|
} else {
|
|
// The end will be reached.
|
|
numSamplesToDecode = numSamplesUntilEnd - numSamplesInFirstFrame;
|
|
numTrailingSamplesToIgnore = 0;
|
|
if (numSamplesToDecode <= 0) {
|
|
numSamplesToDecode = 0;
|
|
numSamplesInFirstFrame = numSamplesUntilEnd;
|
|
}
|
|
numFramesToDecode = (numSamplesToDecode + SAMPLES_PER_FRAME - 1) / SAMPLES_PER_FRAME;
|
|
if (loopInfo->count != 0) {
|
|
if ((loopInfo->count == 2) && synthState->stopLoop) {
|
|
sampleFinished = true;
|
|
} else {
|
|
// Loop around and restart
|
|
loopToPoint = true;
|
|
}
|
|
} else {
|
|
sampleFinished = true;
|
|
}
|
|
}
|
|
|
|
// Set parameters based on compression type
|
|
switch (sample->codec) {
|
|
case CODEC_ADPCM:
|
|
// 16 2-byte samples (32 bytes) compressed into 4-bit samples (8 bytes) + 1 header byte
|
|
frameSize = 9;
|
|
skipInitialSamples = SAMPLES_PER_FRAME;
|
|
zeroOffset = 0;
|
|
break;
|
|
|
|
case CODEC_SMALL_ADPCM:
|
|
// 16 2-byte samples (32 bytes) compressed into 2-bit samples (4 bytes) + 1 header byte
|
|
frameSize = 5;
|
|
skipInitialSamples = SAMPLES_PER_FRAME;
|
|
zeroOffset = 0;
|
|
break;
|
|
|
|
case CODEC_UNK7:
|
|
// 2 2-byte samples (4 bytes) processed without decompression
|
|
frameSize = 4;
|
|
skipInitialSamples = SAMPLES_PER_FRAME;
|
|
zeroOffset = 0;
|
|
break;
|
|
|
|
case CODEC_S8:
|
|
// 16 2-byte samples (32 bytes) compressed into 8-bit samples (16 bytes)
|
|
frameSize = 16;
|
|
skipInitialSamples = SAMPLES_PER_FRAME;
|
|
zeroOffset = 0;
|
|
break;
|
|
|
|
case CODEC_REVERB:
|
|
reverbAddrSrc = (void*)0xFFFFFFFF;
|
|
if (gCustomAudioReverbFunction != NULL) {
|
|
reverbAddrSrc = gCustomAudioReverbFunction(sample, numSamplesToLoadAdj, flags, noteIndex);
|
|
}
|
|
|
|
if (reverbAddrSrc == (void*)0xFFFFFFFF) {
|
|
sampleFinished = true;
|
|
} else if (reverbAddrSrc == NULL) {
|
|
return cmd;
|
|
} else {
|
|
AudioSynth_LoadBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE,
|
|
(numSamplesToLoadAdj + SAMPLES_PER_FRAME) * SAMPLE_SIZE,
|
|
reverbAddrSrc);
|
|
flags = A_CONTINUE;
|
|
skipBytes = 0;
|
|
numSamplesProcessed = numSamplesToLoadAdj;
|
|
dmemUncompressedAddrOffset1 = numSamplesToLoadAdj;
|
|
}
|
|
goto skip;
|
|
|
|
case CODEC_S16_INMEMORY:
|
|
case CODEC_UNK6:
|
|
AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE,
|
|
(numSamplesToLoadAdj + SAMPLES_PER_FRAME) * SAMPLE_SIZE);
|
|
flags = A_CONTINUE;
|
|
skipBytes = 0;
|
|
numSamplesProcessed = numSamplesToLoadAdj;
|
|
dmemUncompressedAddrOffset1 = numSamplesToLoadAdj;
|
|
goto skip;
|
|
|
|
case CODEC_S16:
|
|
AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE,
|
|
(numSamplesToLoadAdj + SAMPLES_PER_FRAME) * SAMPLE_SIZE);
|
|
flags = A_CONTINUE;
|
|
skipBytes = 0;
|
|
numSamplesProcessed = numSamplesToLoadAdj;
|
|
dmemUncompressedAddrOffset1 = numSamplesToLoadAdj;
|
|
goto skip;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Move the compressed raw sample data from ram into the rsp (DMEM)
|
|
if (numFramesToDecode != 0) {
|
|
// Get the offset from the start of the sample to where the sample is currently playing from
|
|
frameIndex = (synthState->samplePosInt + skipInitialSamples - numFirstFrameSamplesToIgnore) /
|
|
SAMPLES_PER_FRAME;
|
|
sampleAddrOffset = frameIndex * frameSize;
|
|
|
|
// Get the ram address of the requested sample chunk
|
|
if (sample->medium == MEDIUM_RAM) {
|
|
// Sample is already loaded into ram
|
|
samplesToLoadAddr = sampleAddr + (zeroOffset + sampleAddrOffset);
|
|
} else if (gAudioContext.unk_29B8) { // always false
|
|
return cmd;
|
|
} else if (sample->medium == MEDIUM_UNK) {
|
|
// This medium is unsupported so terminate processing this note
|
|
return cmd;
|
|
} else {
|
|
// This medium is not in ram, so dma the requested sample into ram
|
|
samplesToLoadAddr =
|
|
AudioLoad_DmaSampleData(sampleAddr + (zeroOffset + sampleAddrOffset),
|
|
ALIGN16((numFramesToDecode * frameSize) + SAMPLES_PER_FRAME), flags,
|
|
&synthState->sampleDmaIndex, sample->medium);
|
|
}
|
|
|
|
if (samplesToLoadAddr == NULL) {
|
|
// The ram address was unsuccessfully allocated
|
|
return cmd;
|
|
}
|
|
|
|
// Move the raw sample chunk from ram to the rsp
|
|
// DMEM at the addresses before DMEM_COMPRESSED_ADPCM_DATA
|
|
sampleDataChunkAlignPad = (u32)samplesToLoadAddr & 0xF;
|
|
sampleDataChunkSize = ALIGN16((numFramesToDecode * frameSize) + SAMPLES_PER_FRAME);
|
|
sampleDataDmemAddr = DMEM_COMPRESSED_ADPCM_DATA - sampleDataChunkSize;
|
|
aLoadBuffer(cmd++, samplesToLoadAddr - sampleDataChunkAlignPad, sampleDataDmemAddr,
|
|
sampleDataChunkSize);
|
|
} else {
|
|
numSamplesToDecode = 0;
|
|
sampleDataChunkAlignPad = 0;
|
|
}
|
|
|
|
if (synthState->atLoopPoint) {
|
|
aSetLoop(cmd++, sample->loop->predictorState);
|
|
flags = A_LOOP;
|
|
synthState->atLoopPoint = false;
|
|
}
|
|
|
|
numSamplesInThisIteration = numSamplesToDecode + numSamplesInFirstFrame - numTrailingSamplesToIgnore;
|
|
|
|
if (numSamplesProcessed == 0) {
|
|
//! FAKE:
|
|
if (1) {}
|
|
skipBytes = numFirstFrameSamplesToIgnore * SAMPLE_SIZE;
|
|
} else {
|
|
dmemUncompressedAddrOffset2 = ALIGN16(dmemUncompressedAddrOffset1 + 8 * SAMPLE_SIZE);
|
|
}
|
|
|
|
// Decompress the raw sample chunks in the rsp
|
|
// Goes from adpcm (compressed) sample data to pcm (uncompressed) sample data
|
|
switch (sample->codec) {
|
|
case CODEC_ADPCM:
|
|
sampleDataChunkSize = ALIGN16((numFramesToDecode * frameSize) + SAMPLES_PER_FRAME);
|
|
sampleDataDmemAddr = DMEM_COMPRESSED_ADPCM_DATA - sampleDataChunkSize;
|
|
aSetBuffer(cmd++, 0, sampleDataDmemAddr + sampleDataChunkAlignPad,
|
|
DMEM_UNCOMPRESSED_NOTE + dmemUncompressedAddrOffset2,
|
|
numSamplesToDecode * SAMPLE_SIZE);
|
|
aADPCMdec(cmd++, flags, synthState->synthesisBuffers->adpcmState);
|
|
break;
|
|
|
|
case CODEC_SMALL_ADPCM:
|
|
sampleDataChunkSize = ALIGN16((numFramesToDecode * frameSize) + SAMPLES_PER_FRAME);
|
|
sampleDataDmemAddr = DMEM_COMPRESSED_ADPCM_DATA - sampleDataChunkSize;
|
|
aSetBuffer(cmd++, 0, sampleDataDmemAddr + sampleDataChunkAlignPad,
|
|
DMEM_UNCOMPRESSED_NOTE + dmemUncompressedAddrOffset2,
|
|
numSamplesToDecode * SAMPLE_SIZE);
|
|
aADPCMdec(cmd++, flags | A_ADPCM_SHORT, synthState->synthesisBuffers->adpcmState);
|
|
break;
|
|
|
|
case CODEC_S8:
|
|
sampleDataChunkSize = ALIGN16((numFramesToDecode * frameSize) + SAMPLES_PER_FRAME);
|
|
sampleDataDmemAddr = DMEM_COMPRESSED_ADPCM_DATA - sampleDataChunkSize;
|
|
AudioSynth_SetBuffer(cmd++, 0, sampleDataDmemAddr + sampleDataChunkAlignPad,
|
|
DMEM_UNCOMPRESSED_NOTE + dmemUncompressedAddrOffset2,
|
|
numSamplesToDecode * SAMPLE_SIZE);
|
|
AudioSynth_S8Dec(cmd++, flags, synthState->synthesisBuffers->adpcmState);
|
|
break;
|
|
|
|
case CODEC_UNK7:
|
|
default:
|
|
// No decompression
|
|
break;
|
|
}
|
|
|
|
if (numSamplesProcessed != 0) {
|
|
aDMEMMove(cmd++,
|
|
DMEM_UNCOMPRESSED_NOTE + dmemUncompressedAddrOffset2 +
|
|
(numFirstFrameSamplesToIgnore * SAMPLE_SIZE),
|
|
DMEM_UNCOMPRESSED_NOTE + dmemUncompressedAddrOffset1,
|
|
numSamplesInThisIteration * SAMPLE_SIZE);
|
|
}
|
|
|
|
numSamplesProcessed += numSamplesInThisIteration;
|
|
|
|
switch (flags) {
|
|
case A_INIT:
|
|
skipBytes = SAMPLES_PER_FRAME * SAMPLE_SIZE;
|
|
dmemUncompressedAddrOffset1 = (numSamplesToDecode + SAMPLES_PER_FRAME) * SAMPLE_SIZE;
|
|
break;
|
|
|
|
case A_LOOP:
|
|
dmemUncompressedAddrOffset1 =
|
|
numSamplesInThisIteration * SAMPLE_SIZE + dmemUncompressedAddrOffset1;
|
|
break;
|
|
|
|
default:
|
|
if (dmemUncompressedAddrOffset1 != 0) {
|
|
dmemUncompressedAddrOffset1 =
|
|
numSamplesInThisIteration * SAMPLE_SIZE + dmemUncompressedAddrOffset1;
|
|
} else {
|
|
dmemUncompressedAddrOffset1 =
|
|
(numFirstFrameSamplesToIgnore + numSamplesInThisIteration) * SAMPLE_SIZE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
flags = A_CONTINUE;
|
|
|
|
skip:
|
|
|
|
// Update what to do with the samples next
|
|
if (sampleFinished) {
|
|
if ((numSamplesToLoadAdj - numSamplesProcessed) != 0) {
|
|
AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE + dmemUncompressedAddrOffset1,
|
|
(numSamplesToLoadAdj - numSamplesProcessed) * SAMPLE_SIZE);
|
|
}
|
|
finished = true;
|
|
note->sampleState.bitField0.finished = true;
|
|
AudioSynth_DisableSampleStates(updateIndex, noteIndex);
|
|
break; // break out of the for-loop
|
|
} else if (loopToPoint) {
|
|
synthState->atLoopPoint = true;
|
|
synthState->samplePosInt = loopInfo->start;
|
|
} else {
|
|
synthState->samplePosInt += numSamplesToProcess;
|
|
}
|
|
}
|
|
|
|
switch (numParts) {
|
|
case 1:
|
|
sampleDmemBeforeResampling = DMEM_UNCOMPRESSED_NOTE + skipBytes;
|
|
break;
|
|
|
|
case 2:
|
|
switch (curPart) {
|
|
case 0:
|
|
AudioSynth_InterL(cmd++, DMEM_UNCOMPRESSED_NOTE + skipBytes,
|
|
DMEM_TEMP + (SAMPLES_PER_FRAME * SAMPLE_SIZE),
|
|
ALIGN8(numSamplesToLoadAdj / 2));
|
|
numSamplesToLoadFirstPart = numSamplesToLoadAdj;
|
|
sampleDmemBeforeResampling = DMEM_TEMP + (SAMPLES_PER_FRAME * SAMPLE_SIZE);
|
|
if (finished) {
|
|
AudioSynth_ClearBuffer(cmd++, sampleDmemBeforeResampling + numSamplesToLoadFirstPart,
|
|
numSamplesToLoadAdj + SAMPLES_PER_FRAME);
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
AudioSynth_InterL(cmd++, DMEM_UNCOMPRESSED_NOTE + skipBytes,
|
|
DMEM_TEMP + (SAMPLES_PER_FRAME * SAMPLE_SIZE) + numSamplesToLoadFirstPart,
|
|
ALIGN8(numSamplesToLoadAdj / 2));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (finished) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the flags for the signal processing below
|
|
flags = A_CONTINUE;
|
|
if (sampleState->bitField0.needsInit == true) {
|
|
sampleState->bitField0.needsInit = false;
|
|
flags = A_INIT;
|
|
}
|
|
|
|
// Resample the decompressed mono-signal to the correct pitch
|
|
cmd = AudioSynth_FinalResample(cmd, synthState, numSamplesPerUpdate * SAMPLE_SIZE, frequencyFixedPoint,
|
|
sampleDmemBeforeResampling, flags);
|
|
|
|
// UnkCmd19 was removed from the audio microcode
|
|
// This block performs no operation
|
|
if (bookOffset == 3) {
|
|
AudioSynth_UnkCmd19(cmd++, DMEM_TEMP, DMEM_TEMP, numSamplesPerUpdate * (s32)SAMPLE_SIZE, 0);
|
|
}
|
|
|
|
// Apply the gain to the mono-signal to adjust the volume
|
|
gain = sampleState->gain;
|
|
if (gain != 0) {
|
|
// A gain of 0x10 (a UQ4.4 number) is equivalent to 1.0 and represents no volume change
|
|
if (gain < 0x10) {
|
|
gain = 0x10;
|
|
}
|
|
AudioSynth_HiLoGain(cmd++, gain, DMEM_TEMP, 0, (numSamplesPerUpdate + SAMPLES_PER_FRAME) * SAMPLE_SIZE);
|
|
}
|
|
|
|
// Apply the filter to the mono-signal
|
|
filter = sampleState->filter;
|
|
if (filter != 0) {
|
|
AudioSynth_LoadFilterSize(cmd++, numSamplesPerUpdate * SAMPLE_SIZE, filter);
|
|
AudioSynth_LoadFilterBuffer(cmd++, flags, DMEM_TEMP, synthState->synthesisBuffers->filterState);
|
|
}
|
|
|
|
// Apply the comb filter to the mono-signal by taking the signal with a small temporal offset,
|
|
// and adding it back to itself
|
|
combFilterSize = sampleState->combFilterSize;
|
|
combFilterGain = sampleState->combFilterGain;
|
|
combFilterState = synthState->synthesisBuffers->combFilterState;
|
|
if ((combFilterSize != 0) && (sampleState->combFilterGain != 0)) {
|
|
AudioSynth_DMemMove(cmd++, DMEM_TEMP, DMEM_COMB_TEMP, numSamplesPerUpdate * SAMPLE_SIZE);
|
|
combFilterDmem = DMEM_COMB_TEMP - combFilterSize;
|
|
if (synthState->combFilterNeedsInit) {
|
|
AudioSynth_ClearBuffer(cmd++, combFilterDmem, combFilterSize);
|
|
synthState->combFilterNeedsInit = false;
|
|
} else {
|
|
AudioSynth_LoadBuffer(cmd++, combFilterDmem, combFilterSize, combFilterState);
|
|
}
|
|
AudioSynth_SaveBuffer(cmd++, DMEM_TEMP + (numSamplesPerUpdate * SAMPLE_SIZE) - combFilterSize, combFilterSize,
|
|
combFilterState);
|
|
AudioSynth_Mix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, combFilterGain, DMEM_COMB_TEMP,
|
|
combFilterDmem);
|
|
AudioSynth_DMemMove(cmd++, combFilterDmem, DMEM_TEMP, numSamplesPerUpdate * SAMPLE_SIZE);
|
|
} else {
|
|
synthState->combFilterNeedsInit = true;
|
|
}
|
|
|
|
// Determine the behavior of the audio processing that leads to the haas effect
|
|
if ((sampleState->haasEffectLeftDelaySize != 0) || (synthState->prevHaasEffectLeftDelaySize != 0)) {
|
|
haasEffectDelaySide = HAAS_EFFECT_DELAY_LEFT;
|
|
} else if ((sampleState->haasEffectRightDelaySize != 0) || (synthState->prevHaasEffectRightDelaySize != 0)) {
|
|
haasEffectDelaySide = HAAS_EFFECT_DELAY_RIGHT;
|
|
} else {
|
|
haasEffectDelaySide = HAAS_EFFECT_DELAY_NONE;
|
|
}
|
|
|
|
// Apply an unknown effect based on the surround sound-mode
|
|
if (gAudioContext.soundMode == SOUNDMODE_SURROUND) {
|
|
sampleState->targetVolLeft = sampleState->targetVolLeft >> 1;
|
|
sampleState->targetVolRight = sampleState->targetVolRight >> 1;
|
|
if (sampleState->surroundEffectIndex != 0xFF) {
|
|
cmd = AudioSynth_ApplySurroundEffect(cmd, sampleState, synthState, numSamplesPerUpdate, DMEM_TEMP, flags);
|
|
}
|
|
}
|
|
|
|
// Split the mono-signal into left and right channels:
|
|
// Both for dry signal (to go to the speakers now)
|
|
// and for wet signal (to go to a reverb buffer to be stored, and brought back later to produce an echo)
|
|
cmd = AudioSynth_ProcessEnvelope(cmd, sampleState, synthState, numSamplesPerUpdate, DMEM_TEMP, haasEffectDelaySide,
|
|
flags);
|
|
|
|
// Apply the haas effect by delaying either the left or the right channel by a small amount
|
|
if (sampleState->bitField1.useHaasEffect) {
|
|
if (!(flags & A_INIT)) {
|
|
flags = A_CONTINUE;
|
|
}
|
|
cmd = AudioSynth_ApplyHaasEffect(cmd, sampleState, synthState, numSamplesPerUpdate * (s32)SAMPLE_SIZE, flags,
|
|
haasEffectDelaySide);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_ApplySurroundEffect(Acmd* cmd, NoteSampleState* sampleState, NoteSynthesisState* synthState,
|
|
s32 numSamplesPerUpdate, s32 haasDmem, s32 flags) {
|
|
s32 wetGain;
|
|
u16 dryGain;
|
|
s64 dmem = DMEM_SURROUND_TEMP;
|
|
f32 decayGain;
|
|
|
|
AudioSynth_DMemMove(cmd++, haasDmem, DMEM_HAAS_TEMP, numSamplesPerUpdate * SAMPLE_SIZE);
|
|
dryGain = synthState->surroundEffectGain;
|
|
|
|
if (flags == A_INIT) {
|
|
aClearBuffer(cmd++, dmem, sizeof(synthState->synthesisBuffers->surroundEffectState));
|
|
synthState->surroundEffectGain = 0;
|
|
} else {
|
|
aLoadBuffer(cmd++, synthState->synthesisBuffers->surroundEffectState, dmem,
|
|
sizeof(synthState->synthesisBuffers->surroundEffectState));
|
|
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, dryGain, dmem, DMEM_LEFT_CH);
|
|
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, (dryGain ^ 0xFFFF), dmem, DMEM_RIGHT_CH);
|
|
|
|
wetGain = (dryGain * synthState->curReverbVol) >> 7;
|
|
|
|
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, wetGain, dmem, DMEM_WET_LEFT_CH);
|
|
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, (wetGain ^ 0xFFFF), dmem, DMEM_WET_RIGHT_CH);
|
|
}
|
|
|
|
aSaveBuffer(cmd++, DMEM_SURROUND_TEMP + (numSamplesPerUpdate * SAMPLE_SIZE),
|
|
synthState->synthesisBuffers->surroundEffectState,
|
|
sizeof(synthState->synthesisBuffers->surroundEffectState));
|
|
|
|
decayGain = (sampleState->targetVolLeft + sampleState->targetVolRight) * (1.0f / 0x2000);
|
|
|
|
if (decayGain > 1.0f) {
|
|
decayGain = 1.0f;
|
|
}
|
|
|
|
decayGain = decayGain * gDefaultPanVolume[127 - sampleState->surroundEffectIndex];
|
|
synthState->surroundEffectGain = ((decayGain * 0x7FFF) + synthState->surroundEffectGain) / 2;
|
|
|
|
AudioSynth_DMemMove(cmd++, DMEM_HAAS_TEMP, haasDmem, numSamplesPerUpdate * SAMPLE_SIZE);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_FinalResample(Acmd* cmd, NoteSynthesisState* synthState, s32 size, u16 pitch, u16 inpDmem,
|
|
s32 resampleFlags) {
|
|
if (pitch == 0) {
|
|
AudioSynth_ClearBuffer(cmd++, DMEM_TEMP, size);
|
|
} else {
|
|
aSetBuffer(cmd++, 0, inpDmem, DMEM_TEMP, size);
|
|
aResample(cmd++, resampleFlags, pitch, synthState->synthesisBuffers->finalResampleState);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_ProcessEnvelope(Acmd* cmd, NoteSampleState* sampleState, NoteSynthesisState* synthState,
|
|
s32 numSamplesPerUpdate, u16 dmemSrc, s32 haasEffectDelaySide, s32 flags) {
|
|
u32 dmemDests;
|
|
u16 curVolLeft;
|
|
u16 targetVolLeft;
|
|
s32 curReverbVol;
|
|
u16 curVolRight;
|
|
s16 targetReverbVol;
|
|
s16 rampLeft;
|
|
s16 rampRight;
|
|
s16 rampReverb;
|
|
s16 curReverbVolAndFlags;
|
|
u16 targetVolRight;
|
|
f32 defaultPanVolume;
|
|
s32 pad;
|
|
|
|
targetReverbVol = sampleState->targetReverbVol;
|
|
|
|
curVolLeft = synthState->curVolLeft;
|
|
curVolRight = synthState->curVolRight;
|
|
|
|
targetVolLeft = sampleState->targetVolLeft;
|
|
targetVolLeft <<= 4;
|
|
targetVolRight = sampleState->targetVolRight;
|
|
targetVolRight <<= 4;
|
|
|
|
if ((gAudioContext.soundMode == SOUNDMODE_SURROUND) && (sampleState->surroundEffectIndex != 0xFF)) {
|
|
defaultPanVolume = gDefaultPanVolume[sampleState->surroundEffectIndex];
|
|
targetVolLeft *= defaultPanVolume;
|
|
targetVolRight *= defaultPanVolume;
|
|
}
|
|
|
|
if (targetVolLeft != curVolLeft) {
|
|
rampLeft = (targetVolLeft - curVolLeft) / (numSamplesPerUpdate >> 3);
|
|
} else {
|
|
rampLeft = 0;
|
|
}
|
|
|
|
if (targetVolRight != curVolRight) {
|
|
rampRight = (targetVolRight - curVolRight) / (numSamplesPerUpdate >> 3);
|
|
} else {
|
|
rampRight = 0;
|
|
}
|
|
|
|
curReverbVolAndFlags = synthState->curReverbVol;
|
|
curReverbVol = curReverbVolAndFlags & 0x7F;
|
|
|
|
if (curReverbVolAndFlags != targetReverbVol) {
|
|
rampReverb = (((targetReverbVol & 0x7F) - curReverbVol) << 9) / (numSamplesPerUpdate >> 3);
|
|
synthState->curReverbVol = targetReverbVol;
|
|
} else {
|
|
rampReverb = 0;
|
|
}
|
|
|
|
synthState->curVolLeft = curVolLeft + (rampLeft * (numSamplesPerUpdate >> 3));
|
|
synthState->curVolRight = curVolRight + (rampRight * (numSamplesPerUpdate >> 3));
|
|
|
|
if (sampleState->bitField1.useHaasEffect) {
|
|
AudioSynth_ClearBuffer(cmd++, DMEM_HAAS_TEMP, DMEM_1CH_SIZE);
|
|
AudioSynth_EnvSetup1(cmd++, curReverbVol * 2, rampReverb, rampLeft, rampRight);
|
|
AudioSynth_EnvSetup2(cmd++, curVolLeft, curVolRight);
|
|
|
|
switch (haasEffectDelaySide) {
|
|
case HAAS_EFFECT_DELAY_LEFT:
|
|
// Store the left dry channel in a temp space to be delayed to produce the haas effect
|
|
dmemDests = sEnvMixerLeftHaasDmemDests;
|
|
break;
|
|
|
|
case HAAS_EFFECT_DELAY_RIGHT:
|
|
// Store the right dry channel in a temp space to be delayed to produce the haas effect
|
|
dmemDests = sEnvMixerRightHaasDmemDests;
|
|
break;
|
|
|
|
default: // HAAS_EFFECT_DELAY_NONE
|
|
dmemDests = sEnvMixerDefaultDmemDests;
|
|
break;
|
|
}
|
|
} else {
|
|
aEnvSetup1(cmd++, curReverbVol * 2, rampReverb, rampLeft, rampRight);
|
|
aEnvSetup2(cmd++, curVolLeft, curVolRight);
|
|
dmemDests = sEnvMixerDefaultDmemDests;
|
|
}
|
|
|
|
aEnvMixer(cmd++, dmemSrc, numSamplesPerUpdate, (curReverbVolAndFlags & 0x80) >> 7,
|
|
sampleState->bitField0.strongReverbRight, sampleState->bitField0.strongReverbLeft,
|
|
sampleState->bitField0.strongRight, sampleState->bitField0.strongLeft, dmemDests, sEnvMixerOp);
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* AudioSynth_LoadWaveSamples(Acmd* cmd, NoteSampleState* sampleState, NoteSynthesisState* synthState,
|
|
s32 numSamplesToLoad) {
|
|
s32 numSamplesAvailable;
|
|
s32 harmonicIndexCurAndPrev = sampleState->harmonicIndexCurAndPrev;
|
|
s32 samplePosInt = synthState->samplePosInt;
|
|
s32 numDuplicates;
|
|
|
|
if (sampleState->bitField1.bookOffset != 0) {
|
|
// Move the noise wave (that reads compiled assembly as samples) from ram to dmem
|
|
AudioSynth_LoadBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE, ALIGN16(numSamplesToLoad * SAMPLE_SIZE), gWaveSamples[8]);
|
|
// Offset the address for the samples read by gWaveSamples[8] to the next set of samples
|
|
gWaveSamples[8] += numSamplesToLoad * SAMPLE_SIZE;
|
|
|
|
return cmd;
|
|
} else {
|
|
// Move the synthetic wave from ram to dmem
|
|
aLoadBuffer(cmd++, sampleState->waveSampleAddr, DMEM_UNCOMPRESSED_NOTE, WAVE_SAMPLE_COUNT * SAMPLE_SIZE);
|
|
|
|
// If the harmonic changes, map the offset in the wave from one harmonic to another for continuity
|
|
if (harmonicIndexCurAndPrev != 0) {
|
|
samplePosInt = (samplePosInt * sNumSamplesPerWavePeriod[harmonicIndexCurAndPrev >> 2]) /
|
|
sNumSamplesPerWavePeriod[harmonicIndexCurAndPrev & 3];
|
|
}
|
|
|
|
// Offset in the WAVE_SAMPLE_COUNT samples of gWaveSamples to start processing the wave for continuity
|
|
samplePosInt = (u32)samplePosInt % WAVE_SAMPLE_COUNT;
|
|
// Number of samples in the initial WAVE_SAMPLE_COUNT samples available to be used to process
|
|
numSamplesAvailable = WAVE_SAMPLE_COUNT - samplePosInt;
|
|
|
|
// Require duplicates if there are more samples to load than available
|
|
if (numSamplesToLoad > numSamplesAvailable) {
|
|
// Duplicate (copy) the WAVE_SAMPLE_COUNT samples as many times as needed to reach numSamplesToLoad.
|
|
// (numSamplesToLoad - numSamplesAvailable) is the number of samples missing.
|
|
// Divide by WAVE_SAMPLE_COUNT, rounding up, to get the amount of duplicates
|
|
numDuplicates = ((numSamplesToLoad - numSamplesAvailable + WAVE_SAMPLE_COUNT - 1) / WAVE_SAMPLE_COUNT);
|
|
if (numDuplicates != 0) {
|
|
aDuplicate(cmd++, numDuplicates, DMEM_UNCOMPRESSED_NOTE,
|
|
DMEM_UNCOMPRESSED_NOTE + (WAVE_SAMPLE_COUNT * SAMPLE_SIZE));
|
|
}
|
|
}
|
|
synthState->samplePosInt = samplePosInt;
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
/**
|
|
* The Haas Effect gives directionality to sound by applying a small (< 35ms) delay to either the left or right channel.
|
|
* The delay is small enough that the sound is still perceived as one sound, but the channel that is not delayed will
|
|
* reach our ear first and give a sense of directionality. The sound is directed towards the opposite side of the delay.
|
|
*/
|
|
Acmd* AudioSynth_ApplyHaasEffect(Acmd* cmd, NoteSampleState* sampleState, NoteSynthesisState* synthState, s32 size,
|
|
s32 flags, s32 haasEffectDelaySide) {
|
|
u16 dmemDest;
|
|
u16 pitch;
|
|
u8 prevHaasEffectDelaySize;
|
|
u8 haasEffectDelaySize;
|
|
|
|
switch (haasEffectDelaySide) {
|
|
case HAAS_EFFECT_DELAY_LEFT:
|
|
// Delay the sample on the left channel
|
|
// This allows the right channel to be heard first
|
|
dmemDest = DMEM_LEFT_CH;
|
|
haasEffectDelaySize = sampleState->haasEffectLeftDelaySize;
|
|
prevHaasEffectDelaySize = synthState->prevHaasEffectLeftDelaySize;
|
|
synthState->prevHaasEffectRightDelaySize = 0;
|
|
synthState->prevHaasEffectLeftDelaySize = haasEffectDelaySize;
|
|
break;
|
|
|
|
case HAAS_EFFECT_DELAY_RIGHT:
|
|
// Delay the sample on the right channel
|
|
// This allows the left channel to be heard first
|
|
dmemDest = DMEM_RIGHT_CH;
|
|
haasEffectDelaySize = sampleState->haasEffectRightDelaySize;
|
|
prevHaasEffectDelaySize = synthState->prevHaasEffectRightDelaySize;
|
|
synthState->prevHaasEffectRightDelaySize = haasEffectDelaySize;
|
|
synthState->prevHaasEffectLeftDelaySize = 0;
|
|
break;
|
|
|
|
default: // HAAS_EFFECT_DELAY_NONE
|
|
return cmd;
|
|
}
|
|
|
|
if (flags != A_INIT) {
|
|
// Slightly adjust the sample rate in order to fit a change in sample delay
|
|
if (haasEffectDelaySize != prevHaasEffectDelaySize) {
|
|
pitch = (((size << 0xF) / 2) - 1) / ((size + haasEffectDelaySize - prevHaasEffectDelaySize - 2) / 2);
|
|
aSetBuffer(cmd++, 0, DMEM_HAAS_TEMP, DMEM_TEMP, size + haasEffectDelaySize - prevHaasEffectDelaySize);
|
|
aResampleZoh(cmd++, pitch, 0);
|
|
} else {
|
|
aDMEMMove(cmd++, DMEM_HAAS_TEMP, DMEM_TEMP, size);
|
|
}
|
|
|
|
if (prevHaasEffectDelaySize != 0) {
|
|
aLoadBuffer(cmd++, synthState->synthesisBuffers->haasEffectDelayState, DMEM_HAAS_TEMP,
|
|
ALIGN16(prevHaasEffectDelaySize));
|
|
aDMEMMove(cmd++, DMEM_TEMP, DMEM_HAAS_TEMP + prevHaasEffectDelaySize,
|
|
size + haasEffectDelaySize - prevHaasEffectDelaySize);
|
|
} else {
|
|
aDMEMMove(cmd++, DMEM_TEMP, DMEM_HAAS_TEMP, size + haasEffectDelaySize);
|
|
}
|
|
} else {
|
|
// Just apply a delay directly
|
|
aDMEMMove(cmd++, DMEM_HAAS_TEMP, DMEM_TEMP, size);
|
|
if (haasEffectDelaySize) { // != 0
|
|
aClearBuffer(cmd++, DMEM_HAAS_TEMP, haasEffectDelaySize);
|
|
}
|
|
aDMEMMove(cmd++, DMEM_TEMP, DMEM_HAAS_TEMP + haasEffectDelaySize, size);
|
|
}
|
|
|
|
if (haasEffectDelaySize) { // != 0
|
|
// Save excessive samples for next iteration
|
|
aSaveBuffer(cmd++, DMEM_HAAS_TEMP + size, synthState->synthesisBuffers->haasEffectDelayState,
|
|
ALIGN16(haasEffectDelaySize));
|
|
}
|
|
|
|
aAddMixer(cmd++, ALIGN64(size), DMEM_HAAS_TEMP, dmemDest, 0x7FFF);
|
|
|
|
return cmd;
|
|
}
|