#include #include #include "synthesis.h" #include "memory.h" #include "data.h" #include "load.h" #include "seqplayer.h" #include "external.h" #define DMEM_ADDR_TEMP 0x0 #define DMEM_ADDR_UNCOMPRESSED_NOTE 0x180 #define DMEM_ADDR_ADPCM_RESAMPLED 0x20 #define DMEM_ADDR_ADPCM_RESAMPLED2 0x160 #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 ALIGN(val, amnt) (((val) + (1 << amnt) - 1) & ~((1 << amnt) - 1)) struct SynthesisReverb gSynthesisReverb; u8 sAudioSynthesisPad[0x20]; struct VolumeChange { u16 sourceLeft; u16 sourceRight; u16 targetLeft; u16 targetRight; }; u64 *synthesis_do_one_audio_update(u16 *aiBuf, s32 bufLen, u64 *cmd, u32 updateIndex); u64 *synthesis_process_notes(u16 *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); void prepare_reverb_ring_buffer(s32 chunkLen, u32 updateIndex) { struct ReverbRingBufferItem *item; s32 srcPos; s32 dstPos; s32 nSamples; s32 numSamplesAfterDownsampling; s32 excessiveSamples; if (gReverbDownsampleRate != 1) { 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->lengths[0] / 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->lengths[1] / 2; srcPos += gReverbDownsampleRate, dstPos++) { gSynthesisReverb.ringBuffer.left[dstPos] = item->toDownsampleLeft[srcPos]; gSynthesisReverb.ringBuffer.right[dstPos] = item->toDownsampleRight[srcPos]; } } } item = &gSynthesisReverb.items[gSynthesisReverb.curFrame][updateIndex]; numSamplesAfterDownsampling = nSamples = chunkLen / gReverbDownsampleRate; if (((nSamples + gSynthesisReverb.nextRingBufferPos) - gSynthesisReverb.bufSizePerChannel) < 0) { // There is space in the ring buffer before it wraps around item->lengths[0] = nSamples * 2; item->lengths[1] = 0; item->startPos = (s32) gSynthesisReverb.nextRingBufferPos; gSynthesisReverb.nextRingBufferPos += nSamples; } else { // Ring buffer wrapped around excessiveSamples = (nSamples + gSynthesisReverb.nextRingBufferPos) - gSynthesisReverb.bufSizePerChannel; nSamples = numSamplesAfterDownsampling - excessiveSamples; item->lengths[0] = nSamples * 2; item->lengths[1] = excessiveSamples * 2; item->startPos = gSynthesisReverb.nextRingBufferPos; gSynthesisReverb.nextRingBufferPos = excessiveSamples; } // These fields are never read later item->numSamplesAfterDownsampling = numSamplesAfterDownsampling; item->chunkLen = chunkLen; } 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; } // bufLen will be divisible by 16 u64 *synthesis_execute(u64 *cmdBuf, s32 *writtenCmds, u16 *aiBuf, s32 bufLen) { s32 chunkLen; s32 i; s32 remaining = bufLen; u32 *aiBufPtr = (u32 *) aiBuf; u64 *cmd = cmdBuf; s32 v0; aSegment(cmd++, 0, 0); for (i = gAudioUpdatesPerFrame; i > 0; i--) { if (i == 1) { // 'remaining' will automatically be divisible by 8, no need to round chunkLen = remaining; } else { v0 = remaining / 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((u16 *) aiBufPtr, chunkLen, cmd, gAudioUpdatesPerFrame - i); remaining -= chunkLen; aiBufPtr += chunkLen; } if (gSynthesisReverb.framesLeftToIgnore != 0) { gSynthesisReverb.framesLeftToIgnore--; } gSynthesisReverb.curFrame ^= 1; *writtenCmds = cmd - cmdBuf; return cmd; } u64 *synthesis_do_one_audio_update(u16 *aiBuf, s32 bufLen, u64 *cmd, u32 updateIndex) { UNUSED s32 pad1[1]; s16 ra; s16 t4; UNUSED s32 pad[2]; struct ReverbRingBufferItem *v1; UNUSED s32 pad2[2]; 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->lengths[1] != 0) { // Ring buffer wrapped aSetLoadBufferPair(cmd++, v1->lengths[0], 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 = ALIGN(v1->lengths[0] + t4, 4); aSetLoadBufferPair(cmd++, 0, v1->startPos - t4 / 2); if (v1->lengths[1] != 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->lengths[0], v1->startPos); if (v1->lengths[1] != 0) { // Ring buffer wrapped aSetSaveBufferPair(cmd++, v1->lengths[0], v1->lengths[1], 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; } #ifdef NON_MATCHING u64 *synthesis_process_notes(u16 *aiBuf, s32 bufLen, u64 *cmd) { s32 noteIndex; // sp174 struct Note *note; // s7 struct AudioBankSample *audioBookSample; // sp164 struct AdpcmLoop *loopInfo; // sp160 s16 *curLoadedBook; // sp15C s32 noteFinished; // 150 t2 s32 restart; // 14c t3 s32 flags; // sp148 UNUSED u8 pad8[0x14]; s32 sp130; UNUSED u8 pad7[0xC]; u8 *sampleAddr; // sp120 u32 samplesLenAdjusted; // 108, definitely unsigned, t5 // UNUSED u8 pad6[4]; s32 endPos; // sp110 s32 nSamplesToProcess; // 10c a0 // UNUSED u8 pad5[0x10c - 0xe8 - 4]; s32 nParts; // spE8 s32 curPart; // spE4 u32 nAdpcmSamplesProcessed; // probably unsigned, fp s32 t0; s32 resampledTempLen; // spD8 u16 noteSamplesDmemAddrBeforeResampling; // spD6 // sp6c is a temporary! u16 resamplingRateFixedPoint; // sp5c s32 samplesLenInt; // sp58 s32 onePart; // sp54 s32 s6; s32 s6_2; s32 s2; s32 s0; s32 s3; s32 s5; // s32 v0; u32 samplesLenFixedPoint; // v1_1 s32 nSamplesInThisIteration; // v1_2 u32 a3; s32 t9; u8 *v0_2; f32 resamplingRate; // f12 UNUSED s32 temp; for (noteIndex = 0, curLoadedBook = NULL; noteIndex < gMaxSimultaneousNotes; noteIndex++) { note = &gNotes[noteIndex]; if (IS_BANK_LOAD_COMPLETE(note->bankId) == FALSE) { gAudioErrorFlags = (note->bankId << 8) + noteIndex + 0x1000000; } else if (note->enabled) { // This matches much much better if enabled is volatile... but that // breaks other functions (e.g. note_enable). Can we achieve the // volatile effect in some other way? flags = 0; if (note->needsInit == TRUE) { flags = A_INIT; note->samplePosInt = 0; note->samplePosFrac = 0; } 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; // 16-bit store, can't reuse if (note->sound == NULL) { // A wave synthesis note (not ADPCM) // samplesLenFixedPoint >> 0x10 stored in s0 cmd = load_wave_samples(cmd, note, samplesLenFixedPoint >> 0x10); noteSamplesDmemAddrBeforeResampling = DMEM_ADDR_UNCOMPRESSED_NOTE + note->samplePosInt * 2; note->samplePosInt += (samplesLenFixedPoint >> 0x10); flags = 0; } else { // ADPCM note audioBookSample = note->sound->sample; // sp58 is a low-numbered register, so possibly a temporary. // Should it be used for samplesLenFixedPoint >> 0x10 above as well? But then // the asm matches worse. This variable seems to highly involved // in causing this function not to match... samplesLenInt = samplesLenFixedPoint >> 0x10; // v0 // sp58 loopInfo = audioBookSample->loop; endPos = loopInfo->end; sampleAddr = audioBookSample->sampleAddr; onePart = (nParts == 1); resampledTempLen = 0; for (curPart = 0; curPart < nParts; curPart++) { nAdpcmSamplesProcessed = 0; s5 = 0; // This whole if-else if chain is weird. First it uses onePart // instead of nParts == 1, and it needs a weird if to not // induce non-matchings all over the rest of the function. // Then it induces a bunch of stack-relative loads that // shouldn't be there. Finally, it relates to sp58, which // behaves very oddly... if (onePart) { // nParts == 1 if (1) { // shouldn't be here, but it makes things line up better... samplesLenAdjusted = samplesLenInt; } } else if (samplesLenInt & 1) { samplesLenAdjusted = (samplesLenInt & ~1) + (curPart * 2); } else { samplesLenAdjusted = samplesLenInt; } if (curLoadedBook != audioBookSample->book->book) { u32 nEntries; // v1 curLoadedBook = audioBookSample->book->book; nEntries = audioBookSample->book->order * audioBookSample->book->npredictors; aLoadADPCM(cmd++, nEntries * 16, VIRTUAL_TO_PHYSICAL2(curLoadedBook)); } while (nAdpcmSamplesProcessed != samplesLenAdjusted) { s32 samplesRemaining; // v1 s32 s0; // sp58 = sp58; here, doesn't happen noteFinished = FALSE; restart = FALSE; nSamplesToProcess = samplesLenAdjusted - nAdpcmSamplesProcessed; s2 = note->samplePosInt & 0xf; samplesRemaining = endPos - note->samplePosInt; if (s2 == 0 && !note->restart) { s2 = 16; } s6 = 16 - s2; // a1 if (nSamplesToProcess < samplesRemaining) { t0 = (nSamplesToProcess - s6 + 0xf) / 16; s0 = t0 * 16; s3 = s6 + s0 - nSamplesToProcess; } else { s0 = samplesRemaining + s2 - 0x10; 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; } } // Improve regalloc for saved registers. Probably // shouldn't be here, but it gives nicer diffs for now. s6_2 = s6; if (t0 != 0) { // maybe keep a var for t0 * 9? v0_2 = dma_sample_data( (uintptr_t) (sampleAddr + (note->samplePosInt - s2 + 0x10) / 16 * 9), t0 * 9, flags, ¬e->sampleDmaIndex); 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; } if (note->restart != FALSE) { aSetLoop(cmd++, VIRTUAL_TO_PHYSICAL2(audioBookSample->loop->state)); flags = A_LOOP; // = 2 note->restart = FALSE; } 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 + ALIGN(s5, 5), s0 * 2); aADPCMdec(cmd++, flags, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->adpcmdecState)); aDMEMMove(cmd++, DMEM_ADDR_UNCOMPRESSED_NOTE + ALIGN(s5, 5) + (s2 * 2), DMEM_ADDR_UNCOMPRESSED_NOTE + s5, (s0 + s6_2 - s3) * 2); } nAdpcmSamplesProcessed = nAdpcmSamplesProcessed + s0 + s6_2 - s3; nSamplesInThisIteration = s0 + s6_2 - s3; switch (flags) { case A_INIT: // = 1 sp130 = 0; s5 += s0 * 2; break; case A_LOOP: // = 2 s5 += nSamplesInThisIteration * 2; break; default: if (s5 != 0) { s5 += nSamplesInThisIteration * 2; } else { s5 = (nSamplesInThisIteration + s2) * 2; } break; } flags = 0; if (noteFinished) { aClearBuffer(cmd++, DMEM_ADDR_UNCOMPRESSED_NOTE + s5, (samplesLenAdjusted - nAdpcmSamplesProcessed) * 2); note->samplePosInt = 0; note->finished = 1; note->enabled = 0; break; // goto? doesn't matter, though } if (restart) { note->restart = TRUE; note->samplePosInt = loopInfo->start; } else { note->samplePosInt += nSamplesToProcess; } } 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_ADPCM_RESAMPLED, samplesLenAdjusted + 4); aResample(cmd++, A_INIT, 0xff60, VIRTUAL_TO_PHYSICAL2( note->synthesisBuffers->dummyResampleState)); resampledTempLen = samplesLenAdjusted + 4; noteSamplesDmemAddrBeforeResampling = DMEM_ADDR_ADPCM_RESAMPLED + 4; if (note->finished != 0) { aClearBuffer(cmd++, DMEM_ADDR_ADPCM_RESAMPLED + samplesLenAdjusted + 4, samplesLenAdjusted + 0x10); } break; case 1: aSetBuffer(cmd++, 0, DMEM_ADDR_UNCOMPRESSED_NOTE + sp130, DMEM_ADDR_ADPCM_RESAMPLED2, samplesLenAdjusted + 8); aResample(cmd++, A_INIT, 0xff60, VIRTUAL_TO_PHYSICAL2( note->synthesisBuffers->dummyResampleState)); aDMEMMove(cmd++, DMEM_ADDR_ADPCM_RESAMPLED2 + 4, DMEM_ADDR_ADPCM_RESAMPLED + resampledTempLen, samplesLenAdjusted + 4); break; } } if (note->finished != 0) { // ("break;" doesn't match) flags = 0; goto out; } } flags = 0; } out: if (note->needsInit == TRUE) { flags = A_INIT; note->needsInit = FALSE; } cmd = final_resample(cmd, note, bufLen * 2, resamplingRateFixedPoint, noteSamplesDmemAddrBeforeResampling, flags); if (note->headsetPanRight != 0 || note->prevHeadsetPanRight != 0) { s0 = 1; } else if (note->headsetPanLeft != 0 || note->prevHeadsetPanLeft != 0) { s0 = 2; } else { s0 = 0; } cmd = process_envelope(cmd, note, bufLen, 0, s0, flags); if (note->usesHeadsetPanEffects) { cmd = note_apply_headset_pan_effects(cmd, note, bufLen * 2, flags, s0); } } } 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)); return cmd; } #elif defined(VERSION_JP) GLOBAL_ASM("asm/non_matchings/synthesis_process_notes_jp.s") #else GLOBAL_ASM("asm/non_matchings/synthesis_process_notes_us.s") #endif 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)); // interesting that it's 128... aLoadBuffer(cmd++, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->samples)); note->samplePosInt = (note->sampleCount - 1) & note->samplePosInt; 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; } u64 *final_resample(u64 *cmd, struct Note *note, s32 count, u16 pitch, u16 dmemIn, u32 flags) { aSetBuffer(cmd++, /*flags*/ 0, dmemIn, /*dmemout*/ 0, count); aResample(cmd++, flags, pitch, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->finalResampleState)); return cmd; } 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; // 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); } } if (vol->sourceLeft == vol->targetLeft && vol->sourceRight == vol->targetRight && !note->envMixerNeedsInit) { mixerFlags = A_CONTINUE; } else { mixerFlags = A_INIT; rampLeft = get_volume_ramping(vol->sourceLeft, vol->targetLeft, nSamples); rampRight = get_volume_ramping(vol->sourceRight, vol->targetRight, nSamples); // The operation's parameters change meanings depending on flags 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->reverbVol); } if (gSynthesisReverb.useReverb && note->reverb) { aEnvMixer(cmd++, mixerFlags | A_AUX, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->mixEnvelopeState)); 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 { aEnvMixer(cmd++, mixerFlags, VIRTUAL_TO_PHYSICAL2(note->synthesisBuffers->mixEnvelopeState)); 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; } u64 *note_apply_headset_pan_effects(u64 *cmd, struct Note *note, s32 bufLen, s32 flags, s32 leftRight) { u16 dest; u16 prevPanShift; u16 panShift; u16 pitch; // t2 UNUSED s32 padding[11]; switch (leftRight) { case 1: dest = DMEM_ADDR_LEFT_CH; note->prevHeadsetPanLeft = 0; panShift = note->headsetPanRight; prevPanShift = note->prevHeadsetPanRight; note->prevHeadsetPanRight = panShift; break; case 2: dest = DMEM_ADDR_RIGHT_CH; note->prevHeadsetPanRight = 0; panShift = note->headsetPanLeft; 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)); pitch = (bufLen << 0xf) / (panShift + bufLen - prevPanShift + 8); 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 { pitch = (panShift == 0) ? (bufLen << 0xf) / (bufLen - prevPanShift - 4) : (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; } void note_init_volume(struct Note *note) { note->targetVolLeft = 0; note->targetVolRight = 0; note->reverb = 0; note->reverbVol = 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 reverb) { s32 panIndex; f32 volLeft; f32 volRight; #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; smallPanIndex = MIN((s8)(pan * 10.0f), 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 = FALSE; u8 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]; } velocity = MAX(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->reverb != reverb) { note->reverb = reverb; note->reverbVol = reverb << 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; }