mirror of
https://github.com/HarbourMasters/SpaghettiKart
synced 2026-06-08 20:30:07 -04:00
780 lines
31 KiB
C
780 lines
31 KiB
C
#include <libultraship.h>
|
|
#include "mixer.h"
|
|
#include <macros.h>
|
|
#include "audio/synthesis.h"
|
|
#include "audio/heap.h"
|
|
#include "audio/data.h"
|
|
#include "audio/load.h"
|
|
#include "audio/seqplayer.h"
|
|
#include "audio/internal.h"
|
|
#include "port/Engine.h"
|
|
#include <libultra/abi.h>
|
|
|
|
#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)));
|
|
|
|
struct VolumeChange {
|
|
u16 sourceLeft;
|
|
u16 sourceRight;
|
|
u16 targetLeft;
|
|
u16 targetRight;
|
|
};
|
|
|
|
u64* process_envelope(u64* cmd, struct NoteSubEu* noteSubEu, struct NoteSynthesisState* synthesisState, s32 nSamples,
|
|
u16 inBuf, s32 headsetPanSettings, u32 flags);
|
|
|
|
struct SynthesisReverb gSynthesisReverbs[4];
|
|
u8 sAudioSynthesisPad[0x10];
|
|
|
|
// s16 gVolume;
|
|
// s8 gUseReverb;
|
|
// s8 gNumSynthesisReverbs;
|
|
// struct NoteSubEu *gNoteSubsEu;
|
|
|
|
// f32 gLeftVolRampings;
|
|
// 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
|
|
|
|
/**
|
|
* Given that (almost) all of these are format strings, it is highly likely
|
|
* that they are meant to be used in some sort of printf variant. But I don't
|
|
* care to try and figure out which function gets which string(s)
|
|
* So I've place them all here instead.
|
|
**/
|
|
char synthesisAudioString0[] = "Terminate-Canceled Channel %d,Phase %d\n";
|
|
char synthesisAudioString1[] = "Copy %d\n";
|
|
char synthesisAudioString2[] = "%d->%d\n";
|
|
char synthesisAudioString3[] = "pitch %x: delaybytes %d : olddelay %d\n";
|
|
char synthesisAudioString4[] = "cont %x: delaybytes %d : olddelay %d\n";
|
|
|
|
// 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, 0x300);
|
|
|
|
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;
|
|
}
|
|
|
|
Acmd* synthesis_load_reverb_ring_buffer(Acmd* acmd, u16 addr, u16 srcOffset, s32 len, s32 reverbIndex) {
|
|
aLoadBuffer(acmd++, VIRTUAL_TO_PHYSICAL2(&gSynthesisReverbs[reverbIndex].ringBuffer.left[srcOffset]), addr, len);
|
|
aLoadBuffer(acmd++, VIRTUAL_TO_PHYSICAL2(&gSynthesisReverbs[reverbIndex].ringBuffer.right[srcOffset]), addr + 0x180,
|
|
len);
|
|
return acmd;
|
|
}
|
|
|
|
Acmd* synthesis_save_reverb_ring_buffer(Acmd* acmd, u16 addr, u16 destOffset, s32 len, s32 reverbIndex) {
|
|
aSaveBuffer(acmd++, addr, VIRTUAL_TO_PHYSICAL2(&gSynthesisReverbs[reverbIndex].ringBuffer.left[destOffset]), len);
|
|
aSaveBuffer(acmd++, addr + 0x180,
|
|
VIRTUAL_TO_PHYSICAL2(&gSynthesisReverbs[reverbIndex].ringBuffer.right[destOffset]), len);
|
|
return acmd;
|
|
}
|
|
|
|
void func_800B6FB4(s32 updateIndexStart, s32 noteIndex) {
|
|
s32 i;
|
|
|
|
for (i = updateIndexStart + 1; i < gAudioBufferParameters.updatesPerFrame; i++) {
|
|
if (!gNoteSubsEu[gMaxSimultaneousNotes * i + noteIndex].needsInit) {
|
|
gNoteSubsEu[gMaxSimultaneousNotes * i + noteIndex].enabled = false;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
Acmd* synthesis_execute(Acmd* acmd, s32* writtenCmds, s16* aiBuf, s32 bufLen) {
|
|
s32 i, j;
|
|
u32* aiBufPtr;
|
|
Acmd* cmd = acmd;
|
|
s32 chunkLen;
|
|
|
|
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) {
|
|
chunkLen = bufLen;
|
|
} else {
|
|
if (bufLen / i >= gAudioBufferParameters.samplesPerUpdateMax) {
|
|
chunkLen = gAudioBufferParameters.samplesPerUpdateMax;
|
|
} else if (bufLen / i <= gAudioBufferParameters.samplesPerUpdateMin) {
|
|
chunkLen = gAudioBufferParameters.samplesPerUpdateMin;
|
|
} else {
|
|
chunkLen = gAudioBufferParameters.samplesPerUpdate;
|
|
}
|
|
}
|
|
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 - acmd;
|
|
return cmd;
|
|
}
|
|
|
|
Acmd* synthesis_resample_and_mix_reverb(Acmd* acmd, s32 bufLen, s16 reverbIndex, s16 updateIndex) {
|
|
struct ReverbRingBufferItem* item;
|
|
s16 startPad;
|
|
s16 paddedLengthA;
|
|
|
|
item = &gSynthesisReverbs[reverbIndex].items[gSynthesisReverbs[reverbIndex].curFrame][updateIndex];
|
|
aClearBuffer(acmd++, 0x840, 0x300);
|
|
if (gSynthesisReverbs[reverbIndex].downsampleRate == 1) {
|
|
|
|
acmd = synthesis_load_reverb_ring_buffer(acmd, 0x840, item->startPos, item->lengthA, reverbIndex);
|
|
if (item->lengthB != 0) {
|
|
acmd = synthesis_load_reverb_ring_buffer(acmd, item->lengthA + 0x840, 0U, item->lengthB, reverbIndex);
|
|
}
|
|
|
|
aMix(acmd++, 0x7fff, 0x840, 0x540, 0x300);
|
|
aMix(acmd++, 0x8000 + gSynthesisReverbs[reverbIndex].reverbGain, 0x840, 0x840, 0x300);
|
|
} else {
|
|
startPad = (item->startPos % 8U) * 2;
|
|
paddedLengthA = ALIGN(startPad + item->lengthA, 4);
|
|
|
|
acmd =
|
|
synthesis_load_reverb_ring_buffer(acmd, 0x0020, item->startPos - (startPad / 2), 0x00000180, reverbIndex);
|
|
if (item->lengthB != 0) {
|
|
acmd = synthesis_load_reverb_ring_buffer(acmd, paddedLengthA + 0x20, 0, 0x180 - paddedLengthA, reverbIndex);
|
|
}
|
|
|
|
aSetBuffer(acmd++, 0, 0x20 + startPad, 0x840, bufLen * 2);
|
|
aResample(acmd++, gSynthesisReverbs[reverbIndex].resampleFlags, gSynthesisReverbs[reverbIndex].resampleRate,
|
|
VIRTUAL_TO_PHYSICAL2(gSynthesisReverbs[reverbIndex].resampleStateLeft));
|
|
aSetBuffer(acmd++, 0, 0x1A0 + startPad, 0x9C0, bufLen * 2);
|
|
aResample(acmd++, gSynthesisReverbs[reverbIndex].resampleFlags, gSynthesisReverbs[reverbIndex].resampleRate,
|
|
VIRTUAL_TO_PHYSICAL2(gSynthesisReverbs[reverbIndex].resampleStateRight));
|
|
aMix(acmd++, 0x7fff, 0x840, 0x540, 0x300);
|
|
aMix(acmd++, 0x8000 + gSynthesisReverbs[reverbIndex].reverbGain, 0x840, 0x840, 0x300);
|
|
}
|
|
return acmd;
|
|
}
|
|
|
|
Acmd* synthesis_save_reverb_samples(Acmd* acmd, 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:
|
|
acmd = synthesis_save_reverb_ring_buffer(acmd, 0x840, item->startPos, item->lengthA, reverbIndex);
|
|
if (item->lengthB != 0) {
|
|
acmd =
|
|
synthesis_save_reverb_ring_buffer(acmd, 0x840 + item->lengthA, 0, item->lengthB, reverbIndex);
|
|
}
|
|
break;
|
|
default:
|
|
aSaveBuffer(acmd++, 0x840,
|
|
VIRTUAL_TO_PHYSICAL2(gSynthesisReverbs[reverbIndex]
|
|
.items[gSynthesisReverbs[reverbIndex].curFrame][updateIndex]
|
|
.toDownsampleLeft),
|
|
0x300);
|
|
gSynthesisReverbs[reverbIndex].resampleFlags = 0;
|
|
break;
|
|
}
|
|
}
|
|
return acmd;
|
|
}
|
|
|
|
Acmd* synthesis_do_one_audio_update(s16* aiBuf, s32 bufLen, Acmd* acmd, 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(acmd++, DMEM_ADDR_LEFT_CH, DEFAULT_LEN_2CH);
|
|
i = 0;
|
|
for (j = 0; j < gNumSynthesisReverbs; j++) {
|
|
gUseReverb = gSynthesisReverbs[j].useReverb;
|
|
if (gUseReverb != 0) {
|
|
acmd = synthesis_resample_and_mix_reverb(acmd, bufLen, j, updateIndex);
|
|
}
|
|
for (; i < notePos; i++) {
|
|
temp = updateIndex * gMaxSimultaneousNotes;
|
|
if (j == gNoteSubsEu[temp + noteIndices[i]].reverbIndex) {
|
|
acmd = synthesis_process_note(noteIndices[i], &gNoteSubsEu[temp + noteIndices[i]],
|
|
&gNotes[noteIndices[i]].synthesisState, aiBuf, bufLen, acmd, updateIndex);
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (gSynthesisReverbs[j].useReverb != 0) {
|
|
acmd = synthesis_save_reverb_samples(acmd, j, updateIndex);
|
|
}
|
|
}
|
|
for (; i < notePos; i++) {
|
|
temp = updateIndex * gMaxSimultaneousNotes;
|
|
if (IS_BANK_LOAD_COMPLETE(gNoteSubsEu[temp + noteIndices[i]].bankId) == true) {
|
|
acmd = synthesis_process_note(noteIndices[i], &gNoteSubsEu[temp + noteIndices[i]],
|
|
&gNotes[noteIndices[i]].synthesisState, aiBuf, bufLen, acmd, updateIndex);
|
|
} else {
|
|
gAudioErrorFlags = (gNoteSubsEu[temp + noteIndices[i]].bankId + (i << 8)) + 0x10000000;
|
|
}
|
|
}
|
|
|
|
temp = bufLen * 2;
|
|
aSetBuffer(acmd++, 0, 0, DMEM_ADDR_TEMP, temp);
|
|
// UTODO: Stubbed
|
|
aInterleave(acmd++, 0, DMEM_ADDR_LEFT_CH, DMEM_ADDR_RIGHT_CH, 0);
|
|
aSaveBuffer(acmd++, DMEM_ADDR_TEMP, VIRTUAL_TO_PHYSICAL2(aiBuf), temp * 2);
|
|
return acmd;
|
|
}
|
|
|
|
#ifdef NON_MATCHING
|
|
// generated by m2c commit beb457dabfc7a01ec6540a5404a6a05097a13602 on Nov-01-2023
|
|
Acmd* synthesis_process_note(s32 noteIndex, struct NoteSubEu* noteSubEu, struct NoteSynthesisState* synthesisState,
|
|
UNUSED s16* aiBuf, s32 inBuf, Acmd* cmd, s32 updateIndex) {
|
|
UNUSED s32 pad[4];
|
|
struct AudioBankSample* audioBookSample;
|
|
s16* curLoadedBook;
|
|
struct AdpcmLoop* loopInfo;
|
|
s32 noteFinished;
|
|
s32 restart;
|
|
s32 flags;
|
|
u16 resamplingRateFixedPoint;
|
|
s32 nSamplesToLoad;
|
|
|
|
s32 spFC = 0;
|
|
UNUSED s32 pad2[2];
|
|
|
|
s32 loopInfo_2;
|
|
u8* sampleAddr;
|
|
s32 a1;
|
|
|
|
s32 samplesLenAdjusted;
|
|
s32 nAdpcmSamplesProcessed;
|
|
s32 endPos;
|
|
s32 nSamplesToProcess;
|
|
|
|
UNUSED s32 pad3;
|
|
|
|
s32 s3;
|
|
s32 leftRight;
|
|
s32 s4;
|
|
u32 samplesLenFixedPoint;
|
|
s32 var_s6;
|
|
s32 nSamplesInThisIteration;
|
|
u32 var_t2;
|
|
u8* var_a0_2;
|
|
|
|
s32 s5Aligned;
|
|
s32 temp_t6;
|
|
s32 nParts;
|
|
s32 curPart;
|
|
s32 aligned;
|
|
UNUSED s32 pad4;
|
|
s32 resampledTempLen; /* compiler-managed */
|
|
u16 noteSamplesDmemAddrBeforeResampling;
|
|
UNUSED s32 pad6[1];
|
|
struct Note* note;
|
|
s16 addr;
|
|
|
|
curLoadedBook = NULL;
|
|
note = &gNotes[noteIndex];
|
|
flags = 0;
|
|
if (noteSubEu->needsInit == true) {
|
|
flags = A_INIT;
|
|
synthesisState->restart = 0;
|
|
synthesisState->samplePosInt = 0;
|
|
synthesisState->samplePosFrac = 0;
|
|
synthesisState->curVolLeft = 0;
|
|
synthesisState->curVolRight = 0;
|
|
synthesisState->prevHeadsetPanRight = 0;
|
|
synthesisState->prevHeadsetPanLeft = 0;
|
|
}
|
|
resamplingRateFixedPoint = noteSubEu->resamplingRateFixedPoint;
|
|
|
|
nParts = noteSubEu->hasTwoAdpcmParts + 1;
|
|
samplesLenFixedPoint = (resamplingRateFixedPoint * inBuf * 2) + synthesisState->samplePosFrac;
|
|
nSamplesToLoad = (samplesLenFixedPoint >> 0x10);
|
|
synthesisState->samplePosFrac = samplesLenFixedPoint & 0xFFFF; // m2c didn't have & 0xFFFF
|
|
|
|
if (noteSubEu->isSyntheticWave) {
|
|
cmd = load_wave_samples(cmd, noteSubEu, synthesisState, nSamplesToLoad);
|
|
|
|
noteSamplesDmemAddrBeforeResampling = (synthesisState->samplePosInt * 2) + 0x1A0; // DMEM_ADDR_UNCOMPRESSED_NOTE
|
|
synthesisState->samplePosInt += nSamplesToLoad;
|
|
} else {
|
|
audioBookSample = noteSubEu->sound.audioBankSound->sample;
|
|
loopInfo = audioBookSample->loop;
|
|
endPos = loopInfo->end;
|
|
sampleAddr = audioBookSample->sampleAddr; // book?
|
|
resampledTempLen = 0;
|
|
|
|
for (curPart = 0; curPart < nParts; curPart++) {
|
|
nAdpcmSamplesProcessed = 0;
|
|
s4 = 0;
|
|
|
|
if (nParts == 1) {
|
|
samplesLenAdjusted = nSamplesToLoad;
|
|
} else if (nSamplesToLoad & 1) {
|
|
samplesLenAdjusted = (nSamplesToLoad & ~1) + (curPart * 2);
|
|
} else {
|
|
samplesLenAdjusted = nSamplesToLoad;
|
|
}
|
|
// sm64 also has if (audioBookSample->codec == CODEC_A) {
|
|
if (curLoadedBook != (*audioBookSample->book).book) {
|
|
u32 nEntries;
|
|
curLoadedBook = audioBookSample->book->book; // or just one book?
|
|
nEntries = 16 * audioBookSample->book->order * audioBookSample->book->npredictors;
|
|
aLoadADPCM(cmd++, nEntries, VIRTUAL_TO_PHYSICAL2(noteSubEu->bookOffset + curLoadedBook));
|
|
// noteSubEu here unsure but seems better?
|
|
}
|
|
if (noteSubEu->bookOffset != 0) {
|
|
curLoadedBook = &gUnknownData_800F6290[0];
|
|
}
|
|
while (nAdpcmSamplesProcessed != samplesLenAdjusted) {
|
|
|
|
s32 samplesRemaining;
|
|
s32 s1;
|
|
noteFinished = false;
|
|
restart = false;
|
|
|
|
s3 = synthesisState->samplePosInt & 0xF;
|
|
samplesRemaining = endPos - synthesisState->samplePosInt;
|
|
nSamplesToProcess = samplesLenAdjusted - nAdpcmSamplesProcessed;
|
|
if ((s3 == 0) && (synthesisState->restart == false)) {
|
|
s3 = 16;
|
|
}
|
|
|
|
a1 = 16 - s3;
|
|
|
|
if (nSamplesToProcess < samplesRemaining) {
|
|
loopInfo_2 = ((nSamplesToProcess - a1) + 0xF) / 16;
|
|
s1 = loopInfo_2 * 16;
|
|
var_s6 = (a1 + s1) - nSamplesToProcess;
|
|
} else {
|
|
s1 = samplesRemaining - a1;
|
|
var_s6 = 0;
|
|
if (s1 <= 0) {
|
|
s1 = 0;
|
|
a1 = samplesRemaining;
|
|
}
|
|
loopInfo_2 = (s1 + 0xF) / 16;
|
|
if (loopInfo->count != 0) {
|
|
restart = 1;
|
|
} else {
|
|
noteFinished = 1;
|
|
}
|
|
}
|
|
// var_t2 = 0; // unsure
|
|
if (loopInfo_2 != 0) {
|
|
temp_t6 = ((synthesisState->samplePosInt - s3) + 16) / 16; // diff from sm64 sh
|
|
if (audioBookSample->loaded == 0x81) { // sm64 has audioBookSample->medium
|
|
var_a0_2 = (temp_t6 * 9) + sampleAddr;
|
|
} else {
|
|
var_a0_2 = (temp_t6 * 9) + sampleAddr;
|
|
// var_a0_2 =
|
|
// dma_sample_data((uintptr_t) (temp_t6 * 9) + sampleAddr, ALIGN(((loopInfo_2 * 9) + 16), 4),
|
|
// flags, &synthesisState->sampleDmaIndex);
|
|
// unsure flags?
|
|
}
|
|
|
|
var_t2 = ((uintptr_t) var_a0_2 & 0xF);
|
|
|
|
aligned = ALIGN(((loopInfo_2 * 9) + 16), 4);
|
|
addr = (0x540 - aligned); // DMEM_ADDR_COMPRESSED_ADPCM_DATA
|
|
|
|
aLoadBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(var_a0_2 - var_t2), addr, aligned);
|
|
} else {
|
|
s1 = 0; // ?
|
|
var_t2 = 0;
|
|
}
|
|
|
|
if (synthesisState->restart != false) {
|
|
aSetLoop(cmd++, VIRTUAL_TO_PHYSICAL2(audioBookSample->loop->state));
|
|
|
|
flags = A_LOOP;
|
|
synthesisState->restart = false;
|
|
}
|
|
nSamplesInThisIteration = (s1 + a1) - var_s6;
|
|
s5Aligned = ALIGN(s4 + 16, 4);
|
|
if (nAdpcmSamplesProcessed == 0) {
|
|
|
|
aligned = ALIGN(((loopInfo_2 * 9) + 16), 4);
|
|
addr = (0x540 - aligned);
|
|
aSetBuffer(cmd++, 0, addr + var_t2, 0x1A0,
|
|
s1 * 2); // unsure
|
|
// s1 or s3 here?
|
|
aADPCMdec(cmd++, flags, VIRTUAL_TO_PHYSICAL2(synthesisState->synthesisBuffers->adpcmdecState));
|
|
spFC = s3 * 2;
|
|
|
|
} else {
|
|
|
|
aligned = ALIGN(((loopInfo_2 * 9) + 16), 4);
|
|
addr = (0x540 - aligned);
|
|
aSetBuffer(cmd++, 0, addr + var_t2, 0x1A0 + s5Aligned, s1 * 2); // unsure
|
|
|
|
aADPCMdec(cmd++, flags, VIRTUAL_TO_PHYSICAL2(synthesisState->synthesisBuffers->adpcmdecState));
|
|
|
|
aDMEMMove(cmd++, 0x1A0 + s5Aligned + (s3 * 2), 0x1A0 + s4, nSamplesInThisIteration * 2);
|
|
}
|
|
|
|
nAdpcmSamplesProcessed += nSamplesInThisIteration;
|
|
switch (flags) {
|
|
case 1:
|
|
spFC = 0x20;
|
|
s4 = (s1 * 2) + 0x20;
|
|
break;
|
|
case 2:
|
|
s4 += nSamplesInThisIteration * 2;
|
|
break;
|
|
default:
|
|
if (s4 != 0) {
|
|
s4 += nSamplesInThisIteration * 2;
|
|
} else {
|
|
s4 = (s3 + nSamplesInThisIteration) * 2;
|
|
}
|
|
break;
|
|
}
|
|
flags = 0;
|
|
|
|
if (noteFinished) {
|
|
aClearBuffer(cmd++, 0x1A0 + s4, (samplesLenAdjusted - nAdpcmSamplesProcessed) * 2);
|
|
noteSubEu->finished = 1; // sm64 says = 1
|
|
note->noteSubEu.finished = 1;
|
|
note->noteSubEu.enabled = 0;
|
|
|
|
func_800B6FB4(updateIndex, noteIndex);
|
|
break;
|
|
} else {
|
|
if (restart) {
|
|
synthesisState->restart = true;
|
|
synthesisState->samplePosInt = loopInfo->start;
|
|
} else {
|
|
synthesisState->samplePosInt += nSamplesToProcess;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (nParts) {
|
|
case 1:
|
|
noteSamplesDmemAddrBeforeResampling = 0x1A0 + spFC;
|
|
break;
|
|
case 2:
|
|
switch (curPart) {
|
|
case 0:
|
|
aDownsampleHalf(cmd++, ALIGN(samplesLenAdjusted / 2, 3), 0x1A0 + spFC, DMEM_ADDR_RESAMPLED);
|
|
resampledTempLen = samplesLenAdjusted;
|
|
noteSamplesDmemAddrBeforeResampling = DMEM_ADDR_RESAMPLED;
|
|
if (noteSubEu->finished != false) {
|
|
aClearBuffer(cmd++, noteSamplesDmemAddrBeforeResampling + resampledTempLen,
|
|
samplesLenAdjusted + 0x10);
|
|
}
|
|
break;
|
|
case 1:
|
|
aDownsampleHalf(cmd++, ALIGN(samplesLenAdjusted / 2, 3), DMEM_ADDR_RESAMPLED2 + spFC,
|
|
resampledTempLen + DMEM_ADDR_RESAMPLED);
|
|
break;
|
|
}
|
|
//break;
|
|
}
|
|
if (noteSubEu->finished != false) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
flags = 0;
|
|
if (noteSubEu->needsInit == true) {
|
|
flags = A_INIT;
|
|
noteSubEu->needsInit = false;
|
|
}
|
|
|
|
cmd = final_resample(cmd, synthesisState, inBuf * 2, resamplingRateFixedPoint, noteSamplesDmemAddrBeforeResampling,
|
|
flags);
|
|
|
|
if ((noteSubEu->headsetPanRight) || (synthesisState->prevHeadsetPanRight)) {
|
|
leftRight = 1; // According to m2c this is a new var. Perhaps flags?
|
|
} else if ((noteSubEu->headsetPanLeft) || (synthesisState->prevHeadsetPanLeft)) {
|
|
leftRight = 2;
|
|
} else {
|
|
leftRight = 0;
|
|
}
|
|
cmd = func_800B86A0(cmd, noteSubEu, synthesisState, inBuf, 0, leftRight, flags);
|
|
|
|
if (noteSubEu->usesHeadsetPanEffects) {
|
|
// synthesisState->restart = 1;
|
|
cmd = note_apply_headset_pan_effects(cmd, noteSubEu, synthesisState, inBuf * 2, flags, leftRight);
|
|
}
|
|
return cmd;
|
|
}
|
|
#else
|
|
GLOBAL_ASM("asm/non_matchings/audio/synthesis/synthesis_process_note.s")
|
|
#endif
|
|
|
|
Acmd* load_wave_samples(Acmd* acmd, struct NoteSubEu* noteSubEu, struct NoteSynthesisState* synthesisState,
|
|
s32 nSamplesToLoad) {
|
|
s32 a3;
|
|
s32 repeats;
|
|
aLoadBuffer(acmd++, VIRTUAL_TO_PHYSICAL2(noteSubEu->sound.samples), 0x1A0, 128);
|
|
|
|
synthesisState->samplePosInt &= 0x3f;
|
|
a3 = 64 - synthesisState->samplePosInt;
|
|
if (a3 < nSamplesToLoad) {
|
|
repeats = (nSamplesToLoad - a3 + 63) / 64;
|
|
if (repeats != 0) {
|
|
// UTODO: Stubbed
|
|
aDMEMMove2(acmd++, repeats, 0x1A0, 0x1A0 + 128, 128);
|
|
}
|
|
}
|
|
return acmd;
|
|
}
|
|
|
|
Acmd* final_resample(Acmd* acmd, struct NoteSynthesisState* synthesisState, s32 count, u16 pitch, u16 dmemIn,
|
|
u32 flags) {
|
|
aSetBuffer(acmd++, /*flags*/ 0, dmemIn, /*dmemout*/ 0, count);
|
|
aResample(acmd++, flags, pitch, VIRTUAL_TO_PHYSICAL2(synthesisState->synthesisBuffers->finalResampleState));
|
|
return acmd;
|
|
}
|
|
|
|
#ifdef NON_MATCHING
|
|
// I wish I knew what was up with this function
|
|
// It doesn't quite match either the EU or Shindou versions
|
|
// of process_envelope.
|
|
// What's here appears to have the right "shape" based on
|
|
// the M2C output, but beyond that I can't really tell what's
|
|
// going on
|
|
Acmd* func_800B86A0(Acmd* cmd, struct NoteSubEu* note, struct NoteSynthesisState* synthesisState, s32 nSamples,
|
|
u16 inBuf, s32 headsetPanSettings, UNUSED u32 flags) {
|
|
u16 sourceRight;
|
|
u16 sourceLeft;
|
|
u16 targetLeft;
|
|
u16 targetRight;
|
|
s32 rampLeft;
|
|
s32 rampRight;
|
|
UNUSED s32 sourceReverbVol;
|
|
UNUSED s16 rampReverb;
|
|
UNUSED s32 reverbVolDiff = 0;
|
|
|
|
sourceLeft = synthesisState->curVolLeft;
|
|
sourceRight = synthesisState->curVolRight;
|
|
targetLeft = note->targetVolLeft;
|
|
targetRight = note->targetVolRight;
|
|
targetLeft <<= 4;
|
|
targetRight <<= 4;
|
|
|
|
rampLeft = (targetLeft - sourceLeft) / (nSamples >> 3);
|
|
rampRight = (targetRight - sourceRight) / (nSamples >> 3);
|
|
synthesisState->curVolLeft = sourceLeft + rampLeft * (nSamples >> 3);
|
|
synthesisState->curVolRight = sourceRight + rampRight * (nSamples >> 3);
|
|
|
|
if (note->usesHeadsetPanEffects) {
|
|
aClearBuffer(cmd++, DMEM_ADDR_NOTE_PAN_TEMP, DEFAULT_LEN_1CH);
|
|
aEnvSetup1(cmd++, note->reverbVol, (((sourceLeft & 0xFF) << 8) | (sourceRight & 0xFF)), rampRight, rampLeft);
|
|
aEnvSetup2(cmd++, sourceLeft, sourceRight);
|
|
|
|
switch (headsetPanSettings) {
|
|
case 1:
|
|
aEnvMixer(cmd++, inBuf, nSamples, 0, note->stereoStrongRight, note->stereoStrongLeft,
|
|
DMEM_ADDR_NOTE_PAN_TEMP, DMEM_ADDR_RIGHT_CH, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_WET_RIGHT_CH);
|
|
break;
|
|
case 2:
|
|
aEnvMixer(cmd++, inBuf, nSamples, 0, note->stereoStrongRight, note->stereoStrongLeft, DMEM_ADDR_LEFT_CH,
|
|
DMEM_ADDR_NOTE_PAN_TEMP, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_WET_RIGHT_CH);
|
|
break;
|
|
default:
|
|
aEnvMixer(cmd++, inBuf, nSamples, 0, note->stereoStrongRight, note->stereoStrongLeft, DMEM_ADDR_LEFT_CH,
|
|
DMEM_ADDR_RIGHT_CH, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_WET_RIGHT_CH);
|
|
break;
|
|
}
|
|
} else {
|
|
aEnvSetup1(cmd++, note->reverbVol, (((sourceLeft & 0xFF) << 8) | (sourceRight & 0xFF)), rampLeft, rampRight);
|
|
aEnvSetup2(cmd++, sourceLeft, sourceRight);
|
|
aEnvMixer(cmd++, inBuf, nSamples, 0, note->stereoStrongRight, note->stereoStrongLeft, DMEM_ADDR_LEFT_CH,
|
|
DMEM_ADDR_RIGHT_CH, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_WET_RIGHT_CH);
|
|
}
|
|
return cmd;
|
|
}
|
|
#else
|
|
GLOBAL_ASM("asm/non_matchings/audio/synthesis/func_800B86A0.s")
|
|
#endif
|
|
|
|
Acmd* note_apply_headset_pan_effects(Acmd* acmd, struct NoteSubEu* noteSubEu, struct NoteSynthesisState* note,
|
|
s32 bufLen, s32 flags, s32 leftRight) {
|
|
u16 dest;
|
|
u16 pitch;
|
|
u8 prevPanShift;
|
|
u8 panShift;
|
|
UNUSED u8 unkDebug;
|
|
|
|
switch (leftRight) {
|
|
case 1:
|
|
dest = 0x540;
|
|
panShift = noteSubEu->headsetPanRight;
|
|
note->prevHeadsetPanLeft = 0;
|
|
prevPanShift = note->prevHeadsetPanRight;
|
|
note->prevHeadsetPanRight = panShift;
|
|
break;
|
|
case 2:
|
|
dest = 0x6C0;
|
|
panShift = noteSubEu->headsetPanLeft;
|
|
note->prevHeadsetPanRight = 0;
|
|
|
|
prevPanShift = note->prevHeadsetPanLeft;
|
|
note->prevHeadsetPanLeft = panShift;
|
|
break;
|
|
default:
|
|
return acmd;
|
|
}
|
|
|
|
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(acmd++, 0x0200, 0x0000, 8);
|
|
aClearBuffer(acmd++, 8, 8); // Set pitch accumulator to 0 in the resample state
|
|
aDMEMMove(acmd++, 0x0200, 0x0000 + 0x10,
|
|
0x10); // No idea, result seems to be overwritten later
|
|
|
|
aSaveBuffer(acmd++, 0x0000, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->panResampleState),
|
|
sizeof(note->synthesisBuffers->panResampleState));
|
|
|
|
pitch = (bufLen << 0xf) / (bufLen + panShift - prevPanShift + 8);
|
|
if (pitch) {}
|
|
aSetBuffer(acmd++, 0, 0x0200 + 8, 0x0000, panShift + bufLen - prevPanShift);
|
|
aResample(acmd++, 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);
|
|
}
|
|
|
|
if (1) {}
|
|
|
|
aSetBuffer(acmd++, 0, 0x0200, 0x0000, bufLen + panShift - prevPanShift);
|
|
aResample(acmd++, 0, pitch, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->panResampleState));
|
|
}
|
|
|
|
if (prevPanShift != 0) {
|
|
aLoadBuffer(acmd++, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->panSamplesBuffer), 0x0200, prevPanShift);
|
|
aDMEMMove(acmd++, 0x0000, 0x0200 + prevPanShift, panShift + bufLen - prevPanShift);
|
|
} else {
|
|
aDMEMMove(acmd++, 0x0000, 0x0200, panShift + bufLen - prevPanShift);
|
|
}
|
|
} else {
|
|
// Just shift right
|
|
aDMEMMove(acmd++, 0x0200, 0x0000, bufLen);
|
|
aDMEMMove(acmd++, 0x0000, 0x0200 + panShift, bufLen);
|
|
aClearBuffer(acmd++, 0x0200, panShift);
|
|
}
|
|
|
|
if (panShift) {
|
|
// Save excessive samples for next iteration
|
|
aSaveBuffer(acmd++, 0x0200 + bufLen, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->panSamplesBuffer), panShift);
|
|
}
|
|
|
|
aMix(acmd++, /*gain*/ 0x7FFF, /*in*/ 0x0200, /*out*/ dest, ALIGN(bufLen, 5));
|
|
|
|
return acmd;
|
|
}
|