diff options
| author | engineer124 <47598039+engineer124@users.noreply.github.com> | 2022-08-28 11:33:38 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-08-28 17:33:38 +0200 |
| commit | e25bb1485aecff500b4860a62a34d27416c42869 (patch) | |
| tree | 2941a7ca6b8362c03cd37b1e5b54efe2cdc396be /src/code | |
| parent | b24b8ad096315873bd0f884ce9898d96b4786025 (diff) | |
Document the Haas Effect (#1302)
* Document Haas Effect
* More docs
* cleanup
* add comments
* Adjust comment
* More cleanup
* Fix
* Add description of the Haas Effect
* roman suggestion
Diffstat (limited to 'src/code')
| -rw-r--r-- | src/code/audio_data.c | 2 | ||||
| -rw-r--r-- | src/code/audio_playback.c | 26 | ||||
| -rw-r--r-- | src/code/audio_synthesis.c | 183 |
3 files changed, 125 insertions, 86 deletions
diff --git a/src/code/audio_data.c b/src/code/audio_data.c index 7aec37494..10ddc4601 100644 --- a/src/code/audio_data.c +++ b/src/code/audio_data.c @@ -556,7 +556,7 @@ NoteSubEu gDefaultNoteSub = { { 1, 1, 0, 0, 0, 0, 0, 0 }, { 0 }, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -u16 gHeadsetPanQuantization[64] = { +u16 gHaasEffectDelaySizes[64] = { 30 * SAMPLE_SIZE, 29 * SAMPLE_SIZE, 28 * SAMPLE_SIZE, diff --git a/src/code/audio_playback.c b/src/code/audio_playback.c index 571a3a0c4..87ad945a6 100644 --- a/src/code/audio_playback.c +++ b/src/code/audio_playback.c @@ -1,8 +1,9 @@ #include "global.h" void Audio_InitNoteSub(Note* note, NoteSubEu* sub, NoteSubAttributes* attrs) { - f32 volRight, volLeft; - s32 smallPanIndex; + f32 volLeft; + f32 volRight; + s32 halfPanIndex; u64 pad; u8 strongLeft; u8 strongRight; @@ -31,22 +32,22 @@ void Audio_InitNoteSub(Note* note, NoteSubEu* sub, NoteSubAttributes* attrs) { sub->bitField0.stereoHeadsetEffects = stereoData.stereoHeadsetEffects; sub->bitField0.usesHeadsetPanEffects = stereoData.usesHeadsetPanEffects; if (stereoHeadsetEffects && (gAudioContext.soundMode == SOUNDMODE_HEADSET)) { - smallPanIndex = pan >> 1; - if (smallPanIndex > 0x3F) { - smallPanIndex = 0x3F; + halfPanIndex = pan >> 1; + if (halfPanIndex > 0x3F) { + halfPanIndex = 0x3F; } - sub->headsetPanLeft = gHeadsetPanQuantization[smallPanIndex]; - sub->headsetPanRight = gHeadsetPanQuantization[0x3F - smallPanIndex]; - sub->bitField1.usesHeadsetPanEffects2 = true; + sub->haasEffectRightDelaySize = gHaasEffectDelaySizes[halfPanIndex]; + sub->haasEffectLeftDelaySize = gHaasEffectDelaySizes[0x3F - halfPanIndex]; + sub->bitField1.useHaasEffect = true; volLeft = gHeadsetPanVolume[pan]; volRight = gHeadsetPanVolume[0x7F - pan]; } else if (stereoHeadsetEffects && (gAudioContext.soundMode == SOUNDMODE_STEREO)) { strongLeft = strongRight = 0; - sub->headsetPanRight = 0; - sub->headsetPanLeft = 0; - sub->bitField1.usesHeadsetPanEffects2 = false; + sub->haasEffectLeftDelaySize = 0; + sub->haasEffectRightDelaySize = 0; + sub->bitField1.useHaasEffect = false; volLeft = gStereoPanVolume[pan]; volRight = gStereoPanVolume[0x7F - pan]; @@ -945,6 +946,7 @@ void Audio_NoteInitAll(void) { note->playbackState.portamento.speed = 0; note->playbackState.stereoHeadsetEffects = false; note->startSamplePos = 0; - note->synthesisState.synthesisBuffers = AudioHeap_AllocDmaMemory(&gAudioContext.miscPool, 0x1E0); + note->synthesisState.synthesisBuffers = + AudioHeap_AllocDmaMemory(&gAudioContext.miscPool, sizeof(NoteSynthesisBuffers)); } } diff --git a/src/code/audio_synthesis.c b/src/code/audio_synthesis.c index 851ae56d9..a00c57ffa 100644 --- a/src/code/audio_synthesis.c +++ b/src/code/audio_synthesis.c @@ -4,7 +4,7 @@ // DMEM Addresses for the RSP #define DMEM_TEMP 0x3C0 #define DMEM_UNCOMPRESSED_NOTE 0x580 -#define DMEM_NOTE_PAN_TEMP 0x5C0 +#define DMEM_HAAS_TEMP 0x5C0 #define DMEM_SCRATCH2 0x760 // = DMEM_TEMP + DMEM_2CH_SIZE + a bit more #define DMEM_COMPRESSED_ADPCM_DATA 0x940 // = DMEM_LEFT_CH #define DMEM_LEFT_CH 0x940 @@ -14,6 +14,12 @@ #define DMEM_WET_LEFT_CH 0xC80 #define DMEM_WET_RIGHT_CH 0xE20 // = DMEM_WET_LEFT_CH + DMEM_1CH_SIZE +typedef enum { + /* 0 */ HAAS_EFFECT_DELAY_NONE, + /* 1 */ HAAS_EFFECT_DELAY_LEFT, // Delay left channel so that right channel is heard first + /* 2 */ HAAS_EFFECT_DELAY_RIGHT // Delay right channel so that left channel is heard first +} HaasEffectDelaySide; + Acmd* AudioSynth_LoadRingBufferPart(Acmd* cmd, u16 dmem, u16 startPos, s32 size, SynthesisReverb* reverb); Acmd* AudioSynth_SaveBufferOffset(Acmd* cmd, u16 dmem, u16 offset, s32 size, s16* buf); Acmd* AudioSynth_SaveRingBufferPart(Acmd* cmd, u16 dmem, u16 startPos, s32 size, SynthesisReverb* reverb); @@ -21,17 +27,25 @@ Acmd* AudioSynth_DoOneAudioUpdate(s16* aiBuf, s32 aiBufLen, Acmd* cmd, s32 updat Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisState* synthState, s16* aiBuf, s32 aiBufLen, Acmd* cmd, s32 updateIndex); Acmd* AudioSynth_LoadWaveSamples(Acmd* cmd, NoteSubEu* noteSubEu, NoteSynthesisState* synthState, s32 numSamplesToLoad); -Acmd* AudioSynth_NoteApplyHeadsetPanEffects(Acmd* cmd, NoteSubEu* noteSubEu, NoteSynthesisState* synthState, s32 bufLen, - s32 flags, s32 side); +Acmd* AudioSynth_ApplyHaasEffect(Acmd* cmd, NoteSubEu* noteSubEu, NoteSynthesisState* synthState, s32 size, s32 flags, + s32 haasEffectDelaySide); Acmd* AudioSynth_ProcessEnvelope(Acmd* cmd, NoteSubEu* noteSubEu, NoteSynthesisState* synthState, s32 aiBufLen, - u16 inBuf, s32 headsetPanSettings, s32 flags); + u16 dmemSrc, s32 haasEffectDelaySide, s32 flags); Acmd* AudioSynth_FinalResample(Acmd* cmd, NoteSynthesisState* synthState, s32 size, u16 pitch, u16 inpDmem, s32 resampleFlags); -u32 D_801304A0 = _SHIFTL(A_ENVMIXER, 24, 8); -u32 D_801304A4 = MK_CMD(DMEM_NOTE_PAN_TEMP >> 4, DMEM_RIGHT_CH >> 4, DMEM_WET_LEFT_CH >> 4, DMEM_WET_RIGHT_CH >> 4); -u32 D_801304A8 = MK_CMD(DMEM_LEFT_CH >> 4, DMEM_NOTE_PAN_TEMP >> 4, DMEM_WET_LEFT_CH >> 4, DMEM_WET_RIGHT_CH >> 4); -u32 D_801304AC = MK_CMD(DMEM_LEFT_CH >> 4, DMEM_RIGHT_CH >> 4, DMEM_WET_LEFT_CH >> 4, DMEM_WET_RIGHT_CH >> 4); +u32 sEnvMixerOp = _SHIFTL(A_ENVMIXER, 24, 8); + +// Store the left dry channel in a temp space to be delayed to produce the haas effect +u32 sEnvMixerLeftHaasDmemDests = + MK_CMD(DMEM_HAAS_TEMP >> 4, DMEM_RIGHT_CH >> 4, DMEM_WET_LEFT_CH >> 4, DMEM_WET_RIGHT_CH >> 4); + +// Store the right dry channel in a temp space to be delayed to produce the haas effect +u32 sEnvMixerRightHaasDmemDests = + MK_CMD(DMEM_LEFT_CH >> 4, DMEM_HAAS_TEMP >> 4, DMEM_WET_LEFT_CH >> 4, DMEM_WET_RIGHT_CH >> 4); + +u32 sEnvMixerDefaultDmemDests = + MK_CMD(DMEM_LEFT_CH >> 4, DMEM_RIGHT_CH >> 4, DMEM_WET_LEFT_CH >> 4, DMEM_WET_RIGHT_CH >> 4); u16 D_801304B0[] = { 0x7FFF, 0xD001, 0x3FFF, 0xF001, 0x5FFF, 0x9001, 0x7FFF, 0x8001, @@ -723,7 +737,7 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS s32 nParts; s32 curPart; s32 sampleDataStartPad; - s32 side; + s32 haasEffectDelaySide; s32 resampledTempLen; u16 sampleDmemBeforeResampling; s32 sampleDataOffset; @@ -752,8 +766,8 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS synthState->samplePosFrac = 0; synthState->curVolLeft = 0; synthState->curVolRight = 0; - synthState->prevHeadsetPanRight = 0; - synthState->prevHeadsetPanLeft = 0; + synthState->prevHaasEffectLeftDelaySize = 0; + synthState->prevHaasEffectRightDelaySize = 0; synthState->reverbVol = noteSubEu->reverbVol; synthState->numParts = 0; synthState->unk_1A = 1; @@ -1078,7 +1092,7 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS unk7 = noteSubEu->unk_07; unkE = noteSubEu->unk_0E; - buf = &synthState->synthesisBuffers->panSamplesBuffer[0x18]; + buf = synthState->synthesisBuffers->unkState; if (unk7 != 0 && noteSubEu->unk_0E != 0) { AudioSynth_DMemMove(cmd++, DMEM_TEMP, DMEM_SCRATCH2, aiBufLen * SAMPLE_SIZE); thing = DMEM_SCRATCH2 - unk7; @@ -1095,21 +1109,24 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS synthState->unk_1A = 1; } - if (noteSubEu->headsetPanRight != 0 || synthState->prevHeadsetPanRight != 0) { - side = 1; - } else if (noteSubEu->headsetPanLeft != 0 || synthState->prevHeadsetPanLeft != 0) { - side = 2; + if ((noteSubEu->haasEffectLeftDelaySize != 0) || (synthState->prevHaasEffectLeftDelaySize != 0)) { + haasEffectDelaySide = HAAS_EFFECT_DELAY_LEFT; + } else if ((noteSubEu->haasEffectRightDelaySize != 0) || (synthState->prevHaasEffectRightDelaySize != 0)) { + haasEffectDelaySide = HAAS_EFFECT_DELAY_RIGHT; } else { - side = 0; + haasEffectDelaySide = HAAS_EFFECT_DELAY_NONE; } - cmd = AudioSynth_ProcessEnvelope(cmd, noteSubEu, synthState, aiBufLen, DMEM_TEMP, side, flags); - if (noteSubEu->bitField1.usesHeadsetPanEffects2) { + + cmd = AudioSynth_ProcessEnvelope(cmd, noteSubEu, synthState, aiBufLen, DMEM_TEMP, haasEffectDelaySide, flags); + + if (noteSubEu->bitField1.useHaasEffect) { if (!(flags & A_INIT)) { flags = A_CONTINUE; } - cmd = - AudioSynth_NoteApplyHeadsetPanEffects(cmd, noteSubEu, synthState, aiBufLen * (s32)SAMPLE_SIZE, flags, side); + cmd = AudioSynth_ApplyHaasEffect(cmd, noteSubEu, synthState, aiBufLen * (s32)SAMPLE_SIZE, flags, + haasEffectDelaySide); } + return cmd; } @@ -1125,8 +1142,8 @@ Acmd* AudioSynth_FinalResample(Acmd* cmd, NoteSynthesisState* synthState, s32 si } Acmd* AudioSynth_ProcessEnvelope(Acmd* cmd, NoteSubEu* noteSubEu, NoteSynthesisState* synthState, s32 aiBufLen, - u16 inBuf, s32 headsetPanSettings, s32 flags) { - u32 phi_a1; + u16 dmemSrc, s32 haasEffectDelaySide, s32 flags) { + u32 dmemDests; u16 curVolLeft; u16 targetVolLeft; s32 phi_t1; @@ -1171,30 +1188,36 @@ Acmd* AudioSynth_ProcessEnvelope(Acmd* cmd, NoteSubEu* noteSubEu, NoteSynthesisS synthState->curVolLeft = curVolLeft + (rampLeft * (aiBufLen >> 3)); synthState->curVolRight = curVolRight + (rampRight * (aiBufLen >> 3)); - if (noteSubEu->bitField1.usesHeadsetPanEffects2) { - AudioSynth_ClearBuffer(cmd++, DMEM_NOTE_PAN_TEMP, DMEM_1CH_SIZE); + if (noteSubEu->bitField1.useHaasEffect) { + AudioSynth_ClearBuffer(cmd++, DMEM_HAAS_TEMP, DMEM_1CH_SIZE); AudioSynth_EnvSetup1(cmd++, phi_t1 * 2, rampReverb, rampLeft, rampRight); AudioSynth_EnvSetup2(cmd++, curVolLeft, curVolRight); - switch (headsetPanSettings) { - case 1: - phi_a1 = D_801304A4; + + switch (haasEffectDelaySide) { + case HAAS_EFFECT_DELAY_LEFT: + // Store the left dry channel in a temp space to be delayed to produce the haas effect + dmemDests = sEnvMixerLeftHaasDmemDests; break; - case 2: - phi_a1 = D_801304A8; + + case HAAS_EFFECT_DELAY_RIGHT: + // Store the right dry channel in a temp space to be delayed to produce the haas effect + dmemDests = sEnvMixerRightHaasDmemDests; break; - default: - phi_a1 = D_801304AC; + + default: // HAAS_EFFECT_DELAY_NONE + dmemDests = sEnvMixerDefaultDmemDests; break; } } else { aEnvSetup1(cmd++, phi_t1 * 2, rampReverb, rampLeft, rampRight); aEnvSetup2(cmd++, curVolLeft, curVolRight); - phi_a1 = D_801304AC; + dmemDests = sEnvMixerDefaultDmemDests; } - aEnvMixer(cmd++, inBuf, aiBufLen, (sourceReverbVol & 0x80) >> 7, noteSubEu->bitField0.stereoHeadsetEffects, + aEnvMixer(cmd++, dmemSrc, aiBufLen, (sourceReverbVol & 0x80) >> 7, noteSubEu->bitField0.stereoHeadsetEffects, noteSubEu->bitField0.usesHeadsetPanEffects, noteSubEu->bitField0.stereoStrongRight, - noteSubEu->bitField0.stereoStrongLeft, phi_a1, D_801304A0); + noteSubEu->bitField0.stereoStrongLeft, dmemDests, sEnvMixerOp); + return cmd; } @@ -1242,61 +1265,75 @@ Acmd* AudioSynth_LoadWaveSamples(Acmd* cmd, NoteSubEu* noteSubEu, NoteSynthesisS return cmd; } -Acmd* AudioSynth_NoteApplyHeadsetPanEffects(Acmd* cmd, NoteSubEu* noteSubEu, NoteSynthesisState* synthState, s32 bufLen, - s32 flags, s32 side) { - u16 dest; +/** + * The Haas Effect gives directionality to sound by appling a small (< 35ms) delay to either the left or right channel. + * The delay is small enough that the sound is still perceived as one sound, but the channel that is not delayed will + * reach our ear first and give a sense of directionality. The sound is directed towards the opposite side of the delay. + */ +Acmd* AudioSynth_ApplyHaasEffect(Acmd* cmd, NoteSubEu* noteSubEu, NoteSynthesisState* synthState, s32 size, s32 flags, + s32 haasEffectDelaySide) { + u16 dmemDest; u16 pitch; - u8 prevPanShift; - u8 panShift; - - switch (side) { - case 1: - dest = DMEM_LEFT_CH; - panShift = noteSubEu->headsetPanRight; - prevPanShift = synthState->prevHeadsetPanRight; - synthState->prevHeadsetPanLeft = 0; - synthState->prevHeadsetPanRight = panShift; + u8 prevHaasEffectDelaySize; + u8 haasEffectDelaySize; + + switch (haasEffectDelaySide) { + case HAAS_EFFECT_DELAY_LEFT: + // Delay the sample on the left channel + // This allows the right channel to be heard first + dmemDest = DMEM_LEFT_CH; + haasEffectDelaySize = noteSubEu->haasEffectLeftDelaySize; + prevHaasEffectDelaySize = synthState->prevHaasEffectLeftDelaySize; + synthState->prevHaasEffectRightDelaySize = 0; + synthState->prevHaasEffectLeftDelaySize = haasEffectDelaySize; break; - case 2: - dest = DMEM_RIGHT_CH; - panShift = noteSubEu->headsetPanLeft; - prevPanShift = synthState->prevHeadsetPanLeft; - synthState->prevHeadsetPanLeft = panShift; - synthState->prevHeadsetPanRight = 0; + + case HAAS_EFFECT_DELAY_RIGHT: + // Delay the sample on the right channel + // This allows the left channel to be heard first + dmemDest = DMEM_RIGHT_CH; + haasEffectDelaySize = noteSubEu->haasEffectRightDelaySize; + prevHaasEffectDelaySize = synthState->prevHaasEffectRightDelaySize; + synthState->prevHaasEffectRightDelaySize = haasEffectDelaySize; + synthState->prevHaasEffectLeftDelaySize = 0; break; - default: + + default: // HAAS_EFFECT_DELAY_NONE return cmd; } if (flags != A_INIT) { - // Slightly adjust the sample rate in order to fit a change in pan shift - if (panShift != prevPanShift) { - pitch = (((bufLen << 0xF) / 2) - 1) / ((bufLen + panShift - prevPanShift - 2) / 2); - aSetBuffer(cmd++, 0, DMEM_NOTE_PAN_TEMP, DMEM_TEMP, bufLen + panShift - prevPanShift); + // Slightly adjust the sample rate in order to fit a change in sample delay + if (haasEffectDelaySize != prevHaasEffectDelaySize) { + pitch = (((size << 0xF) / 2) - 1) / ((size + haasEffectDelaySize - prevHaasEffectDelaySize - 2) / 2); + aSetBuffer(cmd++, 0, DMEM_HAAS_TEMP, DMEM_TEMP, size + haasEffectDelaySize - prevHaasEffectDelaySize); aResampleZoh(cmd++, pitch, 0); } else { - aDMEMMove(cmd++, DMEM_NOTE_PAN_TEMP, DMEM_TEMP, bufLen); + aDMEMMove(cmd++, DMEM_HAAS_TEMP, DMEM_TEMP, size); } - if (prevPanShift != 0) { - aLoadBuffer(cmd++, &synthState->synthesisBuffers->panResampleState[0x8], DMEM_NOTE_PAN_TEMP, - ALIGN16(prevPanShift)); - aDMEMMove(cmd++, DMEM_TEMP, DMEM_NOTE_PAN_TEMP + prevPanShift, bufLen + panShift - prevPanShift); + if (prevHaasEffectDelaySize != 0) { + aLoadBuffer(cmd++, synthState->synthesisBuffers->haasEffectDelayState, DMEM_HAAS_TEMP, + ALIGN16(prevHaasEffectDelaySize)); + aDMEMMove(cmd++, DMEM_TEMP, DMEM_HAAS_TEMP + prevHaasEffectDelaySize, + size + haasEffectDelaySize - prevHaasEffectDelaySize); } else { - aDMEMMove(cmd++, DMEM_TEMP, DMEM_NOTE_PAN_TEMP, bufLen + panShift); + aDMEMMove(cmd++, DMEM_TEMP, DMEM_HAAS_TEMP, size + haasEffectDelaySize); } } else { - // Just shift right - aDMEMMove(cmd++, DMEM_NOTE_PAN_TEMP, DMEM_TEMP, bufLen); - aClearBuffer(cmd++, DMEM_NOTE_PAN_TEMP, panShift); - aDMEMMove(cmd++, DMEM_TEMP, DMEM_NOTE_PAN_TEMP + panShift, bufLen); + // Just apply a delay directly + aDMEMMove(cmd++, DMEM_HAAS_TEMP, DMEM_TEMP, size); + aClearBuffer(cmd++, DMEM_HAAS_TEMP, haasEffectDelaySize); + aDMEMMove(cmd++, DMEM_TEMP, DMEM_HAAS_TEMP + haasEffectDelaySize, size); } - if (panShift) { + if (haasEffectDelaySize) { // != 0 // Save excessive samples for next iteration - aSaveBuffer(cmd++, DMEM_NOTE_PAN_TEMP + bufLen, &synthState->synthesisBuffers->panResampleState[0x8], - ALIGN16(panShift)); + aSaveBuffer(cmd++, DMEM_HAAS_TEMP + size, synthState->synthesisBuffers->haasEffectDelayState, + ALIGN16(haasEffectDelaySize)); } - aAddMixer(cmd++, ALIGN64(bufLen), DMEM_NOTE_PAN_TEMP, dest, 0x7FFF); + + aAddMixer(cmd++, ALIGN64(size), DMEM_HAAS_TEMP, dmemDest, 0x7FFF); + return cmd; } |
