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