/* FAudio - XAudio Reimplementation for FNA * * Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team * * This software is provided 'as-is', without any express or implied warranty. * In no event will the authors be held liable for any damages arising from * the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software in a * product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * * Ethan "flibitijibibo" Lee * */ #include "FAudioFX.h" #include "FAudio_internal.h" /* #define DISABLE_SUBNORMALS */ #ifdef DISABLE_SUBNORMALS #include /* ONLY USE THIS FOR fpclassify/_fpclass! */ /* VS2010 doesn't define fpclassify (which is C99), so here it is. */ #if defined(_MSC_VER) && !defined(fpclassify) #define IS_SUBNORMAL(a) (_fpclass(a) & (_FPCLASS_ND | _FPCLASS_PD)) #else #define IS_SUBNORMAL(a) (fpclassify(a) == FP_SUBNORMAL) #endif #endif /* DISABLE_SUBNORMALS */ /* Utility Functions */ static inline float DbGainToFactor(float gain) { return (float) FAudio_pow(10, gain / 20.0f); } static inline uint32_t MsToSamples(float msec, int32_t sampleRate) { return (uint32_t) ((sampleRate * msec) / 1000.0f); } #ifndef DISABLE_SUBNORMALS #define Undenormalize(a) ((a)) #else /* DISABLE_SUBNORMALS */ static inline float Undenormalize(float sample_in) { if (IS_SUBNORMAL(sample_in)) { return 0.0f; } return sample_in; } #endif /* DISABLE_SUBNORMALS */ /* Component - Delay */ #define DSP_DELAY_MAX_DELAY_MS 300 typedef struct DspDelay { int32_t sampleRate; uint32_t capacity; /* In samples */ uint32_t delay; /* In samples */ uint32_t read_idx; uint32_t write_idx; float *buffer; } DspDelay; static inline void DspDelay_Initialize( DspDelay *filter, int32_t sampleRate, float delay_ms, FAudioMallocFunc pMalloc ) { FAudio_assert(delay_ms >= 0 && delay_ms <= DSP_DELAY_MAX_DELAY_MS); filter->sampleRate = sampleRate; filter->capacity = MsToSamples(DSP_DELAY_MAX_DELAY_MS, sampleRate); filter->delay = MsToSamples(delay_ms, sampleRate); filter->read_idx = 0; filter->write_idx = filter->delay; filter->buffer = (float*) pMalloc(filter->capacity * sizeof(float)); FAudio_zero(filter->buffer, filter->capacity * sizeof(float)); } static inline void DspDelay_Change(DspDelay *filter, float delay_ms) { FAudio_assert(delay_ms >= 0 && delay_ms <= DSP_DELAY_MAX_DELAY_MS); /* Length */ filter->delay = MsToSamples(delay_ms, filter->sampleRate); filter->read_idx = (filter->write_idx - filter->delay + filter->capacity) % filter->capacity; } static inline float DspDelay_Read(DspDelay *filter) { float delay_out; FAudio_assert(filter->read_idx < filter->capacity); delay_out = filter->buffer[filter->read_idx]; filter->read_idx = (filter->read_idx + 1) % filter->capacity; return delay_out; } static inline void DspDelay_Write(DspDelay *filter, float sample) { FAudio_assert(filter->write_idx < filter->capacity); filter->buffer[filter->write_idx] = sample; filter->write_idx = (filter->write_idx + 1) % filter->capacity; } static inline float DspDelay_Process(DspDelay *filter, float sample_in) { float delay_out = DspDelay_Read(filter); DspDelay_Write(filter, sample_in); return delay_out; } static inline float DspDelay_Tap(DspDelay *filter, uint32_t delay) { FAudio_assert(delay <= filter->delay); return filter->buffer[(filter->write_idx - delay + filter->capacity) % filter->capacity]; } static inline void DspDelay_Reset(DspDelay *filter) { filter->read_idx = 0; filter->write_idx = filter->delay; FAudio_zero(filter->buffer, filter->capacity * sizeof(float)); } static inline void DspDelay_Destroy(DspDelay *filter, FAudioFreeFunc pFree) { pFree(filter->buffer); } static inline float DspComb_FeedbackFromRT60(DspDelay *delay, float rt60_ms) { float exponent; if (rt60_ms == 0) { return 0; } exponent = ( (-3.0f * delay->delay * 1000.0f) / (delay->sampleRate * rt60_ms) ); return (float) FAudio_pow(10.0f, exponent); } /* Component - Bi-Quad Filter */ typedef enum DspBiQuadType { DSP_BIQUAD_LOWSHELVING, DSP_BIQUAD_HIGHSHELVING } DspBiQuadType; typedef struct DspBiQuad { int32_t sampleRate; float a0, a1, a2; float b1, b2; float c0, d0; float delay0, delay1; } DspBiQuad; static inline void DspBiQuad_Change( DspBiQuad *filter, DspBiQuadType type, float frequency, float q, float gain ) { const float TWOPI = 6.283185307179586476925286766559005; float theta_c = (TWOPI * frequency) / (float) filter->sampleRate; float mu = DbGainToFactor(gain); float beta = (type == DSP_BIQUAD_LOWSHELVING) ? 4.0f / (1 + mu) : (1 + mu) / 4.0f; float delta = beta * (float) FAudio_tan(theta_c * 0.5f); float gamma = (1 - delta) / (1 + delta); if (type == DSP_BIQUAD_LOWSHELVING) { filter->a0 = (1 - gamma) * 0.5f; filter->a1 = filter->a0; } else { filter->a0 = (1 + gamma) * 0.5f; filter->a1 = -filter->a0; } filter->a2 = 0.0f; filter->b1 = -gamma; filter->b2 = 0.0f; filter->c0 = mu - 1.0f; filter->d0 = 1.0f; } static inline void DspBiQuad_Initialize( DspBiQuad *filter, int32_t sampleRate, DspBiQuadType type, float frequency, /* Corner frequency */ float q, /* Only used by low/high-pass filters */ float gain /* Only used by low/high-shelving filters */ ) { filter->sampleRate = sampleRate; filter->delay0 = 0.0f; filter->delay1 = 0.0f; DspBiQuad_Change(filter, type, frequency, q, gain); } static inline float DspBiQuad_Process(DspBiQuad *filter, float sample_in) { /* Direct Form II Transposed: * - Less delay registers than Direct Form I * - More numerically stable than Direct Form II */ float result = (filter->a0 * sample_in) + filter->delay0; filter->delay0 = (filter->a1 * sample_in) - (filter->b1 * result) + filter->delay1; filter->delay1 = (filter->a2 * sample_in) - (filter->b2 * result); return Undenormalize( (result * filter->c0) + (sample_in * filter->d0) ); } static inline void DspBiQuad_Reset(DspBiQuad *filter) { filter->delay0 = 0.0f; filter->delay1 = 0.0f; } static inline void DspBiQuad_Destroy(DspBiQuad *filter) { } /* Component - Comb Filter with Integrated Low/High Shelving Filters */ typedef struct DspCombShelving { DspDelay comb_delay; float comb_feedback_gain; DspBiQuad low_shelving; DspBiQuad high_shelving; } DspCombShelving; static inline void DspCombShelving_Initialize( DspCombShelving *filter, int32_t sampleRate, float delay_ms, float rt60_ms, float low_frequency, float low_gain, float high_frequency, float high_gain, FAudioMallocFunc pMalloc ) { DspDelay_Initialize(&filter->comb_delay, sampleRate, delay_ms, pMalloc); filter->comb_feedback_gain = DspComb_FeedbackFromRT60( &filter->comb_delay, rt60_ms ); DspBiQuad_Initialize( &filter->low_shelving, sampleRate, DSP_BIQUAD_LOWSHELVING, low_frequency, 0.0f, low_gain ); DspBiQuad_Initialize( &filter->high_shelving, sampleRate, DSP_BIQUAD_HIGHSHELVING, high_frequency, 0.0f, high_gain ); } static inline float DspCombShelving_Process( DspCombShelving *filter, float sample_in ) { float delay_out, feedback, to_buf; delay_out = DspDelay_Read(&filter->comb_delay); /* Apply shelving filters */ feedback = DspBiQuad_Process(&filter->high_shelving, delay_out); feedback = DspBiQuad_Process(&filter->low_shelving, feedback); /* Apply comb filter */ to_buf = Undenormalize(sample_in + (filter->comb_feedback_gain * feedback)); DspDelay_Write(&filter->comb_delay, to_buf); return delay_out; } static inline void DspCombShelving_Reset(DspCombShelving *filter) { DspDelay_Reset(&filter->comb_delay); DspBiQuad_Reset(&filter->low_shelving); DspBiQuad_Reset(&filter->high_shelving); } static inline void DspCombShelving_Destroy( DspCombShelving *filter, FAudioFreeFunc pFree ) { DspDelay_Destroy(&filter->comb_delay, pFree); DspBiQuad_Destroy(&filter->low_shelving); DspBiQuad_Destroy(&filter->high_shelving); } /* Component - Delaying All-Pass Filter */ typedef struct DspAllPass { DspDelay delay; float feedback_gain; } DspAllPass; static inline void DspAllPass_Initialize( DspAllPass *filter, int32_t sampleRate, float delay_ms, float gain, FAudioMallocFunc pMalloc ) { DspDelay_Initialize(&filter->delay, sampleRate, delay_ms, pMalloc); filter->feedback_gain = gain; } static inline void DspAllPass_Change(DspAllPass *filter, float delay_ms, float gain) { DspDelay_Change(&filter->delay, delay_ms); filter->feedback_gain = gain; } static inline float DspAllPass_Process(DspAllPass *filter, float sample_in) { float delay_out, to_buf; delay_out = DspDelay_Read(&filter->delay); to_buf = Undenormalize(sample_in + (filter->feedback_gain * delay_out)); DspDelay_Write(&filter->delay, to_buf); return Undenormalize(delay_out - (filter->feedback_gain * to_buf)); } static inline void DspAllPass_Reset(DspAllPass *filter) { DspDelay_Reset(&filter->delay); } static inline void DspAllPass_Destroy(DspAllPass *filter, FAudioFreeFunc pFree) { DspDelay_Destroy(&filter->delay, pFree); } /* Reverb network - loosely based on the reverberator from "Designing Audio Effect Plug-Ins in C++" by Will Pirkle and the classic classic Schroeder-Moorer reverberator with modifications to fit the XAudio2FX parameters. In +--------+ +----+ +------------+ +-----+ ----|--->PreDelay---->APF1---+--->Sub LeftCh |----->| | Left Out | +--------+ +----+ | +------------+ | Wet |--------> | | +------------+ | | | |---|Sub RightCh |----->| Dry | | +------------+ | | Right Out | | Mix |--------> +----------------------------------------------->| | +-----+ Sub routine per channel : In +-----+ +-----+ * cg ---+->|Delay|--+---|Comb1|------+ | +-----+ | +-----+ | | | | | | +-----+ * cg | | +--->Comb2|------+ | | +-----+ | +-----+ | | +---->| SUM |--------+ | | +-----+ * cg | +-----+ | | +--->... |------+ | | * g0 | +-----+ | | | | | | | +--->-----+ * cg | | | |Comb8|------+ | | +-----+ | v | +-----+ g1 +----+ +----+ +----+ +----+ | | SUM |<------|APF4|<--|APF3|<--|APF2|<--|APF1|<-----+ +-----+ +----+ +----+ +----+ +----+ | | | +-------------+ Out +----------->|RoomFilter |------------------------> +-------------+ Parameters: float WetDryMix; 0 - 100 (0 = fully dry, 100 = fully wet) uint32_t ReflectionsDelay; 0 - 300 ms uint8_t ReverbDelay; 0 - 85 ms uint8_t RearDelay; 0 - 5 ms uint8_t PositionLeft; 0 - 30 uint8_t PositionRight; 0 - 30 uint8_t PositionMatrixLeft; 0 - 30 uint8_t PositionMatrixRight; 0 - 30 uint8_t EarlyDiffusion; 0 - 15 uint8_t LateDiffusion; 0 - 15 uint8_t LowEQGain; 0 - 12 (formula dB = LowEQGain - 8) uint8_t LowEQCutoff; 0 - 9 (formula Hz = 50 + (LowEQCutoff * 50)) uint8_t HighEQGain; 0 - 8 (formula dB = HighEQGain - 8) uint8_t HighEQCutoff; 0 - 14 (formula Hz = 1000 + (HighEqCutoff * 500)) float RoomFilterFreq; 20 - 20000Hz float RoomFilterMain; -100 - 0dB float RoomFilterHF; -100 - 0dB float ReflectionsGain; -100 - 20dB float ReverbGain; -100 - 20dB float DecayTime; 0.1 - .... ms float Density; 0 - 100 % float RoomSize; 1 - 100 feet (NOT USED YET) */ #define REVERB_COUNT_COMB 8 #define REVERB_COUNT_APF_IN 1 #define REVERB_COUNT_APF_OUT 4 static float COMB_DELAYS[REVERB_COUNT_COMB] = { 25.31f, 26.94f, 28.96f, 30.75f, 32.24f, 33.80f, 35.31f, 36.67f }; static float APF_IN_DELAYS[REVERB_COUNT_APF_IN] = { 13.28f, /* 28.13f */ }; static float APF_OUT_DELAYS[REVERB_COUNT_APF_OUT] = { 5.10f, 12.61f, 10.0f, 7.73f }; static const float STEREO_SPREAD[4] = { 0.0f, 0.5216f, 0.0f, 0.5216f }; typedef struct DspReverbChannel { DspDelay reverb_delay; DspCombShelving lpf_comb[REVERB_COUNT_COMB]; DspAllPass apf_out[REVERB_COUNT_APF_OUT]; DspBiQuad room_high_shelf; float early_gain; float gain; } DspReverbChannel; typedef struct DspReverb { DspDelay early_delay; DspAllPass apf_in[REVERB_COUNT_APF_IN]; int32_t in_channels; int32_t out_channels; int32_t reverb_channels; DspReverbChannel channel[4]; float early_gain; float reverb_gain; float room_gain; float wet_ratio; float dry_ratio; } DspReverb; static inline void DspReverb_Create( DspReverb *reverb, int32_t sampleRate, int32_t in_channels, int32_t out_channels, FAudioMallocFunc pMalloc ) { int32_t i, c; FAudio_assert(in_channels == 1 || in_channels == 2); FAudio_assert(out_channels == 1 || out_channels == 2 || out_channels == 6); FAudio_zero(reverb, sizeof(DspReverb)); DspDelay_Initialize(&reverb->early_delay, sampleRate, 10, pMalloc); for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) { DspAllPass_Initialize( &reverb->apf_in[i], sampleRate, APF_IN_DELAYS[i], 0.5f, pMalloc ); } reverb->reverb_channels = (out_channels == 6) ? 4 : out_channels; for (c = 0; c < reverb->reverb_channels; c += 1) { DspDelay_Initialize( &reverb->channel[c].reverb_delay, sampleRate, 10, pMalloc ); for (i = 0; i < REVERB_COUNT_COMB; i += 1) { DspCombShelving_Initialize( &reverb->channel[c].lpf_comb[i], sampleRate, COMB_DELAYS[i] + STEREO_SPREAD[c], 500, 500, -6, 5000, -6, pMalloc ); } for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) { DspAllPass_Initialize( &reverb->channel[c].apf_out[i], sampleRate, APF_OUT_DELAYS[i] + STEREO_SPREAD[c], 0.5f, pMalloc ); } DspBiQuad_Initialize( &reverb->channel[c].room_high_shelf, sampleRate, DSP_BIQUAD_HIGHSHELVING, 5000, 0, -10 ); reverb->channel[c].gain = 1.0f; } reverb->early_gain = 1.0f; reverb->reverb_gain = 1.0f; reverb->dry_ratio = 0.0f; reverb->wet_ratio = 1.0f; reverb->in_channels = in_channels; reverb->out_channels = out_channels; } static inline void DspReverb_Destroy(DspReverb *reverb, FAudioFreeFunc pFree) { int32_t i, c; DspDelay_Destroy(&reverb->early_delay, pFree); for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) { DspAllPass_Destroy(&reverb->apf_in[i], pFree); } for (c = 0; c < reverb->reverb_channels; c += 1) { DspDelay_Destroy(&reverb->channel[c].reverb_delay, pFree); for (i = 0; i < REVERB_COUNT_COMB; i += 1) { DspCombShelving_Destroy( &reverb->channel[c].lpf_comb[i], pFree ); } DspBiQuad_Destroy(&reverb->channel[c].room_high_shelf); for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) { DspAllPass_Destroy( &reverb->channel[c].apf_out[i], pFree ); } } } static inline void DspReverb_SetParameters( DspReverb *reverb, FAudioFXReverbParameters *params ) { float early_diffusion, late_diffusion; float channel_delay[4] = { 0.0f, 0.0f, params->RearDelay, params->RearDelay }; DspCombShelving *comb; int32_t i, c; /* Pre-Delay */ DspDelay_Change(&reverb->early_delay, (float) params->ReflectionsDelay); /* Early Reflections - Diffusion */ early_diffusion = 0.6f - ((params->EarlyDiffusion / 15.0f) * 0.2f); for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) { DspAllPass_Change( &reverb->apf_in[i], APF_IN_DELAYS[i], early_diffusion ); } /* Reverberation */ for (c = 0; c < reverb->reverb_channels; c += 1) { DspDelay_Change( &reverb->channel[c].reverb_delay, (float) params->ReverbDelay + channel_delay[c] ); for (i = 0; i < REVERB_COUNT_COMB; i += 1) { comb = &reverb->channel[c].lpf_comb[i]; /* Set decay time of comb filter */ DspDelay_Change( &comb->comb_delay, COMB_DELAYS[i] + STEREO_SPREAD[c] ); comb->comb_feedback_gain = DspComb_FeedbackFromRT60( &comb->comb_delay, params->DecayTime * 1000.0f ); /* High/Low shelving */ DspBiQuad_Change( &comb->low_shelving, DSP_BIQUAD_LOWSHELVING, 50.0f + params->LowEQCutoff * 50.0f, 0.0f, params->LowEQGain - 8.0f ); DspBiQuad_Change( &comb->high_shelving, DSP_BIQUAD_HIGHSHELVING, 1000 + params->HighEQCutoff * 500.0f, 0.0f, params->HighEQGain - 8.0f ); } } /* Gain */ reverb->early_gain = DbGainToFactor(params->ReflectionsGain); reverb->reverb_gain = DbGainToFactor(params->ReverbGain); reverb->room_gain = DbGainToFactor(params->RoomFilterMain); /* Late Diffusion */ late_diffusion = 0.6f - ((params->LateDiffusion / 15.0f) * 0.2f); for (c = 0; c < reverb->reverb_channels; c += 1) { for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) { DspAllPass_Change( &reverb->channel[c].apf_out[i], APF_OUT_DELAYS[i] + STEREO_SPREAD[c], late_diffusion ); } DspBiQuad_Change( &reverb->channel[c].room_high_shelf, DSP_BIQUAD_HIGHSHELVING, params->RoomFilterFreq, 0.0f, params->RoomFilterMain + params->RoomFilterHF ); reverb->channel[c].gain = 1.5f - ( ((c % 2 == 0 ? params->PositionMatrixLeft : params->PositionMatrixRight ) / 27.0f) * 0.5f ); if (c >= 2) { /* Rear-channel Attenuation */ reverb->channel[c].gain *= 0.75f; } reverb->channel[c].early_gain = 1.2f - ( ((c % 2 == 0 ? params->PositionLeft : params->PositionRight ) / 6.0f) * 0.2f ); reverb->channel[c].early_gain = ( reverb->channel[c].early_gain * reverb->early_gain ); } /* Wet/Dry Mix (100 = fully wet, 0 = fully dry) */ reverb->wet_ratio = params->WetDryMix / 100.0f; reverb->dry_ratio = 1.0f - reverb->wet_ratio; } static inline float DspReverb_INTERNAL_ProcessEarly( DspReverb *reverb, float sample_in ) { float early; int32_t i; /* Pre-Delay */ early = DspDelay_Process(&reverb->early_delay, sample_in); /* Early Reflections */ for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) { early = DspAllPass_Process(&reverb->apf_in[i], early); } return early; } static inline float DspReverb_INTERNAL_ProcessChannel( DspReverb *reverb, DspReverbChannel *channel, float sample_in ) { float revdelay, early_late, sample_out; int32_t i; revdelay = DspDelay_Process(&channel->reverb_delay, sample_in); sample_out = 0.0f; for (i = 0; i < REVERB_COUNT_COMB; i += 1) { sample_out += DspCombShelving_Process( &channel->lpf_comb[i], revdelay ); } sample_out /= (float) REVERB_COUNT_COMB; /* Output Diffusion */ for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) { sample_out = DspAllPass_Process( &channel->apf_out[i], sample_out ); } /* Combine early reflections and reverberation */ early_late = ( (sample_in * channel->early_gain) + (sample_out * reverb->reverb_gain) ); /* Room filter */ sample_out = DspBiQuad_Process( &channel->room_high_shelf, early_late * reverb->room_gain ); /* PositionMatrixLeft/Right */ return sample_out * channel->gain; } /* Reverb Process Functions */ static inline float DspReverb_INTERNAL_Process_1_to_1( DspReverb *reverb, float *restrict samples_in, float *restrict samples_out, size_t sample_count ) { const float *in_end = samples_in + sample_count; float in, early, late, out; float squared_sum = 0.0f; while (samples_in < in_end) { /* Input */ in = *samples_in++; /* Early Reflections */ early = DspReverb_INTERNAL_ProcessEarly(reverb, in); /* Reverberation */ late = DspReverb_INTERNAL_ProcessChannel( reverb, &reverb->channel[0], early ); /* Wet/Dry Mix */ out = (late * reverb->wet_ratio) + (in * reverb->dry_ratio); squared_sum += out * out; /* Output */ *samples_out++ = out; } return squared_sum; } static inline float DspReverb_INTERNAL_Process_1_to_5p1( DspReverb *reverb, float *restrict samples_in, float *restrict samples_out, size_t sample_count ) { const float *in_end = samples_in + sample_count; float in, in_ratio, early, late[4]; float squared_sum = 0.0f; int32_t c; while (samples_in < in_end) { /* Input */ in = *samples_in++; in_ratio = in * reverb->dry_ratio; /* Early Reflections */ early = DspReverb_INTERNAL_ProcessEarly(reverb, in); /* Reverberation with Wet/Dry Mix */ for (c = 0; c < 4; c += 1) { late[c] = (DspReverb_INTERNAL_ProcessChannel( reverb, &reverb->channel[c], early ) * reverb->wet_ratio) + in_ratio; squared_sum += late[c] * late[c]; } /* Output */ *samples_out++ = late[0]; /* Front Left */ *samples_out++ = late[1]; /* Front Right */ *samples_out++ = 0.0f; /* Center */ *samples_out++ = 0.0f; /* LFE */ *samples_out++ = late[2]; /* Rear Left */ *samples_out++ = late[3]; /* Rear Right */ } return squared_sum; } static inline float DspReverb_INTERNAL_Process_2_to_2( DspReverb *reverb, float *restrict samples_in, float *restrict samples_out, size_t sample_count ) { const float *in_end = samples_in + sample_count; float in, in_ratio, early, late[2]; float squared_sum = 0; while (samples_in < in_end) { /* Input - Combine 2 channels into 1 */ in = (samples_in[0] + samples_in[1]) / 2.0f; in_ratio = in * reverb->dry_ratio; samples_in += 2; /* Early Reflections */ early = DspReverb_INTERNAL_ProcessEarly(reverb, in); /* Reverberation with Wet/Dry Mix */ late[0] = DspReverb_INTERNAL_ProcessChannel( reverb, &reverb->channel[0], early ); late[1] = (DspReverb_INTERNAL_ProcessChannel( reverb, &reverb->channel[1], early ) * reverb->wet_ratio) + in_ratio; squared_sum += (late[0] * late[0]) + (late[1] * late[1]); /* Output */ *samples_out++ = late[0]; *samples_out++ = late[1]; } return squared_sum; } static inline float DspReverb_INTERNAL_Process_2_to_5p1( DspReverb *reverb, float *restrict samples_in, float *restrict samples_out, size_t sample_count ) { const float *in_end = samples_in + sample_count; float in, in_ratio, early, late[4]; float squared_sum = 0; int32_t c; while (samples_in < in_end) { /* Input - Combine 2 channels into 1 */ in = (samples_in[0] + samples_in[1]) / 2.0f; in_ratio = in * reverb->dry_ratio; samples_in += 2; /* Early Reflections */ early = DspReverb_INTERNAL_ProcessEarly(reverb, in); /* Reverberation with Wet/Dry Mix */ for (c = 0; c < 4; c += 1) { late[c] = (DspReverb_INTERNAL_ProcessChannel( reverb, &reverb->channel[c], early ) * reverb->wet_ratio) + in_ratio; squared_sum += late[c] * late[c]; } /* Output */ *samples_out++ = late[0]; /* Front Left */ *samples_out++ = late[1]; /* Front Right */ *samples_out++ = 0.0f; /* Center */ *samples_out++ = 0.0f; /* LFE */ *samples_out++ = late[2]; /* Rear Left */ *samples_out++ = late[3]; /* Rear Right */ } return squared_sum; } #undef OUTPUT_SAMPLE /* Reverb FAPO Implementation */ const FAudioGUID FAudioFX_CLSID_AudioReverb = /* 2.7 */ { 0x6A93130E, 0xCB4E, 0x4CE1, { 0xA9, 0xCF, 0xE7, 0x58, 0x80, 0x0B, 0xB1, 0x79 } }; static FAPORegistrationProperties ReverbProperties = { /* .clsid = */ {0}, /*.FriendlyName = */ { 'R', 'e', 'v', 'e', 'r', 'b', '\0' }, /*.CopyrightInfo = */ { 'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')', 'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0' }, /*.MajorVersion = */ 0, /*.MinorVersion = */ 0, /*.Flags = */ ( FAPO_FLAG_FRAMERATE_MUST_MATCH | FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH | FAPO_FLAG_BUFFERCOUNT_MUST_MATCH | FAPO_FLAG_INPLACE_SUPPORTED ), /*.MinInputBufferCount = */ 1, /*.MaxInputBufferCount = */ 1, /*.MinOutputBufferCount = */ 1, /*.MaxOutputBufferCount = */ 1 }; typedef struct FAudioFXReverb { FAPOBase base; uint16_t inChannels; uint16_t outChannels; uint32_t sampleRate; uint16_t inBlockAlign; uint16_t outBlockAlign; DspReverb reverb; } FAudioFXReverb; static inline int8_t IsFloatFormat(const FAudioWaveFormatEx *format) { if (format->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT) { /* Plain ol' WaveFormatEx */ return 1; } if (format->wFormatTag == FAUDIO_FORMAT_EXTENSIBLE) { /* WaveFormatExtensible, match GUID */ #define MAKE_SUBFORMAT_GUID(guid, fmt) \ static FAudioGUID KSDATAFORMAT_SUBTYPE_##guid = \ { \ (uint16_t) (fmt), 0x0000, 0x0010, \ { \ 0x80, 0x00, 0x00, 0xaa, \ 0x00, 0x38, 0x9b, 0x71 \ } \ } MAKE_SUBFORMAT_GUID(IEEE_FLOAT, 3); #undef MAKE_SUBFORMAT_GUID if (FAudio_memcmp( &((FAudioWaveFormatExtensible*) format)->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID) ) == 0) { return 1; } } return 0; } uint32_t FAudioFXReverb_IsInputFormatSupported( FAPOBase *fapo, const FAudioWaveFormatEx *pOutputFormat, const FAudioWaveFormatEx *pRequestedInputFormat, FAudioWaveFormatEx **ppSupportedInputFormat ) { uint32_t result = 0; #define SET_SUPPORTED_FIELD(field, value) \ result = 1; \ if (ppSupportedInputFormat && *ppSupportedInputFormat) \ { \ (*ppSupportedInputFormat)->field = (value); \ } /* Sample Rate */ if (pOutputFormat->nSamplesPerSec != pRequestedInputFormat->nSamplesPerSec) { SET_SUPPORTED_FIELD(nSamplesPerSec, pOutputFormat->nSamplesPerSec); } /* Data Type */ if (!IsFloatFormat(pRequestedInputFormat)) { SET_SUPPORTED_FIELD(wFormatTag, FAUDIO_FORMAT_IEEE_FLOAT); } /* Input/Output Channel Count */ if (pOutputFormat->nChannels == 1 || pOutputFormat->nChannels == 2) { if (pRequestedInputFormat->nChannels != pOutputFormat->nChannels) { SET_SUPPORTED_FIELD(nChannels, pOutputFormat->nChannels); } } else if (pOutputFormat->nChannels == 6) { if ( pRequestedInputFormat->nChannels != 1 && pRequestedInputFormat->nChannels != 2 ) { SET_SUPPORTED_FIELD(nChannels, 1); } } else { SET_SUPPORTED_FIELD(nChannels, 1); } #undef SET_SUPPORTED_FIELD return result; } uint32_t FAudioFXReverb_IsOutputFormatSupported( FAPOBase *fapo, const FAudioWaveFormatEx *pInputFormat, const FAudioWaveFormatEx *pRequestedOutputFormat, FAudioWaveFormatEx **ppSupportedOutputFormat ) { uint32_t result = 0; #define SET_SUPPORTED_FIELD(field, value) \ result = 1; \ if (ppSupportedOutputFormat && *ppSupportedOutputFormat) \ { \ (*ppSupportedOutputFormat)->field = (value); \ } /* Sample Rate */ if (pInputFormat->nSamplesPerSec != pRequestedOutputFormat->nSamplesPerSec) { SET_SUPPORTED_FIELD(nSamplesPerSec, pInputFormat->nSamplesPerSec); } /* Data Type */ if (!IsFloatFormat(pRequestedOutputFormat)) { SET_SUPPORTED_FIELD(wFormatTag, FAUDIO_FORMAT_IEEE_FLOAT); } /* Input/Output Channel Count */ if (pInputFormat->nChannels == 1 || pInputFormat->nChannels == 2) { if ( pRequestedOutputFormat->nChannels != pInputFormat->nChannels && pRequestedOutputFormat->nChannels != 6) { SET_SUPPORTED_FIELD(nChannels, pInputFormat->nChannels); } } else { SET_SUPPORTED_FIELD(nChannels, 1); } #undef SET_SUPPORTED_FIELD return result; } uint32_t FAudioFXReverb_Initialize( FAudioFXReverb *fapo, const void* pData, uint32_t DataByteSize ) { #define INITPARAMS(offset) \ FAudio_memcpy( \ fapo->base.m_pParameterBlocks + DataByteSize * offset, \ pData, \ DataByteSize \ ); INITPARAMS(0) INITPARAMS(1) INITPARAMS(2) #undef INITPARAMS return 0; } uint32_t FAudioFXReverb_LockForProcess( FAudioFXReverb *fapo, uint32_t InputLockedParameterCount, const FAPOLockForProcessBufferParameters *pInputLockedParameters, uint32_t OutputLockedParameterCount, const FAPOLockForProcessBufferParameters *pOutputLockedParameters ) { /* Reverb specific validation */ if (!IsFloatFormat(pInputLockedParameters->pFormat)) { return FAPO_E_FORMAT_UNSUPPORTED; } if ( pInputLockedParameters->pFormat->nSamplesPerSec < FAUDIOFX_REVERB_MIN_FRAMERATE || pInputLockedParameters->pFormat->nSamplesPerSec > FAUDIOFX_REVERB_MAX_FRAMERATE ) { return FAPO_E_FORMAT_UNSUPPORTED; } if (!( (pInputLockedParameters->pFormat->nChannels == 1 && (pOutputLockedParameters->pFormat->nChannels == 1 || pOutputLockedParameters->pFormat->nChannels == 6)) || (pInputLockedParameters->pFormat->nChannels == 2 && (pOutputLockedParameters->pFormat->nChannels == 2 || pOutputLockedParameters->pFormat->nChannels == 6)))) { return FAPO_E_FORMAT_UNSUPPORTED; } /* Save the things we care about */ fapo->inChannels = pInputLockedParameters->pFormat->nChannels; fapo->outChannels = pOutputLockedParameters->pFormat->nChannels; fapo->sampleRate = pOutputLockedParameters->pFormat->nSamplesPerSec; fapo->inBlockAlign = pInputLockedParameters->pFormat->nBlockAlign; fapo->outBlockAlign = pOutputLockedParameters->pFormat->nBlockAlign; /* Create the network */ DspReverb_Create( &fapo->reverb, fapo->sampleRate, fapo->inChannels, fapo->outChannels, fapo->base.pMalloc ); /* Call parent to do basic validation */ return FAPOBase_LockForProcess( &fapo->base, InputLockedParameterCount, pInputLockedParameters, OutputLockedParameterCount, pOutputLockedParameters ); } static inline void FAudioFXReverb_CopyBuffer( FAudioFXReverb *fapo, float *restrict buffer_in, float *restrict buffer_out, size_t frames_in ) { /* In-place processing? */ if (buffer_in == buffer_out) { return; } /* 1 -> 1 or 2 -> 2 */ if (fapo->inBlockAlign == fapo->outBlockAlign) { FAudio_memcpy( buffer_out, buffer_in, fapo->inBlockAlign * frames_in ); return; } /* 1 -> 5.1 */ if (fapo->inChannels == 1 && fapo->outChannels == 6) { const float *in_end = buffer_in + frames_in; while (buffer_in < in_end) { *buffer_out++ = *buffer_in; *buffer_out++ = *buffer_in++; *buffer_out++ = 0.0f; *buffer_out++ = 0.0f; *buffer_out++ = 0.0f; *buffer_out++ = 0.0f; } return; } /* 2 -> 5.1 */ if (fapo->inChannels == 2 && fapo->outChannels == 6) { const float *in_end = buffer_in + (frames_in * 2); while (buffer_in < in_end) { *buffer_out++ = *buffer_in++; *buffer_out++ = *buffer_in++; *buffer_out++ = 0.0f; *buffer_out++ = 0.0f; *buffer_out++ = 0.0f; *buffer_out++ = 0.0f; } return; } FAudio_assert(0 && "Unsupported channel combination"); FAudio_zero(buffer_out, fapo->outBlockAlign * frames_in); } void FAudioFXReverb_Process( FAudioFXReverb *fapo, uint32_t InputProcessParameterCount, const FAPOProcessBufferParameters* pInputProcessParameters, uint32_t OutputProcessParameterCount, FAPOProcessBufferParameters* pOutputProcessParameters, int32_t IsEnabled ) { FAudioFXReverbParameters *params; uint8_t update_params = FAPOBase_ParametersChanged(&fapo->base); float total; /* Handle disabled filter */ if (IsEnabled == 0) { pOutputProcessParameters->BufferFlags = pInputProcessParameters->BufferFlags; if (pOutputProcessParameters->BufferFlags != FAPO_BUFFER_SILENT) { FAudioFXReverb_CopyBuffer( fapo, (float*) pInputProcessParameters->pBuffer, (float*) pOutputProcessParameters->pBuffer, pInputProcessParameters->ValidFrameCount ); } return; } /* XAudio2 passes a 'silent' buffer when no input buffer is available to play the effect tail */ if (pInputProcessParameters->BufferFlags == FAPO_BUFFER_SILENT) { /* Make sure input data is usable. FIXME: Is this required? */ FAudio_zero( pInputProcessParameters->pBuffer, pInputProcessParameters->ValidFrameCount * fapo->inBlockAlign ); } params = (FAudioFXReverbParameters*) FAPOBase_BeginProcess(&fapo->base); /* Update parameters */ if (update_params) { DspReverb_SetParameters(&fapo->reverb, params); } /* Run reverb effect */ #define PROCESS(pin, pout) \ DspReverb_INTERNAL_Process_##pin##_to_##pout( \ &fapo->reverb, \ (float*) pInputProcessParameters->pBuffer, \ (float*) pOutputProcessParameters->pBuffer, \ pInputProcessParameters->ValidFrameCount * fapo->inChannels \ ) switch (fapo->reverb.out_channels) { case 1: total = PROCESS(1, 1); break; case 2: total = PROCESS(2, 2); break; default: /* 5.1 */ if (fapo->reverb.in_channels == 1) { total = PROCESS(1, 5p1); } else { total = PROCESS(2, 5p1); } break; } #undef PROCESS /* Set BufferFlags to silent so PLAY_TAILS knows when to stop */ pOutputProcessParameters->BufferFlags = (total < 0.0000001f) ? FAPO_BUFFER_SILENT : FAPO_BUFFER_VALID; FAPOBase_EndProcess(&fapo->base); } void FAudioFXReverb_Reset(FAudioFXReverb *fapo) { int32_t i, c; FAPOBase_Reset(&fapo->base); /* Reset the cached state of the reverb filter */ DspDelay_Reset(&fapo->reverb.early_delay); for (i = 0; i < REVERB_COUNT_APF_IN; i += 1) { DspAllPass_Reset(&fapo->reverb.apf_in[i]); } for (c = 0; c < fapo->reverb.reverb_channels; c += 1) { DspDelay_Reset(&fapo->reverb.channel[c].reverb_delay); for (i = 0; i < REVERB_COUNT_COMB; i += 1) { DspCombShelving_Reset(&fapo->reverb.channel[c].lpf_comb[i]); } DspBiQuad_Reset(&fapo->reverb.channel[c].room_high_shelf); for (i = 0; i < REVERB_COUNT_APF_OUT; i += 1) { DspAllPass_Reset(&fapo->reverb.channel[c].apf_out[i]); } } } void FAudioFXReverb_Free(void* fapo) { FAudioFXReverb *reverb = (FAudioFXReverb*) fapo; DspReverb_Destroy(&reverb->reverb, reverb->base.pFree); reverb->base.pFree(reverb->base.m_pParameterBlocks); reverb->base.pFree(fapo); } /* Public API */ uint32_t FAudioCreateReverb(FAPO** ppApo, uint32_t Flags) { return FAudioCreateReverbWithCustomAllocatorEXT( ppApo, Flags, FAudio_malloc, FAudio_free, FAudio_realloc ); } uint32_t FAudioCreateReverbWithCustomAllocatorEXT( FAPO** ppApo, uint32_t Flags, FAudioMallocFunc customMalloc, FAudioFreeFunc customFree, FAudioReallocFunc customRealloc ) { const FAudioFXReverbParameters fxdefault = { FAUDIOFX_REVERB_DEFAULT_WET_DRY_MIX, FAUDIOFX_REVERB_DEFAULT_REFLECTIONS_DELAY, FAUDIOFX_REVERB_DEFAULT_REVERB_DELAY, FAUDIOFX_REVERB_DEFAULT_REAR_DELAY, FAUDIOFX_REVERB_DEFAULT_POSITION, FAUDIOFX_REVERB_DEFAULT_POSITION, FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX, FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX, FAUDIOFX_REVERB_DEFAULT_EARLY_DIFFUSION, FAUDIOFX_REVERB_DEFAULT_LATE_DIFFUSION, FAUDIOFX_REVERB_DEFAULT_LOW_EQ_GAIN, FAUDIOFX_REVERB_DEFAULT_LOW_EQ_CUTOFF, FAUDIOFX_REVERB_DEFAULT_HIGH_EQ_GAIN, FAUDIOFX_REVERB_DEFAULT_HIGH_EQ_CUTOFF, FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_FREQ, FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_MAIN, FAUDIOFX_REVERB_DEFAULT_ROOM_FILTER_HF, FAUDIOFX_REVERB_DEFAULT_REFLECTIONS_GAIN, FAUDIOFX_REVERB_DEFAULT_REVERB_GAIN, FAUDIOFX_REVERB_DEFAULT_DECAY_TIME, FAUDIOFX_REVERB_DEFAULT_DENSITY, FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE }; /* Allocate... */ FAudioFXReverb *result = (FAudioFXReverb*) customMalloc(sizeof(FAudioFXReverb)); uint8_t *params = (uint8_t*) customMalloc( sizeof(FAudioFXReverbParameters) * 3 ); #define INITPARAMS(offset) \ FAudio_memcpy( \ params + sizeof(FAudioFXReverbParameters) * offset, \ &fxdefault, \ sizeof(FAudioFXReverbParameters) \ ); INITPARAMS(0) INITPARAMS(1) INITPARAMS(2) #undef INITPARAMS /* Initialize... */ FAudio_memcpy( &ReverbProperties.clsid, &FAudioFX_CLSID_AudioReverb, sizeof(FAudioGUID) ); CreateFAPOBaseWithCustomAllocatorEXT( &result->base, &ReverbProperties, params, sizeof(FAudioFXReverbParameters), 0, customMalloc, customFree, customRealloc ); result->inChannels = 0; result->outChannels = 0; result->sampleRate = 0; FAudio_zero(&result->reverb, sizeof(DspReverb)); /* Function table... */ #define ASSIGN_VT(name) \ result->base.base.name = (name##Func) FAudioFXReverb_##name; ASSIGN_VT(LockForProcess); ASSIGN_VT(IsInputFormatSupported); ASSIGN_VT(IsOutputFormatSupported); ASSIGN_VT(Initialize); ASSIGN_VT(Reset); ASSIGN_VT(Process); result->base.Destructor = FAudioFXReverb_Free; #undef ASSIGN_VT /* Finally. */ *ppApo = &result->base.base; return 0; } void ReverbConvertI3DL2ToNative( const FAudioFXReverbI3DL2Parameters *pI3DL2, FAudioFXReverbParameters *pNative ) { float reflectionsDelay; float reverbDelay; pNative->RearDelay = FAUDIOFX_REVERB_DEFAULT_REAR_DELAY; pNative->PositionLeft = FAUDIOFX_REVERB_DEFAULT_POSITION; pNative->PositionRight = FAUDIOFX_REVERB_DEFAULT_POSITION; pNative->PositionMatrixLeft = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX; pNative->PositionMatrixRight = FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX; pNative->RoomSize = FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE; pNative->LowEQCutoff = 4; pNative->HighEQCutoff = 6; pNative->RoomFilterMain = (float) pI3DL2->Room / 100.0f; pNative->RoomFilterHF = (float) pI3DL2->RoomHF / 100.0f; if (pI3DL2->DecayHFRatio >= 1.0f) { int32_t index = (int32_t) (-4.0 * FAudio_log10(pI3DL2->DecayHFRatio)); if (index < -8) { index = -8; } pNative->LowEQGain = (uint8_t) ((index < 0) ? index + 8 : 8); pNative->HighEQGain = 8; pNative->DecayTime = pI3DL2->DecayTime * pI3DL2->DecayHFRatio; } else { int32_t index = (int32_t) (4.0 * FAudio_log10(pI3DL2->DecayHFRatio)); if (index < -8) { index = -8; } pNative->LowEQGain = 8; pNative->HighEQGain = (uint8_t) ((index < 0) ? index + 8 : 8); pNative->DecayTime = pI3DL2->DecayTime; } reflectionsDelay = pI3DL2->ReflectionsDelay * 1000.0f; if (reflectionsDelay >= FAUDIOFX_REVERB_MAX_REFLECTIONS_DELAY) { reflectionsDelay = (float) (FAUDIOFX_REVERB_MAX_REFLECTIONS_DELAY - 1); } else if (reflectionsDelay <= 1) { reflectionsDelay = 1; } pNative->ReflectionsDelay = (uint32_t) reflectionsDelay; reverbDelay = pI3DL2->ReverbDelay * 1000.0f; if (reverbDelay >= FAUDIOFX_REVERB_MAX_REVERB_DELAY) { reverbDelay = (float) (FAUDIOFX_REVERB_MAX_REVERB_DELAY - 1); } pNative->ReverbDelay = (uint8_t) reverbDelay; pNative->ReflectionsGain = pI3DL2->Reflections / 100.0f; pNative->ReverbGain = pI3DL2->Reverb / 100.0f; pNative->EarlyDiffusion = (uint8_t) (15.0f * pI3DL2->Diffusion / 100.0f); pNative->LateDiffusion = pNative->EarlyDiffusion; pNative->Density = pI3DL2->Density; pNative->RoomFilterFreq = pI3DL2->HFReference; pNative->WetDryMix = pI3DL2->WetDryMix; } /* vim: set noexpandtab shiftwidth=8 tabstop=8: */