Show More
Commit Description:
Add timers for Simulation and various engines...
Commit Description:
Add timers for Simulation and various engines Starting to add additional timers for different stages of the process of updating in order to get more insight into what is slowing it down. The update takes 9ms, which is much longer than it used to. Engine-specific timers are coming later.
File last commit:
Show/Diff file:
Action:
FNA/lib/FAudio/src/FAudioFX_reverb.c
1616 lines | 39.0 KiB | text/x-c | CLexer
/* 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 <flibitijibibo@flibitijibibo.com>
*
*/
#include "FAudioFX.h"
#include "FAudio_internal.h"
/* #define DISABLE_SUBNORMALS */
#ifdef DISABLE_SUBNORMALS
#include <math.h> /* 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: */