From 0f214555c13a73823ac3fc680186bc5a12dcfe74 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 18 Nov 2015 09:13:03 -0800 Subject: [PATCH 01/12] New algorithmic stereo reverb --- libraries/audio/src/AudioReverb.cpp | 1965 +++++++++++++++++++++++++++ libraries/audio/src/AudioReverb.h | 76 ++ 2 files changed, 2041 insertions(+) create mode 100644 libraries/audio/src/AudioReverb.cpp create mode 100644 libraries/audio/src/AudioReverb.h diff --git a/libraries/audio/src/AudioReverb.cpp b/libraries/audio/src/AudioReverb.cpp new file mode 100644 index 0000000000..3f28a9c00d --- /dev/null +++ b/libraries/audio/src/AudioReverb.cpp @@ -0,0 +1,1965 @@ +// +// AudioReverb.cpp +// libraries/audio/src +// +// Created by Ken Cooke on 10/11/15. +// Copyright 2015 High Fidelity, Inc. +// + +#include +#include +#include + +#include "AudioReverb.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4351) // new behavior: elements of array will be default initialized + +#include +inline static int MULHI(int a, int b) { + long long c = __emul(a, b); + return ((int*)&c)[1]; +} +#else + +#define MULHI(a,b) (int)(((long long)(a) * (b)) >> 32) + +#endif // _MSC_VER + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +static const float M_PHI = 0.6180339887f; // maximum allpass diffusion + +static const double M_PI = 3.14159265358979323846; +static const double M_SQRT2 = 1.41421356237309504880; + +static const double FIXQ31 = 2147483648.0; +static const double FIXQ32 = 4294967296.0; + +// Round an integer to the next power-of-two, at compile time. +// VS2013 does not support constexpr so macros are used instead. +#define SETBITS0(x) (x) +#define SETBITS1(x) (SETBITS0(x) | (SETBITS0(x) >> 1)) +#define SETBITS2(x) (SETBITS1(x) | (SETBITS1(x) >> 2)) +#define SETBITS3(x) (SETBITS2(x) | (SETBITS2(x) >> 4)) +#define SETBITS4(x) (SETBITS3(x) | (SETBITS3(x) >> 8)) +#define SETBITS5(x) (SETBITS4(x) | (SETBITS4(x) >> 16)) +#define NEXTPOW2(x) (SETBITS5((x) - 1) + 1) + +// +// Allpass delay modulation +// +static const int MOD_INTBITS = 4; +static const int MOD_FRACBITS = 31 - MOD_INTBITS; +static const uint32_t MOD_FRACMASK = (1 << MOD_FRACBITS) - 1; +static const float QMOD_TO_FLOAT = 1.0f / (1 << MOD_FRACBITS); + +// +// Reverb delay values, defined for sampleRate=48000 roomSize=100% density=100% +// +// max path should already be prime, to prevent getPrime(NEXTPOW2(N)) > NEXTPOW2(N) +// +static const int M_PD0 = 16000; // max predelay = 333ms + +static const int M_AP0 = 83; +static const int M_AP1 = 211; +static const int M_AP2 = 311; +static const int M_AP3 = 97; +static const int M_AP4 = 223; +static const int M_AP5 = 293; + +static const int M_MT0 = 1017; +static const int M_MT1 = 4077; +static const int M_MT2 = 2039; +static const int M_MT3 = 1017; +static const int M_MT4 = 2593; +static const int M_MT5 = 2039; + +static const int M_MT1_2 = 600; +static const int M_MT4_2 = 600; +static const int M_MT1_MAX = MAX(M_MT1, M_MT1_2); +static const int M_MT4_MAX = MAX(M_MT4, M_MT4_2); + +static const int M_AP6 = 817; +static const int M_AP7 = 513; +static const int M_AP8 = 765; +static const int M_AP9 = 465; +static const int M_AP10 = 3021; +static const int M_AP11 = 2121; +static const int M_AP12 = 1705; +static const int M_AP13 = 1081; +static const int M_AP14 = 3313; +static const int M_AP15 = 2205; +static const int M_AP16 = 1773; +static const int M_AP17 = 981; + +static const int M_AP7_MAX = M_AP7 + (1 << MOD_INTBITS) + 1; // include max excursion +static const int M_AP9_MAX = M_AP9 + (1 << MOD_INTBITS) + 1; + +static const int M_MT6 = 6863; +static const int M_MT7 = 7639; +static const int M_MT8 = 3019; +static const int M_MT9 = 2875; + +static const int M_LD0 = 8000; // max late delay = 166ms +static const int M_MT6_MAX = MAX(M_MT6, M_LD0); +static const int M_MT7_MAX = MAX(M_MT7, M_LD0); +static const int M_MT8_MAX = MAX(M_MT8, M_LD0); +static const int M_MT9_MAX = MAX(M_MT9, M_LD0); + +static const int M_AP18 = 131; +static const int M_AP19 = 113; +static const int M_AP20 = 107; +static const int M_AP21 = 127; + +// +// Filter design tools using analog-matched response. +// All filter types approximate the s-plane response, including cutoff > Nyquist. +// + +static float dBToGain(float dB) { + return powf(10.0f, dB * (1/20.0f)); +} + +static double dBToGain(double dB) { + return pow(10.0, dB * (1/20.0)); +} + +// Returns the gain of analog (s-plane) peak-filter evaluated at w +static double analogPeak(double w0, double G, double Q, double w) { + double w0sq, wsq, Gsq, Qsq; + double num, den; + + w0sq = w0 * w0; + wsq = w * w; + Gsq = G * G; + Qsq = Q * Q; + + num = Qsq * (wsq - w0sq) * (wsq - w0sq) + wsq * w0sq * Gsq; + den = Qsq * (wsq - w0sq) * (wsq - w0sq) + wsq * w0sq; + + return sqrt(num / den); +} + +// Returns the gain of analog (s-plane) shelf evaluated at w +// non-resonant Q = sqrt(0.5) so 1/Q^2 = 2.0 +static double analogShelf(double w0, double G, double resonance, int isHigh, double w) { + double w0sq, wsq, Qrsq; + double num, den; + + resonance = MIN(MAX(resonance, 0.0), 1.0); + Qrsq = 2.0 * pow(G, 1.0 - resonance); // resonant 1/Q^2 + + w0sq = w0 * w0; + wsq = w * w; + + if (isHigh) { + num = (G*wsq - w0sq) * (G*wsq - w0sq) + wsq * w0sq * Qrsq; + } else { + num = (wsq - G*w0sq) * (wsq - G*w0sq) + wsq * w0sq * Qrsq; + } + den = wsq * wsq + w0sq * w0sq; + + return sqrt(num / den); +} + +// Returns the gain of analog (s-plane) highpass/lowpass evaluated at w +// Q = sqrt(0.5) = 2nd order Butterworth +static double analogFilter(double w0, int isHigh, double w) { + double w0sq, wsq; + double num, den; + + w0sq = w0 * w0; + wsq = w * w; + + if (isHigh) { + num = wsq * wsq; + } else { + num = w0sq * w0sq; + } + den = wsq * wsq + w0sq * w0sq; + + return sqrt(num / den); +} + +// +// Biquad Peaking EQ using analog matching. +// NOTE: Peak topology becomes ill-conditioned as w0 approaches pi. +// +static void BQPeakBelowPi(double coef[5], double w0, double dbgain, double Q) { + double G, G1, Gsq, G1sq, Qsq; + double G11, G00, Gratio; + double wpi, wh, w0sq, whsq; + double Wsq, B, A; + double b0, b1, b2, a0, a1, a2; + double temp, scale; + int isUnity; + + // convert cut into boost, invert later + G = dBToGain(fabs(dbgain)); + + // special case near unity gain + isUnity = G < 1.001; + G = MAX(G, 1.001); + + // compute the Nyquist gain + wpi = w0 + 2.8 * (1.0 - w0/M_PI); // minimax-like error + wpi = MIN(wpi, M_PI); + G1 = analogPeak(w0, G, Q, wpi); + + G1sq = G1 * G1; + Gsq = G * G; + Qsq = Q * Q; + + // compute the analog half-gain frequency + temp = G + 2.0 * Qsq - sqrt(Gsq + 4.0 * Qsq * G); + wh = sqrt(temp) * w0 / (Q * M_SQRT2); + + // prewarp freqs of w0 and wh + w0 = tan(0.5 * w0); + wh = tan(0.5 * wh); + w0sq = w0 * w0; + whsq = wh * wh; + + // compute Wsq, from asymmetry due to G1 + G11 = Gsq - G1sq; + G00 = Gsq - 1.0; + Gratio = G11 / G00; + Wsq = w0sq * sqrt(Gratio); + + // compute B, matching gains at w0 and wh + temp = 2.0 * Wsq * (G1 - sqrt(Gratio)); + temp += Gsq * whsq * (1.0 - G1sq) / G00; + temp += G * Gratio * (w0sq - whsq) * (w0sq - whsq) / whsq; + B = sqrt(temp); + + // compute A, matching gains at w0 and wh + temp = 2.0 * Wsq; + temp += (2.0 * G11 * w0sq + (G1sq - G) * whsq) / (G - Gsq); + temp += Gratio * w0sq * w0sq / (G * whsq); + A = sqrt(temp); + + // design digital filter via bilinear tranform + b0 = G1 + B + Wsq; + b1 = 2.0 * (Wsq - G1); + b2 = G1 - B + Wsq; + a0 = 1.0 + A + Wsq; + a1 = 2.0 * (Wsq - 1.0); + a2 = 1.0 - A + Wsq; + + // for unity gain, ensure poles/zeros in the right place. + // needed for smooth interpolation when gain is changed. + if (isUnity) { + b0 = a0; + b1 = a1; + b2 = a2; + } + + // invert filter for cut + if (dbgain < 0.0) { + temp = b0; b0 = a0; a0 = temp; + temp = b1; b1 = a1; a1 = temp; + temp = b2; b2 = a2; a2 = temp; + } + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = b2 * scale; + coef[3] = a1 * scale; + coef[4] = a2 * scale; +} + +// +// Biquad Peaking EQ using a shelf instead of peak. +// +// This uses a shelf topology, matched to the analog peaking filter located above Nyquist. +// +// NOTE: the result is close, but not identical to BQPeakBelowPi(), since a pole/zero +// pair must jump to the right side of the real axis. Eg. inflection at the peak goes away. +// However, interpolation from peak to shelf is well behaved if the switch is made near pi, +// as the pole/zero be near (and travel down) the real axis. +// +static void BQPeakAbovePi(double coef[5], double w0, double dbgain, double Q) { + double G, G1, Gsq, Qsq; + double wpi, wh, wn, wd; + double wna, wda; + double gn, gd, gnsq, gdsq; + double num, den; + double Wnsq, Wdsq, B, A; + double b0, b1, b2, a0, a1, a2; + double temp, scale; + int isUnity; + + // convert cut into boost, invert later + G = dBToGain(fabs(dbgain)); + + // special case near unity gain + isUnity = G < 1.001; + G = MAX(G, 1.001); + + // compute the Nyquist gain + wpi = M_PI; + if (w0 < M_PI) { + G1 = G; // use the peak gain + } else { + G1 = analogPeak(w0, G, Q, wpi); // use Nyquist gain of analog response + } + + Gsq = G * G; + Qsq = Q * Q; + + // compute the analog half-gain frequency + temp = G + 2.0 * Qsq - sqrt(Gsq + 4.0 * Qsq * G); + wh = sqrt(temp) * w0 / (Q * M_SQRT2); + + // approximate wn and wd + // use half-gain frequency as mapping + wn = 0.5 * wh / sqrt(sqrt((G1))); + wd = wn * sqrt(G1); + Wnsq = wn * wn; + Wdsq = wd * wd; + + // analog freqs of wn and wd + wna = 2.0 * atan(wn); + wda = 2.0 * atan(wd); + + // normalized analog gains at wna and wda + temp = 1.0 / G1; + gn = temp * analogPeak(w0, G, Q, wna); + gd = temp * analogPeak(w0, G, Q, wda); + gnsq = gn * gn; + gdsq = gd * gd; + + // compute B, matching gains at wn and wd + temp = 1.0 / (wn * wd); + den = fabs(gnsq - gdsq); + num = gnsq * (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gdsq * Wdsq); + B = temp * sqrt(num / den); + + // compute A, matching gains at wn and wd + num = (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gnsq * Wdsq); + A = temp * sqrt(num / den); + + // design digital filter via bilinear tranform + b0 = G1 * (1.0 + B + Wnsq); + b1 = G1 * 2.0 * (Wnsq - 1.0); + b2 = G1 * (1.0 - B + Wnsq); + a0 = 1.0 + A + Wdsq; + a1 = 2.0 * (Wdsq - 1.0); + a2 = 1.0 - A + Wdsq; + + // for unity gain, ensure poles/zeros are in the right place. + // allows smooth coefficient interpolation when gain is changed. + if (isUnity) { + b0 = a0; + b1 = a1; + b2 = a2; + } + + // invert filter for cut + if (dbgain < 0.0) { + temp = b0; b0 = a0; a0 = temp; + temp = b1; b1 = a1; a1 = temp; + temp = b2; b2 = a2; a2 = temp; + } + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = b2 * scale; + coef[3] = a1 * scale; + coef[4] = a2 * scale; +} + +// +// Biquad Peaking EQ using analog matching. +// Supports full range of w0. +// +static void BQPeak(double coef[5], double w0, double dbgain, double Q) { + w0 = MAX(w0, 0.0); // allow w0 > pi + + Q = MIN(MAX(Q, 1.0e-6), 1.0e+6); + + // Switch from peak to shelf, just before w0 = pi. + // Too early causes a jump in the peak location, which interpolates to lowered gains. + // Too late becomes ill-conditioned, and makes no improvement to interpolated response. + // 3.14 is a good choice. + if (w0 > 3.14) { + BQPeakAbovePi(coef, w0, dbgain, Q); + } else { + BQPeakBelowPi(coef, w0, dbgain, Q); + } +} + +// +// Biquad Shelf using analog matching. +// +static void BQShelf(double coef[5], double w0, double dbgain, double resonance, int isHigh) { + double G, G1; + double wpi, wn, wd; + double wna, wda; + double gn, gd, gnsq, gdsq; + double num, den; + double Wnsq, Wdsq, B, A; + double b0, b1, b2, a0, a1, a2; + double temp, scale; + int isUnity; + + w0 = MAX(w0, 0.0); // allow w0 > pi + + resonance = MIN(MAX(resonance, 0.0), 1.0); + + // convert cut into boost, invert later + G = dBToGain(fabs(dbgain)); + + // special case near unity gain + isUnity = G < 1.001; + G = MAX(G, 1.001); + + // compute the Nyquist gain + wpi = w0 + 2.8 * (1.0 - w0/M_PI); // minimax-like error + wpi = MIN(wpi, M_PI); + G1 = analogShelf(w0, G, resonance, isHigh, wpi); + + // approximate wn and wd + if (isHigh) { + // use center as mapping + wn = 0.5 * w0 / sqrt(sqrt((G * G1))); + wd = wn * sqrt(G1); + } else { + // use wd as mapping + wd = 0.5 * w0; + wn = wd * sqrt(G/G1); + } + Wnsq = wn * wn; + Wdsq = wd * wd; + + // analog freqs of wn and wd + wna = 2.0 * atan(wn); + wda = 2.0 * atan(wd); + + // normalized analog gains at wna and wda + temp = 1.0 / G1; + gn = temp * analogShelf(w0, G, resonance, isHigh, wna); + gd = temp * analogShelf(w0, G, resonance, isHigh, wda); + gnsq = gn * gn; + gdsq = gd * gd; + + // compute B, matching gains at wn and wd + temp = 1.0 / (wn * wd); + den = fabs(gnsq - gdsq); + num = gnsq * (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gdsq * Wdsq); + B = temp * sqrt(num / den); + + // compute A, matching gains at wn and wd + num = (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gnsq * Wdsq); + A = temp * sqrt(num / den); + + // design digital filter via bilinear tranform + b0 = G1 * (1.0 + B + Wnsq); + b1 = G1 * 2.0 * (Wnsq - 1.0); + b2 = G1 * (1.0 - B + Wnsq); + a0 = 1.0 + A + Wdsq; + a1 = 2.0 * (Wdsq - 1.0); + a2 = 1.0 - A + Wdsq; + + // for unity gain, ensure poles/zeros in the right place. + // needed for smooth interpolation when gain is changed. + if (isUnity) { + b0 = a0; + b1 = a1; + b2 = a2; + } + + // invert filter for cut + if (dbgain < 0.0) { + temp = b0; b0 = a0; a0 = temp; + temp = b1; b1 = a1; a1 = temp; + temp = b2; b2 = a2; a2 = temp; + } + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = b2 * scale; + coef[3] = a1 * scale; + coef[4] = a2 * scale; +} + +// +// Biquad Lowpass/Highpass using analog matching. +// Q = sqrt(0.5) = 2nd order Butterworth +// +static void BQFilter(double coef[5], double w0, int isHigh) { + double G1; + double wpi, wn, wd; + double wna, wda; + double gn, gd, gnsq, gdsq; + double num, den; + double Wnsq, Wdsq, B, A; + double b0, b1, b2, a0, a1, a2; + double temp, scale; + + w0 = MAX(w0, 0.0); // allow w0 > pi for lowpass + + if (isHigh) { + + w0 = MIN(w0, M_PI); // disallow w0 > pi for highpass + + // compute the Nyquist gain + wpi = M_PI; + G1 = analogFilter(w0, isHigh, wpi); + + // approximate wn and wd + wn = 0.0; // zeros at zero + wd = 0.5 * w0; + + Wnsq = wn * wn; + Wdsq = wd * wd; + + // compute B and A + B = 0.0; + A = M_SQRT2 * Wdsq / atan(wd); // Qd = sqrt(0.5) * atan(wd)/wd; + + } else { + + // compute the Nyquist gain + wpi = w0 + 2.8 * (1.0 - w0/M_PI); // minimax-like error + wpi = MIN(wpi, M_PI); + G1 = analogFilter(w0, isHigh, wpi); + + // approximate wn and wd + wd = 0.5 * w0; + wn = wd * sqrt(1.0/G1); // down G1 at pi, instead of zeros + + Wnsq = wn * wn; + Wdsq = wd * wd; + + // analog freqs of wn and wd + wna = 2.0 * atan(wn); + wda = 2.0 * atan(wd); + + // normalized analog gains at wna and wda + temp = 1.0 / G1; + gn = temp * analogFilter(w0, isHigh, wna); + gd = temp * analogFilter(w0, isHigh, wda); + gnsq = gn * gn; + gdsq = gd * gd; + + // compute B, matching gains at wn and wd + temp = 1.0 / (wn * wd); + den = fabs(gnsq - gdsq); + num = gnsq * (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gdsq * Wdsq); + B = temp * sqrt(num / den); + + // compute A, matching gains at wn and wd + num = (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gnsq * Wdsq); + A = temp * sqrt(num / den); + } + + // design digital filter via bilinear tranform + b0 = G1 * (1.0 + B + Wnsq); + b1 = G1 * 2.0 * (Wnsq - 1.0); + b2 = G1 * (1.0 - B + Wnsq); + a0 = 1.0 + A + Wdsq; + a1 = 2.0 * (Wdsq - 1.0); + a2 = 1.0 - A + Wdsq; + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = b2 * scale; + coef[3] = a1 * scale; + coef[4] = a2 * scale; +} + +// +// PoleZero Shelf. For Lowpass/Highpass, setCoef dbgain to -100dB. +// NOTE: w0 always sets the pole frequency (3dB corner from unity gain) +// +static void PZShelf(double coef[3], double w0, double dbgain, int isHigh) { + double G, G0, G1; + double b0, b1, a0, a1; + double temp, scale; + + w0 = MAX(w0, 0.0); // allow w0 > pi + + // convert boost into cut, invert later + G = dBToGain(-fabs(dbgain)); + + if (isHigh) { + G0 = 1.0; // gain at DC + G1 = G; // gain at Nyquist + } else { + G0 = G; + G1 = 1.0; + } + + b0 = 1.0; + a0 = 1.0; + b1 = -exp(-w0 * G0 / G1); + a1 = -exp(-w0); + + b1 += 0.12 * (1.0 + b1) * (1.0 + b1) * (1.0 - G1); // analog-matched gain near Nyquist + + scale = G0 * (1.0 + a1) / (1.0 + b1); + b0 *= scale; + b1 *= scale; + + // invert filter for boost + if (dbgain > 0.0) { + temp = b0; b0 = a0; a0 = temp; + temp = b1; b1 = a1; a1 = temp; + } + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = a1 * scale; +} + +class BandwidthEQ { + + float _buffer[4] {}; + + float _output0 = 0.0f; + float _output1 = 0.0f; + + float _dcL = 0.0f; + float _dcR = 0.0f; + + float _b0 = 1.0f; + float _b1 = 0.0f; + float _b2 = 0.0f; + float _a1 = 0.0f; + float _a2 = 0.0f; + + float _alpha = 0.0f; + +public: + void setFreq(float freq, float sampleRate) { + freq = MIN(MAX(freq, 1.0f), 24000.0f); + + // lowpass filter, -3dB @ freq + double coef[5]; + BQFilter(coef, M_PI * freq / (0.5 * sampleRate), 0); + _b0 = (float)coef[0]; + _b1 = (float)coef[1]; + _b2 = (float)coef[2]; + _a1 = (float)coef[3]; + _a2 = (float)coef[4]; + + // DC-blocking filter, -3dB @ 10Hz + _alpha = (float)(1.0 - exp(-M_PI * 10.0 / (0.5 * sampleRate))); + } + + void process(float input0, float input1, float& output0, float& output1) { + output0 = _output0; + output1 = _output1; + + // prevent denormalized zero-input limit cycles in the reverb + input0 += 1.0e-20f; + input1 += 1.0e-20f; + + // remove DC + input0 -= _dcL; + input1 -= _dcR; + + _dcL += _alpha * input0; + _dcR += _alpha * input1; + + // transposed Direct Form II + _output0 = _b0 * input0 + _buffer[0]; + _buffer[0] = _b1 * input0 - _a1 * _output0 + _buffer[1]; + _buffer[1] = _b2 * input0 - _a2 * _output0; + + _output1 = _b0 * input1 + _buffer[2]; + _buffer[2] = _b1 * input1 - _a1 * _output1 + _buffer[3]; + _buffer[3] = _b2 * input1 - _a2 * _output1; + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output0 = 0.0f; + _output1 = 0.0f; + _dcL = 0.0f; + _dcR = 0.0f; + } +}; + +template +class DelayLine { + + float _buffer[N] {}; + + float _output = 0.0f; + + int _index = 0; + int _delay = N; + +public: + void setDelay(int d) { + d = MIN(MAX(d, 1), N); + + _delay = d; + } + + void process(float input, float& output) { + output = _output; + + int k = (_index - _delay) & (N - 1); + + _output = _buffer[k]; + + _buffer[_index] = input; + _index = (_index + 1) & (N - 1); + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +template +class Allpass { + + float _buffer[N] {}; + + float _output = 0.0f; + float _coef = 0.5f; + + int _index0 = 0; + int _index1 = 0; + int _delay = N; + +public: + void setDelay(int d) { + d = MIN(MAX(d, 1), N); + + _index1 = (_index0 - d) & (N - 1); + _delay = d; + } + + int getDelay() { + return _delay; + } + + void setCoef(float coef) { + coef = MIN(MAX(coef, -1.0f), 1.0f); + + _coef = coef; + } + + void process(float input, float& output) { + output = _output; + + _output = _buffer[_index1] - _coef * input; // feedforward path + _buffer[_index0] = input + _coef * _output; // feedback path + + _index0 = (_index0 + 1) & (N - 1); + _index1 = (_index1 + 1) & (N - 1); + } + + void getOutput(float& output) { + output = _output; + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +class RandomLFO { + + int32_t _y0 = 0; + int32_t _y1 = 0; + int32_t _k = 0; + + int32_t _gain = 0; + + uint32_t _rand0 = 0; + int32_t _q0 = 0; // prev + int32_t _r0 = 0; // next + int32_t _m0 = _r0/2 - _q0/2; // slope + int32_t _b0 = _r0/2 + _q0/2; // offset + + uint32_t _rand1 = 0; + int32_t _q1 = 0; + int32_t _r1 = 0; + int32_t _m1 = _q1/2 - _r1/2; + int32_t _b1 = _q1/2 + _r1/2; + +public: + void setFreq(float freq, float sampleRate) { + freq = MIN(freq, 1/16.0f * sampleRate); + freq = MAX(freq, 1/16777216.0f * sampleRate); + + // amplitude slightly less than 1.0 + _y0 = (int32_t)(0.000 * FIXQ31); + _y1 = (int32_t)(0.999 * cos(M_PI * freq / sampleRate) * FIXQ31); + + _k = (int32_t)(2.0 * sin(M_PI * freq / sampleRate) * FIXQ32); + } + + void setGain(int32_t gain) { + gain = MIN(MAX(gain, 0), 0x7fffffff); + + _gain = gain; + } + + void process(int32_t& lfoSin, int32_t& lfoCos) { + lfoSin = _y0; + lfoCos = _y1; + + // "Magic Circle" quadrature oscillator + _y1 -= MULHI(_k, _y0); + _y0 += MULHI(_k, _y1); + + // since the oscillators are in quadrature, zero-crossing in one detects a peak in the other + if ((lfoCos ^ _y1) < 0) { + //_rand0 = 69069 * _rand0 + 1; + _rand0 = (11 * _rand0 + 0x04000000) & 0xfc000000; // periodic version + + _q0 = _r0; + _r0 = 2 * MULHI((int32_t)_rand0, _gain); // Q31 + + // scale the peak-to-peak segment to traverse from q0 to r0 + _m0 = _r0/2 - _q0/2; // slope in Q31 + _b0 = _r0/2 + _q0/2; // offset in Q31 + + int32_t sign = _y1 >> 31; + _m0 = (_m0 ^ sign) - sign; + } + if ((lfoSin ^ _y0) < 0) { + //_rand1 = 69069 * _rand1 + 1; + _rand1 = (11 * _rand1 + 0x04000000) & 0xfc000000; // periodic version + + _q1 = _r1; + _r1 = 2 * MULHI((int32_t)_rand1, _gain); // Q31 + + // scale the peak-to-peak segment to traverse from q1 to r1 + _m1 = _q1/2 - _r1/2; // slope in Q31 + _b1 = _q1/2 + _r1/2; // offset in Q31 + + int32_t sign = _y0 >> 31; + _m1 = (_m1 ^ sign) - sign; + } + + lfoSin = 2 * MULHI(lfoSin, _m0) + _b0; // Q31 + lfoCos = 2 * MULHI(lfoCos, _m1) + _b1; // Q31 + } +}; + +template +class AllPassMod { + + float _buffer[N] {}; + + float _output = 0.0f; + float _coef = 0.5f; + + int _index = 0; + int _delay = N; + +public: + void setDelay(int d) { + d = MIN(MAX(d, 1), N); + + _delay = d; + } + + int getDelay() { + return _delay; + } + + void setCoef(float coef) { + coef = MIN(MAX(coef, -1.0f), 1.0f); + + _coef = coef; + } + + void process(float input, int32_t mod, float& output) { + output = _output; + + // add modulation to delay + uint32_t offset = _delay + (mod >> MOD_FRACBITS); + float frac = (mod & MOD_FRACMASK) * QMOD_TO_FLOAT; + + // 3rd-order Lagrange interpolation + int k0 = (_index - (offset-1)) & (N - 1); + int k1 = (_index - (offset+0)) & (N - 1); + int k2 = (_index - (offset+1)) & (N - 1); + int k3 = (_index - (offset+2)) & (N - 1); + + float x0 = _buffer[k0]; + float x1 = _buffer[k1]; + float x2 = _buffer[k2]; + float x3 = _buffer[k3]; + + // compute the polynomial coefficients + float c0 = (1/6.0f) * (x3 - x0) + (1/2.0f) * (x1 - x2); + float c1 = (1/2.0f) * (x0 + x2) - x1; + float c2 = x2 - (1/3.0f) * x0 - (1/2.0f) * x1 - (1/6.0f) * x3; + float c3 = x1; + + // compute the polynomial + float delayMod = ((c0 * frac + c1) * frac + c2) * frac + c3; + + _output = delayMod - _coef * input; // feedforward path + _buffer[_index] = input + _coef * _output; // feedback path + + _index = (_index + 1) & (N - 1); + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +class LowpassEQ { + + float _buffer[2] {}; + + float _output = 0.0f; + + float _b0 = 1.0f; + float _b1 = 0.0f; + float _b2 = 0.0f; + +public: + void setFreq(float sampleRate) { + sampleRate = MIN(MAX(sampleRate, 24000.0f), 48000.0f); + + // two-zero lowpass filter, with zeros at approximately 12khz + // zero radius is adjusted to match the response from 0..9khz + _b0 = 0.5f; + _b1 = 0.5f * sqrtf(2.0f * 12000.0f/(0.5f * sampleRate) - 1.0f); + _b2 = 0.5f - _b1; + } + + void process(float input, float& output) { + output = _output; + + _output = _b0 * input + _b1 * _buffer[0] + _b2 * _buffer[1]; + _buffer[1] = _buffer[0]; + _buffer[0] = input; + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +class DampingEQ { + + float _buffer[2] {}; + + float _output = 0.0f; + + float _b0 = 1.0f; + float _b1 = 0.0f; + float _b2 = 0.0f; + float _a1 = 0.0f; + float _a2 = 0.0f; + +public: + void setCoef(float dBgain0, float dBgain1, float freq0, float freq1, float sampleRate) { + dBgain0 = MIN(MAX(dBgain0, -100.0f), 100.0f); + dBgain1 = MIN(MAX(dBgain1, -100.0f), 100.0f); + freq0 = MIN(MAX(freq0, 1.0f), 24000.0f); + freq1 = MIN(MAX(freq1, 1.0f), 24000.0f); + + double coefLo[3], coefHi[3]; + PZShelf(coefLo, M_PI * freq0 / (0.5 * sampleRate), dBgain0, 0); // low shelf + PZShelf(coefHi, M_PI * freq1 / (0.5 * sampleRate), dBgain1, 1); // high shelf + + // convolve into a single biquad + _b0 = (float)(coefLo[0] * coefHi[0]); + _b1 = (float)(coefLo[0] * coefHi[1] + coefLo[1] * coefHi[0]); + _b2 = (float)(coefLo[1] * coefHi[1]); + _a1 = (float)(coefLo[2] + coefHi[2]); + _a2 = (float)(coefLo[2] * coefHi[2]); + } + + void process(float input, float& output) { + output = _output; + + // transposed Direct Form II + _output = _b0 * input + _buffer[0]; + _buffer[0] = _b1 * input - _a1 * _output + _buffer[1]; + _buffer[1] = _b2 * input - _a2 * _output; + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +template +class MultiTap2 { + + float _buffer[N] {}; + + float _output0 = 0.0f; + float _output1 = 0.0f; + + float _gain0 = 1.0f; + float _gain1 = 1.0f; + + int _index = 0; + int _delay0 = N; + int _delay1 = N; + +public: + void setDelay(int d0, int d1) { + d0 = MIN(MAX(d0, 1), N); + d1 = MIN(MAX(d1, 1), N); + + _delay0 = d0; + _delay1 = d1; + } + + int getDelay(int k) { + switch (k) { + case 0: return _delay0; + case 1: return _delay1; + default: return 0; + } + } + + void setGain(float g0, float g1) { + _gain0 = g0; + _gain1 = g1; + } + + void process(float input, float& output0, float& output1) { + output0 = _output0; + output1 = _output1; + + int k0 = (_index - _delay0) & (N - 1); + int k1 = (_index - _delay1) & (N - 1); + + _output0 = _gain0 * _buffer[k0]; + _output1 = _gain1 * _buffer[k1]; + + _buffer[_index] = input; + _index = (_index + 1) & (N - 1); + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output0 = 0.0f; + _output1 = 0.0f; + } +}; + +template +class MultiTap3 { + + float _buffer[N] {}; + + float _output0 = 0.0f; + float _output1 = 0.0f; + float _output2 = 0.0f; + + float _gain0 = 1.0f; + float _gain1 = 1.0f; + float _gain2 = 1.0f; + + int _index = 0; + int _delay0 = N; + int _delay1 = N; + int _delay2 = N; + +public: + void setDelay(int d0, int d2) { + d0 = MIN(MAX(d0, 1), N); + d2 = MIN(MAX(d2, 1), N); + + _delay0 = d0; + _delay1 = d0 - 1; + _delay2 = d2; + } + + int getDelay(int k) { + switch (k) { + case 0: return _delay0; + case 1: return _delay1; + case 2: return _delay2; + default: return 0; + } + } + + void setGain(float g0, float g1, float g2) { + _gain0 = g0; + _gain1 = g1; + _gain2 = g2; + } + + void process(float input, float& output0, float& output1, float& output2) { + output0 = _output0; + output1 = _output1; + output2 = _output2; + + int k0 = (_index - _delay0) & (N - 1); + int k1 = (_index - _delay1) & (N - 1); + int k2 = (_index - _delay2) & (N - 1); + + _output0 = _gain0 * _buffer[k0]; + _output1 = _gain1 * _buffer[k1]; + _output2 = _gain2 * _buffer[k2]; + + _buffer[_index] = input; + _index = (_index + 1) & (N - 1); + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output0 = 0.0f; + _output1 = 0.0f; + _output2 = 0.0f; + } +}; + +// +// Stereo Reverb +// +class ReverbImpl { + + // Preprocess + BandwidthEQ _bw; + DelayLine _dl0; + DelayLine _dl1; + + // Early Left + float _earlyMix1L = 0.0f; + float _earlyMix2L = 0.0f; + + MultiTap3 _mt0; + Allpass _ap0; + MultiTap3 _mt1; + Allpass _ap1; + Allpass _ap2; + MultiTap2 _mt2; + + // Early Right + float _earlyMix1R = 0.0f; + float _earlyMix2R = 0.0f; + + MultiTap3 _mt3; + Allpass _ap3; + MultiTap3 _mt4; + Allpass _ap4; + Allpass _ap5; + MultiTap2 _mt5; + + RandomLFO _lfo; + + // Late + Allpass _ap6; + AllPassMod _ap7; + DampingEQ _eq0; + MultiTap2 _mt6; + + Allpass _ap8; + AllPassMod _ap9; + DampingEQ _eq1; + MultiTap2 _mt7; + + Allpass _ap10; + Allpass _ap11; + Allpass _ap12; + Allpass _ap13; + MultiTap2 _mt8; + LowpassEQ _lp0; + + Allpass _ap14; + Allpass _ap15; + Allpass _ap16; + Allpass _ap17; + MultiTap2 _mt9; + LowpassEQ _lp1; + + // Output Left + Allpass _ap18; + Allpass _ap19; + + // Output Right + Allpass _ap20; + Allpass _ap21; + + float _earlyGain = 0.0f; + float _wetDryMix = 0.0f; + +public: + void setParameters(ReverbParameters *p); + void process(float** inputs, float** outputs, int numFrames); + void reset(); +}; + +static const short primeTable[] = { + 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, + 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, + 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, + 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, + 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, + 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, + 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, + 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, + 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, + 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, + 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, + 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, + 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, + 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, + 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, + 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, + 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, + 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, + 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, + 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, + 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, + 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, + 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, + 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, + 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, + 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, + 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, + 2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, + 2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, + 3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, + 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, + 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, + 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, + 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, + 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, + 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, + 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, + 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, + 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, + 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, + 4409, 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, + 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, + 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, + 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, + 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, + 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, + 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, + 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, + 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, + 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, + 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, + 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, + 5939, 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, + 6091, 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, + 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, + 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, + 6473, 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, + 6619, 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, + 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, + 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, + 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, + 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, + 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, + 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, + 7561, 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, + 7687, 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, + 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, 7927, 7933, 7937, 7949, + 7951, 7963, 7993, 8009, 8011, 8017, 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, + 8111, 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, +}; + +static int getPrime(int n) { + int low = 0; + int high = sizeof(primeTable) / sizeof(primeTable[0]) - 1; + + // clip to table limits + if (n <= primeTable[low]) { + return primeTable[low]; + } + if (n >= primeTable[high]) { + return primeTable[high]; + } + + // binary search + while (low <= high) { + int mid = (low + high) >> 1; + + if (n < primeTable[mid]) { + high = mid - 1; + } else if (n > primeTable[mid]) { + low = mid + 1; + } else { + return n; // found it + } + } + + //return primeTable[high]; // lower prime + //return (n - primeTable[high]) < (primeTable[low] - n) ? primeTable[high] : primeTable[low]; // nearest prime + return primeTable[low]; // higher prime +} + +static int scaleDelay(float delay, float sampleRate) { + return getPrime((int)(delay * (sampleRate/48000.0f) + 0.5f)); +} + +// +// Piecewise-linear lookup tables +// input clamped to [0.0f, 100.0f] +// +static const float earlyMix0Table[][2] = { + 0.0000f, 0.6000f, + 63.3333f, 0.0800f, + 83.3333f, 0.0200f, + 93.3333f, 0.0048f, + 100.0000f, 0.0048f, +}; + +static const float earlyMix1Table[][2] = { + 0.0000f, 0.3360f, + 20.0000f, 0.6000f, + 100.0000f, 0.0240f, +}; + +static const float earlyMix2Table[][2] = { + 0.0000f, 0.0480f, + 13.3333f, 0.0960f, + 53.3333f, 0.9600f, + 100.0000f, 0.1200f, +}; + +static const float lateMix0Table[][2] = { + 0.0000f, 0.1250f, + 13.3333f, 0.1875f, + 66.6666f, 0.7500f, + 100.0000f, 0.8750f, +}; + +static const float lateMix1Table[][2] = { + 0.0000f, 0.9990f, + 33.3333f, 0.5000f, + 66.6666f, 0.9990f, + 93.3333f, 0.6000f, + 100.0000f, 0.6000f, +}; + +static const float lateMix2Table[][2] = { + 0.0000f, 0.9990f, + 33.3333f, 0.9990f, + 63.3333f, 0.4500f, + 100.0000f, 0.9990f, +}; + +static const float diffusionCoefTable[][2] = { + 0.0000f, 0.0000f, + 20.0000f, 0.0470f, + 33.3333f, 0.0938f, + 46.6666f, 0.1563f, + 60.0000f, 0.2344f, + 73.3333f, 0.3125f, + 93.3333f, 0.5000f, + 100.0000f, M_PHI, +}; + +static const float roomSizeTable[][2] = { + 0.0000f, 0.1500f, + 25.0000f, 0.3000f, + 50.0000f, 0.5000f, + 100.0000f, 1.0000f, +}; + +static float interpolateTable(const float table[][2], float x) { + x = MIN(MAX(x, 0.0f), 100.0f); + + // locate the segment in the table + int i = 0; + while (x > table[i+1][0]) { + i++; + } + + // linear interpolate + float frac = (x - table[i+0][0]) / (table[i+1][0] - table[i+0][0]); + return table[i+0][1] + frac * (table[i+1][1] - table[i+0][1]); +} + +void ReverbImpl::setParameters(ReverbParameters *p) { + + float sampleRate = MIN(MAX(p->sampleRate, 24000.0f), 48000.0f); + + // Bandwidth + _bw.setFreq(p->bandwidth, sampleRate); + + // Modulation + _lfo.setFreq(p->modRate, sampleRate); + _lfo.setGain((int32_t)MIN(MAX(p->modDepth * (1/100.0) * FIXQ31, 0.0), 0X7fffffff)); + + // + // Set delays + // + int preDelay = (int)(p->preDelay * (1/1000.0f) * sampleRate + 0.5f); + preDelay = MIN(MAX(preDelay, 1), M_PD0); + _dl0.setDelay(preDelay); + _dl1.setDelay(preDelay); + + // RoomSize scalefactor + float roomSize = interpolateTable(roomSizeTable, p->roomSize); + + // Density scalefactors + float density0 = p->density * (1/25.0f) - 0.0f; + float density1 = p->density * (1/25.0f) - 1.0f; + float density2 = p->density * (1/25.0f) - 2.0f; + float density3 = p->density * (1/25.0f) - 3.0f; + density0 = MIN(MAX(density0, 0.0f), 1.0f); + density1 = MIN(MAX(density1, 0.0f), 1.0f); + density2 = MIN(MAX(density2, 0.0f), 1.0f); + density3 = MIN(MAX(density3, 0.0f), 1.0f); + + // Early delays + _ap0.setDelay(scaleDelay(M_AP0 * 1.0f, sampleRate)); + _ap1.setDelay(scaleDelay(M_AP1 * 1.0f, sampleRate)); + _ap2.setDelay(scaleDelay(M_AP2 * 1.0f, sampleRate)); + _ap3.setDelay(scaleDelay(M_AP3 * 1.0f, sampleRate)); + _ap4.setDelay(scaleDelay(M_AP4 * 1.0f, sampleRate)); + _ap5.setDelay(scaleDelay(M_AP5 * 1.0f, sampleRate)); + + _mt0.setDelay(scaleDelay(M_MT0 * roomSize, sampleRate), 1); + _mt1.setDelay(scaleDelay(M_MT1 * roomSize, sampleRate), scaleDelay(M_MT1_2 * 1.0f, sampleRate)); + _mt2.setDelay(scaleDelay(M_MT2 * roomSize, sampleRate), 1); + _mt3.setDelay(scaleDelay(M_MT3 * roomSize, sampleRate), 1); + _mt4.setDelay(scaleDelay(M_MT4 * roomSize, sampleRate), scaleDelay(M_MT4_2 * 1.0f, sampleRate)); + _mt5.setDelay(scaleDelay(M_MT5 * roomSize, sampleRate), 1); + + // Late delays + _ap6.setDelay(scaleDelay(M_AP6 * roomSize * density3, sampleRate)); + _ap7.setDelay(scaleDelay(M_AP7 * roomSize, sampleRate)); + _ap8.setDelay(scaleDelay(M_AP8 * roomSize * density3, sampleRate)); + _ap9.setDelay(scaleDelay(M_AP9 * roomSize, sampleRate)); + _ap10.setDelay(scaleDelay(M_AP10 * roomSize * density1, sampleRate)); + _ap11.setDelay(scaleDelay(M_AP11 * roomSize * density2, sampleRate)); + _ap12.setDelay(scaleDelay(M_AP12 * roomSize, sampleRate)); + _ap13.setDelay(scaleDelay(M_AP13 * roomSize * density3, sampleRate)); + _ap14.setDelay(scaleDelay(M_AP14 * roomSize * density1, sampleRate)); + _ap15.setDelay(scaleDelay(M_AP15 * roomSize * density2, sampleRate)); + _ap16.setDelay(scaleDelay(M_AP16 * roomSize * density3, sampleRate)); + _ap17.setDelay(scaleDelay(M_AP17 * roomSize * density3, sampleRate)); + + int lateDelay = scaleDelay(p->lateDelay * (1/1000.0f) * 48000, sampleRate); + lateDelay = MIN(MAX(lateDelay, 1), M_LD0); + + _mt6.setDelay(scaleDelay(M_MT6 * roomSize * density3, sampleRate), lateDelay); + _mt7.setDelay(scaleDelay(M_MT7 * roomSize * density2, sampleRate), lateDelay); + _mt8.setDelay(scaleDelay(M_MT8 * roomSize * density0, sampleRate), lateDelay); + _mt9.setDelay(scaleDelay(M_MT9 * roomSize, sampleRate), lateDelay); + + // Output delays + _ap18.setDelay(scaleDelay(M_AP18 * 1.0f, sampleRate)); + _ap19.setDelay(scaleDelay(M_AP19 * 1.0f, sampleRate)); + _ap20.setDelay(scaleDelay(M_AP20 * 1.0f, sampleRate)); + _ap21.setDelay(scaleDelay(M_AP21 * 1.0f, sampleRate)); + + // RT60 is determined by mean delay of feedback paths + int loopDelay; + loopDelay = _ap6.getDelay(); + loopDelay += _ap7.getDelay(); + loopDelay += _ap8.getDelay(); + loopDelay += _ap9.getDelay(); + loopDelay += _ap10.getDelay(); + loopDelay += _ap11.getDelay(); + loopDelay += _ap12.getDelay(); + loopDelay += _ap13.getDelay(); + loopDelay += _ap14.getDelay(); + loopDelay += _ap15.getDelay(); + loopDelay += _ap16.getDelay(); + loopDelay += _ap17.getDelay(); + loopDelay += _mt6.getDelay(0); + loopDelay += _mt7.getDelay(0); + loopDelay += _mt8.getDelay(0); + loopDelay += _mt9.getDelay(0); + loopDelay /= 2; + + // + // Set gains + // + float rt60 = MIN(MAX(p->reverbTime, 0.01f), 100.0f); + float rt60Gain = -60.0f * loopDelay / (rt60 * sampleRate); // feedback gain (dB) for desired RT + + float bassMult = MIN(MAX(p->bassMult, 0.1f), 10.0f); + float bassGain = (1.0f / bassMult - 1.0f) * rt60Gain; // filter gain (dB) that results in RT *= mult + + float loopGain1 = sqrtf(0.5f * dBToGain(rt60Gain)); // distributed (series-parallel) loop gain + float loopGain2 = 0.9f - (0.63f * loopGain1); + + // Damping + _eq0.setCoef(bassGain, p->highGain, p->bassFreq, p->highFreq, sampleRate); + _eq1.setCoef(bassGain, p->highGain, p->bassFreq, p->highFreq, sampleRate); + _lp0.setFreq(sampleRate); + _lp1.setFreq(sampleRate); + + float earlyDiffusionCoef = interpolateTable(diffusionCoefTable, p->earlyDiffusion); + + // Early Left + _earlyMix1L = interpolateTable(earlyMix1Table, p->earlyMixRight); + _earlyMix2L = interpolateTable(earlyMix2Table, p->earlyMixLeft); + + _mt0.setGain(0.2f, 0.4f, interpolateTable(earlyMix0Table, p->earlyMixLeft)); + + _ap0.setCoef(earlyDiffusionCoef); + _ap1.setCoef(earlyDiffusionCoef); + _ap2.setCoef(earlyDiffusionCoef); + + _mt1.setGain(0.2f, 0.6f, interpolateTable(lateMix0Table, p->lateMixLeft) * 0.125f); + + _mt2.setGain(interpolateTable(lateMix1Table, p->lateMixLeft) * loopGain2, + interpolateTable(lateMix2Table, p->lateMixLeft) * loopGain2); + + // Early Right + _earlyMix1R = interpolateTable(earlyMix1Table, p->earlyMixLeft); + _earlyMix2R = interpolateTable(earlyMix2Table, p->earlyMixRight); + + _mt3.setGain(0.2f, 0.4f, interpolateTable(earlyMix0Table, p->earlyMixRight)); + + _ap3.setCoef(earlyDiffusionCoef); + _ap4.setCoef(earlyDiffusionCoef); + _ap5.setCoef(earlyDiffusionCoef); + + _mt4.setGain(0.2f, 0.6f, interpolateTable(lateMix0Table, p->lateMixRight) * 0.125f); + + _mt5.setGain(interpolateTable(lateMix1Table, p->lateMixRight) * loopGain2, + interpolateTable(lateMix2Table, p->lateMixRight) * loopGain2); + + _earlyGain = dBToGain(p->earlyGain); + + // Late + float lateDiffusionCoef = interpolateTable(diffusionCoefTable, p->lateDiffusion); + _ap6.setCoef(lateDiffusionCoef); + _ap7.setCoef(lateDiffusionCoef); + _ap8.setCoef(lateDiffusionCoef); + _ap9.setCoef(lateDiffusionCoef); + + _ap10.setCoef(M_PHI); + _ap11.setCoef(M_PHI); + _ap12.setCoef(lateDiffusionCoef); + _ap13.setCoef(lateDiffusionCoef); + + _ap14.setCoef(M_PHI); + _ap15.setCoef(M_PHI); + _ap16.setCoef(lateDiffusionCoef); + _ap17.setCoef(lateDiffusionCoef); + + float lateGain = dBToGain(p->lateGain) * 2.0f; + _mt6.setGain(loopGain1, lateGain * interpolateTable(lateMix0Table, p->lateMixLeft)); + _mt7.setGain(loopGain1, lateGain * interpolateTable(lateMix0Table, p->lateMixRight)); + _mt8.setGain(loopGain1, lateGain * interpolateTable(lateMix2Table, p->lateMixLeft) * loopGain2 * 0.125f); + _mt9.setGain(loopGain1, lateGain * interpolateTable(lateMix2Table, p->lateMixRight) * loopGain2 * 0.125f); + + // Output + float outputDiffusionCoef = lateDiffusionCoef * 0.6f; + _ap18.setCoef(outputDiffusionCoef); + _ap19.setCoef(outputDiffusionCoef); + _ap20.setCoef(outputDiffusionCoef); + _ap21.setCoef(outputDiffusionCoef); + + _wetDryMix = p->wetDryMix * (1/100.0f); + _wetDryMix = MIN(MAX(_wetDryMix, 0.0f), 1.0f); +} + +void ReverbImpl::process(float** inputs, float** outputs, int numFrames) { + + for (int i = 0; i < numFrames; i++) { + float x0, x1, y0, y1, y2, y3; + + // Preprocess + x0 = inputs[0][i]; + x1 = inputs[1][i]; + _bw.process(x0, x1, x0, x1); + + float preL, preR; + _dl0.process(x0, preL); + _dl1.process(x1, preR); + + // Early Left + float early0L, early1L, early2L, earlyOutL; + _mt0.process(preL, x0, x1, y0); + _ap0.process(x0 + x1, y1); + _mt1.process(y1, x0, x1, early0L); + _ap1.process(x0 + x1, y2); + _ap2.process(y2, x0); + _mt2.process(x0, early1L, early2L); + + earlyOutL = (y0 + y1 * _earlyMix1L + y2 * _earlyMix2L) * _earlyGain; + + // Early Right + float early0R, early1R, early2R, earlyOutR; + _mt3.process(preR, x0, x1, y0); + _ap3.process(x0 + x1, y1); + _mt4.process(y1, x0, x1, early0R); + _ap4.process(x0 + x1, y2); + _ap5.process(y2, x0); + _mt5.process(x0, early1R, early2R); + + earlyOutR = (y0 + y1 * _earlyMix1R + y2 * _earlyMix2R) * _earlyGain; + + // LFO update + int32_t lfoSin, lfoCos; + _lfo.process(lfoSin, lfoCos); + + // Late + float lateOut0; + _ap6.getOutput(x0); + _ap7.process(x0, lfoSin, x0); + _eq0.process(-early0L + x0, x0); + _mt6.process(x0, y0, lateOut0); + + float lateOut1; + _ap8.getOutput(x0); + _ap9.process(x0, lfoCos, x0); + _eq1.process(-early0R + x0, x0); + _mt7.process(x0, y1, lateOut1); + + float lateOut2; + _ap10.getOutput(x0); + _ap11.process(-early2L + x0, x0); + _ap12.process(x0, x0); + _ap13.process(-early2L - x0, x0); + _mt8.process(-early0L + x0, x0, lateOut2); + _lp0.process(x0, y2); + + float lateOut3; + _ap14.getOutput(x0); + _ap15.process(-early2R + x0, x0); + _ap16.process(x0, x0); + _ap17.process(-early2R - x0, x0); + _mt9.process(-early0R + x0, x0, lateOut3); + _lp1.process(x0, y3); + + // Feedback matrix + _ap6.process(early1L + y2 - y3, x0); + _ap8.process(early1R - y2 - y3, x0); + _ap10.process(-early2R + y0 + y1, x0); + _ap14.process(-early2L - y0 + y1, x0); + + // Output Left + _ap18.process(-earlyOutL + lateOut0 + lateOut3, x0); + _ap19.process(x0, y0); + + // Output Right + _ap20.process(-earlyOutR + lateOut1 + lateOut2, x1); + _ap21.process(x1, y1); + + x0 = inputs[0][i]; + x1 = inputs[1][i]; + outputs[0][i] = x0 + (y0 - x0) * _wetDryMix; + outputs[1][i] = x1 + (y1 - x1) * _wetDryMix; + } +} + +// clear internal state, but retain settings +void ReverbImpl::reset() { + + _bw.reset(); + + _dl0.reset(); + _dl1.reset(); + + _mt0.reset(); + _mt1.reset(); + _mt2.reset(); + _mt3.reset(); + _mt4.reset(); + _mt5.reset(); + _mt6.reset(); + _mt7.reset(); + _mt8.reset(); + _mt9.reset(); + + _ap0.reset(); + _ap1.reset(); + _ap2.reset(); + _ap3.reset(); + _ap4.reset(); + _ap5.reset(); + _ap6.reset(); + _ap7.reset(); + _ap8.reset(); + _ap9.reset(); + _ap10.reset(); + _ap11.reset(); + _ap12.reset(); + _ap13.reset(); + _ap14.reset(); + _ap15.reset(); + _ap16.reset(); + _ap17.reset(); + _ap18.reset(); + _ap19.reset(); + _ap20.reset(); + _ap21.reset(); + + _eq0.reset(); + _eq1.reset(); + + _lp0.reset(); + _lp1.reset(); +} + +// +// Public API +// + +static const int REVERB_BLOCK = 1024; + +AudioReverb::AudioReverb(float sampleRate) { + + _impl = new ReverbImpl; + + // format conversion buffers + _inout[0] = new float[REVERB_BLOCK]; + _inout[1] = new float[REVERB_BLOCK]; + + // default parameters + ReverbParameters p; + p.sampleRate = sampleRate; + p.bandwidth = 10000.0f; + + p.preDelay = 20.0f; + p.lateDelay = 0.0f; + + p.reverbTime = 2.0f; + + p.earlyDiffusion = 100.0f; + p.lateDiffusion = 100.0f; + + p.roomSize = 50.0f; + p.density = 100.0f; + + p.bassMult = 1.5f; + p.bassFreq = 250.0f; + p.highGain = -6.0f; + p.highFreq = 3000.0f; + + p.modRate = 2.3f; + p.modDepth = 50.0f; + + p.earlyGain = 0.0f; + p.lateGain = 0.0f; + + p.earlyMixLeft = 20.0f; + p.earlyMixRight = 20.0f; + p.lateMixLeft = 90.0f; + p.lateMixRight = 90.0f; + + p.wetDryMix = 100.0f; + + setParameters(&p); +} + +AudioReverb::~AudioReverb() { + delete _impl; + + delete[] _inout[0]; + delete[] _inout[1]; +} + +void AudioReverb::setParameters(ReverbParameters *p) { + _params = *p; + _impl->setParameters(p); +}; + +void AudioReverb::getParameters(ReverbParameters *p) { + *p = _params; +}; + +void AudioReverb::reset() { + _impl->reset(); +} + +void AudioReverb::render(float** inputs, float** outputs, int numFrames) { + _impl->process(inputs, outputs, numFrames); +} + +// +// on x86 architecture, assume that SSE2 is present +// +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) + +#include + +// convert int16_t to float, deinterleave stereo +void AudioReverb::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { + __m128 scale = _mm_set1_ps(1/32768.0f); + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128i a0 = _mm_loadu_si128((__m128i*)&input[2*i]); + __m128i a1 = a0; + + // deinterleave and sign-extend + a0 = _mm_madd_epi16(a0, _mm_set1_epi32(0x00000001)); + a1 = _mm_madd_epi16(a1, _mm_set1_epi32(0x00010000)); + + __m128 f0 = _mm_mul_ps(_mm_cvtepi32_ps(a0), scale); + __m128 f1 = _mm_mul_ps(_mm_cvtepi32_ps(a1), scale); + + _mm_storeu_ps(&outputs[0][i], f0); + _mm_storeu_ps(&outputs[1][i], f1); + } + for (; i < numFrames; i++) { + __m128i a0 = _mm_cvtsi32_si128(*(int32_t*)&input[2*i]); + __m128i a1 = a0; + + // deinterleave and sign-extend + a0 = _mm_madd_epi16(a0, _mm_set1_epi32(0x00000001)); + a1 = _mm_madd_epi16(a1, _mm_set1_epi32(0x00010000)); + + __m128 f0 = _mm_mul_ps(_mm_cvtepi32_ps(a0), scale); + __m128 f1 = _mm_mul_ps(_mm_cvtepi32_ps(a1), scale); + + _mm_store_ss(&outputs[0][i], f0); + _mm_store_ss(&outputs[1][i], f1); + } +} + +// fast TPDF dither in [-1.0f, 1.0f] +static inline __m128 dither4() { + static __m128i rz; + + // update the 8 different maximum-length LCGs + rz = _mm_mullo_epi16(rz, _mm_set_epi16(25173, -25511, -5975, -23279, 19445, -27591, 30185, -3495)); + rz = _mm_add_epi16(rz, _mm_set_epi16(13849, -32767, 105, -19675, -7701, -32679, -13225, 28013)); + + // promote to 32-bit + __m128i r0 = _mm_unpacklo_epi16(rz, _mm_setzero_si128()); + __m128i r1 = _mm_unpackhi_epi16(rz, _mm_setzero_si128()); + + // return (r0 - r1) * (1/65536.0f); + __m128 d0 = _mm_cvtepi32_ps(_mm_sub_epi32(r0, r1)); + return _mm_mul_ps(d0, _mm_set1_ps(1/65536.0f)); +} + +// convert float to int16_t, interleave stereo +void AudioReverb::convertOutputToInt16(float** inputs, int16_t* output, int numFrames) { + __m128 scale = _mm_set1_ps(32768.0f); + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128 f0 = _mm_mul_ps(_mm_loadu_ps(&inputs[0][i]), scale); + __m128 f1 = _mm_mul_ps(_mm_loadu_ps(&inputs[1][i]), scale); + + __m128 d0 = dither4(); + f0 = _mm_add_ps(f0, d0); + f1 = _mm_add_ps(f1, d0); + + // round and saturate + __m128i a0 = _mm_cvtps_epi32(f0); + __m128i a1 = _mm_cvtps_epi32(f1); + a0 = _mm_packs_epi32(a0, a0); + a1 = _mm_packs_epi32(a1, a1); + + // interleave + a0 = _mm_unpacklo_epi16(a0, a1); + _mm_storeu_si128((__m128i*)&output[2*i], a0); + } + for (; i < numFrames; i++) { + __m128 f0 = _mm_mul_ps(_mm_load_ss(&inputs[0][i]), scale); + __m128 f1 = _mm_mul_ps(_mm_load_ss(&inputs[1][i]), scale); + + __m128 d0 = dither4(); + f0 = _mm_add_ps(f0, d0); + f1 = _mm_add_ps(f1, d0); + + // round and saturate + __m128i a0 = _mm_cvtps_epi32(f0); + __m128i a1 = _mm_cvtps_epi32(f1); + a0 = _mm_packs_epi32(a0, a0); + a1 = _mm_packs_epi32(a1, a1); + + // interleave + a0 = _mm_unpacklo_epi16(a0, a1); + *(int32_t*)&output[2*i] = _mm_cvtsi128_si32(a0); + } +} + +#else + +// convert int16_t to float, deinterleave stereo +void AudioReverb::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { + const float scale = 1/32768.0f; + + for (int i = 0; i < numFrames; i++) { + outputs[0][i] = (float)input[2*i + 0] * scale; + outputs[1][i] = (float)input[2*i + 1] * scale; + } +} + +// fast TPDF dither in [-1.0f, 1.0f] +static inline float dither() { + static uint32_t rz = 0; + rz = rz * 69069 + 1; + int32_t r0 = rz & 0xffff; + int32_t r1 = rz >> 16; + return (int32_t)(r0 - r1) * (1/65536.0f); +} + +// convert float to int16_t, interleave stereo +void AudioReverb::convertOutputToInt16(float** inputs, int16_t* output, int numFrames) { + const float scale = 32768.0f; + + for (int i = 0; i < numFrames; i++) { + + float f0 = inputs[0][i] * scale; + float f1 = inputs[1][i] * scale; + + float d = dither(); + f0 += d; + f1 += d; + + // round and saturate + f0 += (f0 < 0.0f ? -0.5f : +0.5f); + f1 += (f1 < 0.0f ? -0.5f : +0.5f); + f0 = MIN(MAX(f0, -32768.0f), 32767.0f); + f1 = MIN(MAX(f1, -32768.0f), 32767.0f); + + // interleave + output[2*i + 0] = (int16_t)f0; + output[2*i + 1] = (int16_t)f1; + } +} + +#endif + +// +// This version handles input/output as interleaved int16_t +// +void AudioReverb::render(const int16_t* input, int16_t* output, int numFrames) { + + while (numFrames) { + + int n = MIN(numFrames, REVERB_BLOCK); + + convertInputFromInt16(input, _inout, n); + + _impl->process(_inout, _inout, n); + + convertOutputToInt16(_inout, output, n); + + input += 2 * n; + output += 2 * n; + numFrames -= n; + } +} diff --git a/libraries/audio/src/AudioReverb.h b/libraries/audio/src/AudioReverb.h new file mode 100644 index 0000000000..f135a46127 --- /dev/null +++ b/libraries/audio/src/AudioReverb.h @@ -0,0 +1,76 @@ +// +// AudioReverb.h +// libraries/audio/src +// +// Created by Ken Cooke on 10/11/15. +// Copyright 2015 High Fidelity, Inc. +// + +#ifndef hifi_AudioReverb_h +#define hifi_AudioReverb_h + +#include "stdint.h" + +typedef struct ReverbParameters { + + float sampleRate; // [24000, 48000] Hz + float bandwidth; // [20, 24000] Hz + + float preDelay; // [0, 333] ms + float lateDelay; // [0, 166] ms + + float reverbTime; // [0.1, 100] seconds + + float earlyDiffusion; // [0, 100] percent + float lateDiffusion; // [0, 100] percent + + float roomSize; // [0, 100] percent + float density; // [0, 100] percent + + float bassMult; // [0.1, 10] ratio + float bassFreq; // [10, 500] Hz + float highGain; // [-24, 0] dB + float highFreq; // [1000, 12000] Hz + + float modRate; // [0.1, 10] Hz + float modDepth; // [0, 100] percent + + float earlyGain; // [-96, +24] dB + float lateGain; // [-96, +24] dB + + float earlyMixLeft; // [0, 100] percent + float earlyMixRight; // [0, 100] percent + float lateMixLeft; // [0, 100] percent + float lateMixRight; // [0, 100] percent + + float wetDryMix; // [0, 100] percent + +} ReverbParameters; + +class ReverbImpl; + +class AudioReverb { + + ReverbImpl *_impl; + ReverbParameters _params; + + float* _inout[2]; + void convertInputFromInt16(const int16_t* input, float** outputs, int numFrames); + void convertOutputToInt16(float** inputs, int16_t* output, int numFrames); + +public: + AudioReverb(float sampleRate); + ~AudioReverb(); + + void setParameters(ReverbParameters *p); + void getParameters(ReverbParameters *p); + void reset(); + + // deinterleaved float input/output (native format) + void render(float** inputs, float** outputs, int numFrames); + + // interleaved int16_t input/output + void render(const int16_t* input, int16_t* output, int numFrames); +}; + +#endif // hifi_AudioReverb_h From c61dad108c5125fad38cf1e360c94904a6fc4bfa Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 18 Nov 2015 09:16:18 -0800 Subject: [PATCH 02/12] Replace Gverb with new reverb --- libraries/audio-client/src/AudioClient.cpp | 184 ++++++++++++--------- libraries/audio-client/src/AudioClient.h | 12 +- 2 files changed, 114 insertions(+), 82 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a506fe217c..daf89d3473 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -119,7 +119,7 @@ AudioClient::AudioClient() : _audioSourceInjectEnabled(false), _reverb(false), _reverbOptions(&_scriptReverbOptions), - _gverb(NULL), + //_gverb(NULL), _inputToNetworkResampler(NULL), _networkToOutputResampler(NULL), _loopbackResampler(NULL), @@ -145,8 +145,8 @@ AudioClient::AudioClient() : updateTimer->start(DEVICE_CHECK_INTERVAL_MSECS); // create GVerb filter - _gverb = createGverbFilter(); - configureGverbFilter(_gverb); + //_gverb = createGverbFilter(); + configureReverb(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket"); @@ -160,9 +160,9 @@ AudioClient::AudioClient() : AudioClient::~AudioClient() { stop(); - if (_gverb) { - gverb_free(_gverb); - } + //if (_gverb) { + // gverb_free(_gverb); + //} } void AudioClient::reset() { @@ -173,7 +173,8 @@ void AudioClient::reset() { _sourceGain.reset(); _inputGain.reset(); - gverb_flush(_gverb); + //gverb_flush(_gverb); + _stereoReverb.reset(); } void AudioClient::audioMixerKilled() { @@ -568,27 +569,42 @@ bool AudioClient::switchOutputToAudioDevice(const QString& outputDeviceName) { return switchOutputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName)); } -ty_gverb* AudioClient::createGverbFilter() { - // Initialize a new gverb instance - ty_gverb* filter = gverb_new(_outputFormat.sampleRate(), _reverbOptions->getMaxRoomSize(), _reverbOptions->getRoomSize(), - _reverbOptions->getReverbTime(), _reverbOptions->getDamping(), _reverbOptions->getSpread(), - _reverbOptions->getInputBandwidth(), _reverbOptions->getEarlyLevel(), - _reverbOptions->getTailLevel()); +//ty_gverb* AudioClient::createGverbFilter() { +// // Initialize a new gverb instance +// ty_gverb* filter = gverb_new(_outputFormat.sampleRate(), _reverbOptions->getMaxRoomSize(), _reverbOptions->getRoomSize(), +// _reverbOptions->getReverbTime(), _reverbOptions->getDamping(), _reverbOptions->getSpread(), +// _reverbOptions->getInputBandwidth(), _reverbOptions->getEarlyLevel(), +// _reverbOptions->getTailLevel()); +// +// return filter; +//} - return filter; -} - -void AudioClient::configureGverbFilter(ty_gverb* filter) { +void AudioClient::configureReverb() { // Configure the instance (these functions are not super well named - they actually set several internal variables) - gverb_set_roomsize(filter, _reverbOptions->getRoomSize()); - gverb_set_revtime(filter, _reverbOptions->getReverbTime()); - gverb_set_damping(filter, _reverbOptions->getDamping()); - gverb_set_inputbandwidth(filter, _reverbOptions->getInputBandwidth()); - gverb_set_earlylevel(filter, DB_CO(_reverbOptions->getEarlyLevel())); - gverb_set_taillevel(filter, DB_CO(_reverbOptions->getTailLevel())); + //gverb_set_roomsize(filter, _reverbOptions->getRoomSize()); + //gverb_set_revtime(filter, _reverbOptions->getReverbTime()); + //gverb_set_damping(filter, _reverbOptions->getDamping()); + //gverb_set_inputbandwidth(filter, _reverbOptions->getInputBandwidth()); + //gverb_set_earlylevel(filter, DB_CO(_reverbOptions->getEarlyLevel())); + //gverb_set_taillevel(filter, DB_CO(_reverbOptions->getTailLevel())); + + ReverbParameters p; + _stereoReverb.getParameters(&p); + + // for now, use the gverb settings + p.sampleRate = _outputFormat.sampleRate(); + p.roomSize = _reverbOptions->getRoomSize(); + p.reverbTime = _reverbOptions->getReverbTime(); + p.highGain = -24.0f * _reverbOptions->getDamping(); + p.bandwidth = 12000.0f * _reverbOptions->getInputBandwidth(); + //p.earlyGain = _reverbOptions->getEarlyLevel(); + //p.lateGain = _reverbOptions->getTailLevel(); + p.wetDryMix = _shouldEchoLocally ? DB_CO(_reverbOptions->getWetLevel()) : 100.0f; // !_shouldEchoLocally apparently means 100% wet? + + _stereoReverb.setParameters(&p); } -void AudioClient::updateGverbOptions() { +void AudioClient::updateReverbOptions() { bool reverbChanged = false; if (_receivedAudioStream.hasReverb()) { @@ -599,6 +615,7 @@ void AudioClient::updateGverbOptions() { if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); // Not part of actual filter config, no need to set reverbChanged to true + reverbChanged = true; } if (_reverbOptions != &_zoneReverbOptions) { @@ -611,9 +628,9 @@ void AudioClient::updateGverbOptions() { } if (reverbChanged) { - gverb_free(_gverb); - _gverb = createGverbFilter(); - configureGverbFilter(_gverb); + //gverb_free(_gverb); + //_gverb = createGverbFilter(); + configureReverb(); } } @@ -621,7 +638,8 @@ void AudioClient::setReverb(bool reverb) { _reverb = reverb; if (!_reverb) { - gverb_flush(_gverb); + //gverb_flush(_gverb); + _stereoReverb.reset(); } } @@ -641,49 +659,49 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) { if (_reverbOptions == &_scriptReverbOptions) { // Apply them to the reverb instances - gverb_free(_gverb); - _gverb = createGverbFilter(); - configureGverbFilter(_gverb); + //gverb_free(_gverb); + //_gverb = createGverbFilter(); + configureReverb(); } } -void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reverbAlone, int numSamples, - QAudioFormat& audioFormat, bool noEcho) { - float wetFraction = DB_CO(_reverbOptions->getWetLevel()); - float dryFraction = 1.0f - wetFraction; - - float lValue,rValue; - for (int sample = 0; sample < numSamples; sample += audioFormat.channelCount()) { - // Run GVerb - float value = (float)samplesData[sample]; - gverb_do(gverb, value, &lValue, &rValue); - - // Mix, accounting for clipping, the left and right channels. Ignore the rest. - for (int j = sample; j < sample + audioFormat.channelCount(); j++) { - if (j == sample) { - // left channel - int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction), - AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); - samplesData[j] = (int16_t)lResult; - - if (noEcho) { - reverbAlone[j] = (int16_t)lValue * wetFraction; - } - } else if (j == (sample + 1)) { - // right channel - int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction), - AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); - samplesData[j] = (int16_t)rResult; - - if (noEcho) { - reverbAlone[j] = (int16_t)rValue * wetFraction; - } - } else { - // ignore channels above 2 - } - } - } -} +//void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reverbAlone, int numSamples, +// QAudioFormat& audioFormat, bool noEcho) { +// float wetFraction = DB_CO(_reverbOptions->getWetLevel()); +// float dryFraction = 1.0f - wetFraction; +// +// float lValue,rValue; +// for (int sample = 0; sample < numSamples; sample += audioFormat.channelCount()) { +// // Run GVerb +// float value = (float)samplesData[sample]; +// gverb_do(gverb, value, &lValue, &rValue); +// +// // Mix, accounting for clipping, the left and right channels. Ignore the rest. +// for (int j = sample; j < sample + audioFormat.channelCount(); j++) { +// if (j == sample) { +// // left channel +// int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction), +// AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); +// samplesData[j] = (int16_t)lResult; +// +// if (noEcho) { +// reverbAlone[j] = (int16_t)lValue * wetFraction; +// } +// } else if (j == (sample + 1)) { +// // right channel +// int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction), +// AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); +// samplesData[j] = (int16_t)rResult; +// +// if (noEcho) { +// reverbAlone[j] = (int16_t)rValue * wetFraction; +// } +// } else { +// // ignore channels above 2 +// } +// } +// } +//} void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { // If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here. @@ -715,30 +733,42 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { _loopbackResampler = new AudioSRC(_inputFormat.sampleRate(), _outputFormat.sampleRate(), channelCount); } - static QByteArray reverbAlone; // Intermediary for local reverb with no echo + //static QByteArray reverbAlone; // Intermediary for local reverb with no echo static QByteArray loopBackByteArray; int numInputSamples = inputByteArray.size() / sizeof(int16_t); int numLoopbackSamples = numDestinationSamplesRequired(_inputFormat, _outputFormat, numInputSamples); - reverbAlone.resize(numInputSamples * sizeof(int16_t)); + //reverbAlone.resize(numInputSamples * sizeof(int16_t)); loopBackByteArray.resize(numLoopbackSamples * sizeof(int16_t)); int16_t* inputSamples = reinterpret_cast(inputByteArray.data()); - int16_t* reverbAloneSamples = reinterpret_cast(reverbAlone.data()); + //int16_t* reverbAloneSamples = reinterpret_cast(reverbAlone.data()); int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); - if (hasReverb) { - updateGverbOptions(); - addReverb(_gverb, inputSamples, reverbAloneSamples, numInputSamples, - _inputFormat, !_shouldEchoLocally); - } + //if (hasReverb) { + // updateGverbOptions(); + // addReverb(_gverb, inputSamples, reverbAloneSamples, numInputSamples, + // _inputFormat, !_shouldEchoLocally); + //} + + //possibleResampling(_loopbackResampler, + // (_shouldEchoLocally) ? inputSamples : reverbAloneSamples, loopbackSamples, + // numInputSamples, numLoopbackSamples, + // _inputFormat, _outputFormat); possibleResampling(_loopbackResampler, - (_shouldEchoLocally) ? inputSamples : reverbAloneSamples, loopbackSamples, + inputSamples, loopbackSamples, numInputSamples, numLoopbackSamples, _inputFormat, _outputFormat); + if (hasReverb) { + // always use the stereo reverb output + assert(_outputFormat.channelCount() == 2); + updateReverbOptions(); + _stereoReverb.render(loopbackSamples, loopbackSamples, numLoopbackSamples/2); + } + _loopbackOutputDevice->write(loopBackByteArray); } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 7d2b5a783f..c2a235938b 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -46,6 +46,7 @@ #include "AudioIOStats.h" #include "AudioNoiseGate.h" #include "AudioSRC.h" +#include "AudioReverb.h" #ifdef _WIN32 #pragma warning( push ) @@ -262,7 +263,8 @@ private: AudioEffectOptions _scriptReverbOptions; AudioEffectOptions _zoneReverbOptions; AudioEffectOptions* _reverbOptions; - ty_gverb* _gverb; + //ty_gverb* _gverb; + AudioReverb _stereoReverb { AudioConstants::SAMPLE_RATE }; // possible streams needed for resample AudioSRC* _inputToNetworkResampler; @@ -270,10 +272,10 @@ private: AudioSRC* _loopbackResampler; // Adds Reverb - ty_gverb* createGverbFilter(); - void configureGverbFilter(ty_gverb* filter); - void updateGverbOptions(); - void addReverb(ty_gverb* gverb, int16_t* samples, int16_t* reverbAlone, int numSamples, QAudioFormat& format, bool noEcho = false); + //ty_gverb* createGverbFilter(); + void configureReverb(); + void updateReverbOptions(); + //void addReverb(ty_gverb* gverb, int16_t* samples, int16_t* reverbAlone, int numSamples, QAudioFormat& format, bool noEcho = false); void handleLocalEchoAndReverb(QByteArray& inputByteArray); From 8ae3fa61c50d846cab1bf4c423e0bc47168b3045 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 18 Nov 2015 10:06:40 -0800 Subject: [PATCH 03/12] Fix compiler issues --- libraries/audio/src/AudioReverb.cpp | 60 ++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/libraries/audio/src/AudioReverb.cpp b/libraries/audio/src/AudioReverb.cpp index 3f28a9c00d..a822da3377 100644 --- a/libraries/audio/src/AudioReverb.cpp +++ b/libraries/audio/src/AudioReverb.cpp @@ -17,8 +17,8 @@ #include inline static int MULHI(int a, int b) { - long long c = __emul(a, b); - return ((int*)&c)[1]; + long long c = __emul(a, b); + return ((int*)&c)[1]; } #else @@ -33,10 +33,10 @@ inline static int MULHI(int a, int b) { #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif -static const float M_PHI = 0.6180339887f; // maximum allpass diffusion +static const float PHI = 0.6180339887f; // maximum allpass diffusion -static const double M_PI = 3.14159265358979323846; -static const double M_SQRT2 = 1.41421356237309504880; +static const double PI = 3.14159265358979323846; +static const double SQRT2 = 1.41421356237309504880; static const double FIXQ31 = 2147483648.0; static const double FIXQ32 = 4294967296.0; @@ -208,8 +208,8 @@ static void BQPeakBelowPi(double coef[5], double w0, double dbgain, double Q) { G = MAX(G, 1.001); // compute the Nyquist gain - wpi = w0 + 2.8 * (1.0 - w0/M_PI); // minimax-like error - wpi = MIN(wpi, M_PI); + wpi = w0 + 2.8 * (1.0 - w0/PI); // minimax-like error + wpi = MIN(wpi, PI); G1 = analogPeak(w0, G, Q, wpi); G1sq = G1 * G1; @@ -218,7 +218,7 @@ static void BQPeakBelowPi(double coef[5], double w0, double dbgain, double Q) { // compute the analog half-gain frequency temp = G + 2.0 * Qsq - sqrt(Gsq + 4.0 * Qsq * G); - wh = sqrt(temp) * w0 / (Q * M_SQRT2); + wh = sqrt(temp) * w0 / (Q * SQRT2); // prewarp freqs of w0 and wh w0 = tan(0.5 * w0); @@ -305,8 +305,8 @@ static void BQPeakAbovePi(double coef[5], double w0, double dbgain, double Q) { G = MAX(G, 1.001); // compute the Nyquist gain - wpi = M_PI; - if (w0 < M_PI) { + wpi = PI; + if (w0 < PI) { G1 = G; // use the peak gain } else { G1 = analogPeak(w0, G, Q, wpi); // use Nyquist gain of analog response @@ -317,7 +317,7 @@ static void BQPeakAbovePi(double coef[5], double w0, double dbgain, double Q) { // compute the analog half-gain frequency temp = G + 2.0 * Qsq - sqrt(Gsq + 4.0 * Qsq * G); - wh = sqrt(temp) * w0 / (Q * M_SQRT2); + wh = sqrt(temp) * w0 / (Q * SQRT2); // approximate wn and wd // use half-gain frequency as mapping @@ -425,8 +425,8 @@ static void BQShelf(double coef[5], double w0, double dbgain, double resonance, G = MAX(G, 1.001); // compute the Nyquist gain - wpi = w0 + 2.8 * (1.0 - w0/M_PI); // minimax-like error - wpi = MIN(wpi, M_PI); + wpi = w0 + 2.8 * (1.0 - w0/PI); // minimax-like error + wpi = MIN(wpi, PI); G1 = analogShelf(w0, G, resonance, isHigh, wpi); // approximate wn and wd @@ -513,10 +513,10 @@ static void BQFilter(double coef[5], double w0, int isHigh) { if (isHigh) { - w0 = MIN(w0, M_PI); // disallow w0 > pi for highpass + w0 = MIN(w0, PI); // disallow w0 > pi for highpass // compute the Nyquist gain - wpi = M_PI; + wpi = PI; G1 = analogFilter(w0, isHigh, wpi); // approximate wn and wd @@ -528,13 +528,13 @@ static void BQFilter(double coef[5], double w0, int isHigh) { // compute B and A B = 0.0; - A = M_SQRT2 * Wdsq / atan(wd); // Qd = sqrt(0.5) * atan(wd)/wd; + A = SQRT2 * Wdsq / atan(wd); // Qd = sqrt(0.5) * atan(wd)/wd; } else { // compute the Nyquist gain - wpi = w0 + 2.8 * (1.0 - w0/M_PI); // minimax-like error - wpi = MIN(wpi, M_PI); + wpi = w0 + 2.8 * (1.0 - w0/PI); // minimax-like error + wpi = MIN(wpi, PI); G1 = analogFilter(w0, isHigh, wpi); // approximate wn and wd @@ -653,7 +653,7 @@ public: // lowpass filter, -3dB @ freq double coef[5]; - BQFilter(coef, M_PI * freq / (0.5 * sampleRate), 0); + BQFilter(coef, PI * freq / (0.5 * sampleRate), 0); _b0 = (float)coef[0]; _b1 = (float)coef[1]; _b2 = (float)coef[2]; @@ -661,7 +661,7 @@ public: _a2 = (float)coef[4]; // DC-blocking filter, -3dB @ 10Hz - _alpha = (float)(1.0 - exp(-M_PI * 10.0 / (0.5 * sampleRate))); + _alpha = (float)(1.0 - exp(-PI * 10.0 / (0.5 * sampleRate))); } void process(float input0, float input1, float& output0, float& output1) { @@ -809,9 +809,9 @@ public: // amplitude slightly less than 1.0 _y0 = (int32_t)(0.000 * FIXQ31); - _y1 = (int32_t)(0.999 * cos(M_PI * freq / sampleRate) * FIXQ31); + _y1 = (int32_t)(0.999 * cos(PI * freq / sampleRate) * FIXQ31); - _k = (int32_t)(2.0 * sin(M_PI * freq / sampleRate) * FIXQ32); + _k = (int32_t)(2.0 * sin(PI * freq / sampleRate) * FIXQ32); } void setGain(int32_t gain) { @@ -895,7 +895,7 @@ public: output = _output; // add modulation to delay - uint32_t offset = _delay + (mod >> MOD_FRACBITS); + int32_t offset = _delay + (mod >> MOD_FRACBITS); float frac = (mod & MOD_FRACMASK) * QMOD_TO_FLOAT; // 3rd-order Lagrange interpolation @@ -985,8 +985,8 @@ public: freq1 = MIN(MAX(freq1, 1.0f), 24000.0f); double coefLo[3], coefHi[3]; - PZShelf(coefLo, M_PI * freq0 / (0.5 * sampleRate), dBgain0, 0); // low shelf - PZShelf(coefHi, M_PI * freq1 / (0.5 * sampleRate), dBgain1, 1); // high shelf + PZShelf(coefLo, PI * freq0 / (0.5 * sampleRate), dBgain0, 0); // low shelf + PZShelf(coefHi, PI * freq1 / (0.5 * sampleRate), dBgain1, 1); // high shelf // convolve into a single biquad _b0 = (float)(coefLo[0] * coefHi[0]); @@ -1374,7 +1374,7 @@ static const float diffusionCoefTable[][2] = { 60.0000f, 0.2344f, 73.3333f, 0.3125f, 93.3333f, 0.5000f, - 100.0000f, M_PHI, + 100.0000f, PHI, }; static const float roomSizeTable[][2] = { @@ -1552,13 +1552,13 @@ void ReverbImpl::setParameters(ReverbParameters *p) { _ap8.setCoef(lateDiffusionCoef); _ap9.setCoef(lateDiffusionCoef); - _ap10.setCoef(M_PHI); - _ap11.setCoef(M_PHI); + _ap10.setCoef(PHI); + _ap11.setCoef(PHI); _ap12.setCoef(lateDiffusionCoef); _ap13.setCoef(lateDiffusionCoef); - _ap14.setCoef(M_PHI); - _ap15.setCoef(M_PHI); + _ap14.setCoef(PHI); + _ap15.setCoef(PHI); _ap16.setCoef(lateDiffusionCoef); _ap17.setCoef(lateDiffusionCoef); From 1a7f06dc95a7f72b31bf21dc119ed96ad9c4fec7 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 18 Nov 2015 10:26:06 -0800 Subject: [PATCH 04/12] Removed the commented-out Gverb code --- libraries/audio-client/src/AudioClient.cpp | 112 +-------------------- libraries/audio-client/src/AudioClient.h | 5 - 2 files changed, 2 insertions(+), 115 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index daf89d3473..eb0be9dc17 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -33,29 +33,6 @@ #include #include -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdouble-promotion" -#endif - -#ifdef WIN32 -#pragma warning (push) -#pragma warning (disable: 4273 4305) -#endif - -extern "C" { - #include - #include -} - -#ifdef WIN32 -#pragma warning (pop) -#endif - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif - #include #include #include @@ -119,7 +96,6 @@ AudioClient::AudioClient() : _audioSourceInjectEnabled(false), _reverb(false), _reverbOptions(&_scriptReverbOptions), - //_gverb(NULL), _inputToNetworkResampler(NULL), _networkToOutputResampler(NULL), _loopbackResampler(NULL), @@ -144,8 +120,6 @@ AudioClient::AudioClient() : connect(updateTimer, &QTimer::timeout, this, &AudioClient::checkDevices); updateTimer->start(DEVICE_CHECK_INTERVAL_MSECS); - // create GVerb filter - //_gverb = createGverbFilter(); configureReverb(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); @@ -159,10 +133,6 @@ AudioClient::AudioClient() : AudioClient::~AudioClient() { stop(); - - //if (_gverb) { - // gverb_free(_gverb); - //} } void AudioClient::reset() { @@ -172,8 +142,6 @@ void AudioClient::reset() { _toneSource.reset(); _sourceGain.reset(); _inputGain.reset(); - - //gverb_flush(_gverb); _stereoReverb.reset(); } @@ -569,29 +537,11 @@ bool AudioClient::switchOutputToAudioDevice(const QString& outputDeviceName) { return switchOutputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName)); } -//ty_gverb* AudioClient::createGverbFilter() { -// // Initialize a new gverb instance -// ty_gverb* filter = gverb_new(_outputFormat.sampleRate(), _reverbOptions->getMaxRoomSize(), _reverbOptions->getRoomSize(), -// _reverbOptions->getReverbTime(), _reverbOptions->getDamping(), _reverbOptions->getSpread(), -// _reverbOptions->getInputBandwidth(), _reverbOptions->getEarlyLevel(), -// _reverbOptions->getTailLevel()); -// -// return filter; -//} - void AudioClient::configureReverb() { - // Configure the instance (these functions are not super well named - they actually set several internal variables) - //gverb_set_roomsize(filter, _reverbOptions->getRoomSize()); - //gverb_set_revtime(filter, _reverbOptions->getReverbTime()); - //gverb_set_damping(filter, _reverbOptions->getDamping()); - //gverb_set_inputbandwidth(filter, _reverbOptions->getInputBandwidth()); - //gverb_set_earlylevel(filter, DB_CO(_reverbOptions->getEarlyLevel())); - //gverb_set_taillevel(filter, DB_CO(_reverbOptions->getTailLevel())); - ReverbParameters p; _stereoReverb.getParameters(&p); - // for now, use the gverb settings + // for now, reuse the gverb parameters p.sampleRate = _outputFormat.sampleRate(); p.roomSize = _reverbOptions->getRoomSize(); p.reverbTime = _reverbOptions->getReverbTime(); @@ -599,7 +549,7 @@ void AudioClient::configureReverb() { p.bandwidth = 12000.0f * _reverbOptions->getInputBandwidth(); //p.earlyGain = _reverbOptions->getEarlyLevel(); //p.lateGain = _reverbOptions->getTailLevel(); - p.wetDryMix = _shouldEchoLocally ? DB_CO(_reverbOptions->getWetLevel()) : 100.0f; // !_shouldEchoLocally apparently means 100% wet? + p.wetDryMix = _shouldEchoLocally ? powf(10.0f, _reverbOptions->getWetLevel() * (1/20.0f)) : 100.0f; _stereoReverb.setParameters(&p); } @@ -614,7 +564,6 @@ void AudioClient::updateReverbOptions() { } if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); - // Not part of actual filter config, no need to set reverbChanged to true reverbChanged = true; } @@ -628,8 +577,6 @@ void AudioClient::updateReverbOptions() { } if (reverbChanged) { - //gverb_free(_gverb); - //_gverb = createGverbFilter(); configureReverb(); } } @@ -638,7 +585,6 @@ void AudioClient::setReverb(bool reverb) { _reverb = reverb; if (!_reverb) { - //gverb_flush(_gverb); _stereoReverb.reset(); } } @@ -659,50 +605,10 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) { if (_reverbOptions == &_scriptReverbOptions) { // Apply them to the reverb instances - //gverb_free(_gverb); - //_gverb = createGverbFilter(); configureReverb(); } } -//void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reverbAlone, int numSamples, -// QAudioFormat& audioFormat, bool noEcho) { -// float wetFraction = DB_CO(_reverbOptions->getWetLevel()); -// float dryFraction = 1.0f - wetFraction; -// -// float lValue,rValue; -// for (int sample = 0; sample < numSamples; sample += audioFormat.channelCount()) { -// // Run GVerb -// float value = (float)samplesData[sample]; -// gverb_do(gverb, value, &lValue, &rValue); -// -// // Mix, accounting for clipping, the left and right channels. Ignore the rest. -// for (int j = sample; j < sample + audioFormat.channelCount(); j++) { -// if (j == sample) { -// // left channel -// int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction), -// AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); -// samplesData[j] = (int16_t)lResult; -// -// if (noEcho) { -// reverbAlone[j] = (int16_t)lValue * wetFraction; -// } -// } else if (j == (sample + 1)) { -// // right channel -// int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction), -// AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); -// samplesData[j] = (int16_t)rResult; -// -// if (noEcho) { -// reverbAlone[j] = (int16_t)rValue * wetFraction; -// } -// } else { -// // ignore channels above 2 -// } -// } -// } -//} - void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { // If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here. bool hasReverb = _reverb || _receivedAudioStream.hasReverb(); @@ -733,30 +639,16 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { _loopbackResampler = new AudioSRC(_inputFormat.sampleRate(), _outputFormat.sampleRate(), channelCount); } - //static QByteArray reverbAlone; // Intermediary for local reverb with no echo static QByteArray loopBackByteArray; int numInputSamples = inputByteArray.size() / sizeof(int16_t); int numLoopbackSamples = numDestinationSamplesRequired(_inputFormat, _outputFormat, numInputSamples); - //reverbAlone.resize(numInputSamples * sizeof(int16_t)); loopBackByteArray.resize(numLoopbackSamples * sizeof(int16_t)); int16_t* inputSamples = reinterpret_cast(inputByteArray.data()); - //int16_t* reverbAloneSamples = reinterpret_cast(reverbAlone.data()); int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); - //if (hasReverb) { - // updateGverbOptions(); - // addReverb(_gverb, inputSamples, reverbAloneSamples, numInputSamples, - // _inputFormat, !_shouldEchoLocally); - //} - - //possibleResampling(_loopbackResampler, - // (_shouldEchoLocally) ? inputSamples : reverbAloneSamples, loopbackSamples, - // numInputSamples, numLoopbackSamples, - // _inputFormat, _outputFormat); - possibleResampling(_loopbackResampler, inputSamples, loopbackSamples, numInputSamples, numLoopbackSamples, diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index c2a235938b..30e60d1775 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -75,8 +75,6 @@ class QAudioInput; class QAudioOutput; class QIODevice; -typedef struct ty_gverb ty_gverb; - class NLPacket; @@ -263,7 +261,6 @@ private: AudioEffectOptions _scriptReverbOptions; AudioEffectOptions _zoneReverbOptions; AudioEffectOptions* _reverbOptions; - //ty_gverb* _gverb; AudioReverb _stereoReverb { AudioConstants::SAMPLE_RATE }; // possible streams needed for resample @@ -272,10 +269,8 @@ private: AudioSRC* _loopbackResampler; // Adds Reverb - //ty_gverb* createGverbFilter(); void configureReverb(); void updateReverbOptions(); - //void addReverb(ty_gverb* gverb, int16_t* samples, int16_t* reverbAlone, int numSamples, QAudioFormat& format, bool noEcho = false); void handleLocalEchoAndReverb(QByteArray& inputByteArray); From cc90662bf050b7cf3cf3ec51313e284ed00a8351 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 18 Nov 2015 12:17:27 -0800 Subject: [PATCH 05/12] PR feedback --- libraries/audio/src/AudioReverb.cpp | 8 ++++---- libraries/audio/src/AudioReverb.h | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libraries/audio/src/AudioReverb.cpp b/libraries/audio/src/AudioReverb.cpp index a822da3377..f06bbe76ed 100644 --- a/libraries/audio/src/AudioReverb.cpp +++ b/libraries/audio/src/AudioReverb.cpp @@ -244,7 +244,7 @@ static void BQPeakBelowPi(double coef[5], double w0, double dbgain, double Q) { temp += Gratio * w0sq * w0sq / (G * whsq); A = sqrt(temp); - // design digital filter via bilinear tranform + // design digital filter via bilinear transform b0 = G1 + B + Wsq; b1 = 2.0 * (Wsq - G1); b2 = G1 - B + Wsq; @@ -347,7 +347,7 @@ static void BQPeakAbovePi(double coef[5], double w0, double dbgain, double Q) { num = (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gnsq * Wdsq); A = temp * sqrt(num / den); - // design digital filter via bilinear tranform + // design digital filter via bilinear transform b0 = G1 * (1.0 + B + Wnsq); b1 = G1 * 2.0 * (Wnsq - 1.0); b2 = G1 * (1.0 - B + Wnsq); @@ -463,7 +463,7 @@ static void BQShelf(double coef[5], double w0, double dbgain, double resonance, num = (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gnsq * Wdsq); A = temp * sqrt(num / den); - // design digital filter via bilinear tranform + // design digital filter via bilinear transform b0 = G1 * (1.0 + B + Wnsq); b1 = G1 * 2.0 * (Wnsq - 1.0); b2 = G1 * (1.0 - B + Wnsq); @@ -566,7 +566,7 @@ static void BQFilter(double coef[5], double w0, int isHigh) { A = temp * sqrt(num / den); } - // design digital filter via bilinear tranform + // design digital filter via bilinear transform b0 = G1 * (1.0 + B + Wnsq); b1 = G1 * 2.0 * (Wnsq - 1.0); b2 = G1 * (1.0 - B + Wnsq); diff --git a/libraries/audio/src/AudioReverb.h b/libraries/audio/src/AudioReverb.h index f135a46127..92e9c0009b 100644 --- a/libraries/audio/src/AudioReverb.h +++ b/libraries/audio/src/AudioReverb.h @@ -50,14 +50,6 @@ typedef struct ReverbParameters { class ReverbImpl; class AudioReverb { - - ReverbImpl *_impl; - ReverbParameters _params; - - float* _inout[2]; - void convertInputFromInt16(const int16_t* input, float** outputs, int numFrames); - void convertOutputToInt16(float** inputs, int16_t* output, int numFrames); - public: AudioReverb(float sampleRate); ~AudioReverb(); @@ -71,6 +63,14 @@ public: // interleaved int16_t input/output void render(const int16_t* input, int16_t* output, int numFrames); + +private: + ReverbImpl *_impl; + ReverbParameters _params; + + float* _inout[2]; + void convertInputFromInt16(const int16_t* input, float** outputs, int numFrames); + void convertOutputToInt16(float** inputs, int16_t* output, int numFrames); }; #endif // hifi_AudioReverb_h From 2489eaa30e3c0384baf90f9ae8f14397d54a022a Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 19 Nov 2015 08:32:13 -0800 Subject: [PATCH 06/12] Changed the audio pipeline to allow stereo reverberation, using separate source (loopback audio) and listener (received audio) reverbs. --- libraries/audio-client/src/AudioClient.cpp | 40 ++++++++++++++++------ libraries/audio-client/src/AudioClient.h | 3 +- libraries/audio/src/AudioEffectOptions.cpp | 6 ++-- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index eb0be9dc17..81af183c0f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -142,7 +142,8 @@ void AudioClient::reset() { _toneSource.reset(); _sourceGain.reset(); _inputGain.reset(); - _stereoReverb.reset(); + _sourceReverb.reset(); + _listenerReverb.reset(); } void AudioClient::audioMixerKilled() { @@ -539,7 +540,7 @@ bool AudioClient::switchOutputToAudioDevice(const QString& outputDeviceName) { void AudioClient::configureReverb() { ReverbParameters p; - _stereoReverb.getParameters(&p); + _listenerReverb.getParameters(&p); // for now, reuse the gverb parameters p.sampleRate = _outputFormat.sampleRate(); @@ -547,11 +548,19 @@ void AudioClient::configureReverb() { p.reverbTime = _reverbOptions->getReverbTime(); p.highGain = -24.0f * _reverbOptions->getDamping(); p.bandwidth = 12000.0f * _reverbOptions->getInputBandwidth(); - //p.earlyGain = _reverbOptions->getEarlyLevel(); - //p.lateGain = _reverbOptions->getTailLevel(); - p.wetDryMix = _shouldEchoLocally ? powf(10.0f, _reverbOptions->getWetLevel() * (1/20.0f)) : 100.0f; + p.earlyGain = _reverbOptions->getEarlyLevel(); + p.lateGain = _reverbOptions->getTailLevel(); + p.wetDryMix = 100.0f * powf(10.0f, _reverbOptions->getWetLevel() * (1/20.0f)); + _listenerReverb.setParameters(&p); - _stereoReverb.setParameters(&p); + // used for adding self-reverb to loopback audio + p.wetDryMix = _shouldEchoLocally ? 0.0f : 100.0f; // local echo is 100% dry + p.preDelay = 0.0f; + p.earlyGain = -96.0f; // disable ER + p.lateGain -= 6.0f; // quieter than listener reverb + p.lateMixLeft = 0.0f; + p.lateMixRight = 0.0f; + _sourceReverb.setParameters(&p); } void AudioClient::updateReverbOptions() { @@ -585,7 +594,8 @@ void AudioClient::setReverb(bool reverb) { _reverb = reverb; if (!_reverb) { - _stereoReverb.reset(); + _sourceReverb.reset(); + _listenerReverb.reset(); } } @@ -654,11 +664,11 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { numInputSamples, numLoopbackSamples, _inputFormat, _outputFormat); + // apply stereo reverb at the source, to the loopback audio if (hasReverb) { - // always use the stereo reverb output assert(_outputFormat.channelCount() == 2); updateReverbOptions(); - _stereoReverb.render(loopbackSamples, loopbackSamples, numLoopbackSamples/2); + _sourceReverb.render(loopbackSamples, loopbackSamples, numLoopbackSamples/2); } _loopbackOutputDevice->write(loopBackByteArray); @@ -867,12 +877,20 @@ void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArr outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t)); const int16_t* receivedSamples = reinterpret_cast(inputBuffer.data()); + int16_t* outputSamples = reinterpret_cast(outputBuffer.data()); // copy the packet from the RB to the output - possibleResampling(_networkToOutputResampler, receivedSamples, - reinterpret_cast(outputBuffer.data()), + possibleResampling(_networkToOutputResampler, receivedSamples, outputSamples, numNetworkOutputSamples, numDeviceOutputSamples, _desiredOutputFormat, _outputFormat); + + // apply stereo reverb at the listener, to the received audio + bool hasReverb = _receivedAudioStream.hasReverb(); + if (hasReverb) { + assert(_outputFormat.channelCount() == 2); + updateReverbOptions(); + _listenerReverb.render(outputSamples, outputSamples, numDeviceOutputSamples/2); + } } void AudioClient::sendMuteEnvironmentPacket() { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 30e60d1775..2c2a9f36ff 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -261,7 +261,8 @@ private: AudioEffectOptions _scriptReverbOptions; AudioEffectOptions _zoneReverbOptions; AudioEffectOptions* _reverbOptions; - AudioReverb _stereoReverb { AudioConstants::SAMPLE_RATE }; + AudioReverb _sourceReverb { AudioConstants::SAMPLE_RATE }; + AudioReverb _listenerReverb { AudioConstants::SAMPLE_RATE }; // possible streams needed for resample AudioSRC* _inputToNetworkResampler; diff --git a/libraries/audio/src/AudioEffectOptions.cpp b/libraries/audio/src/AudioEffectOptions.cpp index 221d70aa75..a61213d9c4 100644 --- a/libraries/audio/src/AudioEffectOptions.cpp +++ b/libraries/audio/src/AudioEffectOptions.cpp @@ -28,10 +28,10 @@ AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) : _damping(0.5f), _spread(15.0f), _inputBandwidth(0.75f), - _earlyLevel(-22.0f), - _tailLevel(-28.0f), + _earlyLevel(-12.0f), + _tailLevel(-18.0f), _dryLevel(0.0f), - _wetLevel(6.0f) { + _wetLevel(0.0f) { if (arguments.property(MAX_ROOM_SIZE_HANDLE).isNumber()) { _maxRoomSize = arguments.property(MAX_ROOM_SIZE_HANDLE).toNumber(); } From 3060aa960d9b1467cd2394ddda11fc9af90eb4e9 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 19 Nov 2015 09:23:58 -0800 Subject: [PATCH 07/12] Fix the case when local-echo and self-reverb both active --- libraries/audio-client/src/AudioClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 9c4349013f..d0f2af37af 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -555,7 +555,7 @@ void AudioClient::configureReverb() { _listenerReverb.setParameters(&p); // used for adding self-reverb to loopback audio - p.wetDryMix = _shouldEchoLocally ? 0.0f : 100.0f; // local echo is 100% dry + p.wetDryMix = 100.0f; p.preDelay = 0.0f; p.earlyGain = -96.0f; // disable ER p.lateGain -= 6.0f; // quieter than listener reverb @@ -666,7 +666,7 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { _inputFormat, _outputFormat); // apply stereo reverb at the source, to the loopback audio - if (hasReverb) { + if (!_shouldEchoLocally && hasReverb) { assert(_outputFormat.channelCount() == 2); updateReverbOptions(); _sourceReverb.render(loopbackSamples, loopbackSamples, numLoopbackSamples/2); From 2c98976ef258d5294e51b776cbff136ad3b6cdee Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 19 Nov 2015 10:05:10 -0800 Subject: [PATCH 08/12] Fix bug from implicit cast --- libraries/audio/src/AudioSRC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio/src/AudioSRC.cpp b/libraries/audio/src/AudioSRC.cpp index 59fe29df36..c187d381a4 100644 --- a/libraries/audio/src/AudioSRC.cpp +++ b/libraries/audio/src/AudioSRC.cpp @@ -1218,7 +1218,7 @@ static inline float dither() { rz = rz * 69069 + 1; int32_t r0 = rz & 0xffff; int32_t r1 = rz >> 16; - return (r0 - r1) * (1/65536.0f); + return (int32_t)(r0 - r1) * (1/65536.0f); } // convert float to int16_t, interleave stereo From a5b64473af481adc62d085f1417ae7e56ec1277b Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 25 Nov 2015 16:15:04 -0800 Subject: [PATCH 09/12] Fix bugs --- libraries/audio-client/src/AudioClient.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index d0f2af37af..b141c70272 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -547,8 +547,8 @@ void AudioClient::configureReverb() { p.sampleRate = _outputFormat.sampleRate(); p.roomSize = _reverbOptions->getRoomSize(); p.reverbTime = _reverbOptions->getReverbTime(); - p.highGain = -24.0f * _reverbOptions->getDamping(); - p.bandwidth = 12000.0f * _reverbOptions->getInputBandwidth(); + p.highGain = -24.0f * (1.0f - _reverbOptions->getDamping()); + p.bandwidth = 10000.0f * _reverbOptions->getInputBandwidth(); p.earlyGain = _reverbOptions->getEarlyLevel(); p.lateGain = _reverbOptions->getTailLevel(); p.wetDryMix = 100.0f * powf(10.0f, _reverbOptions->getWetLevel() * (1/20.0f)); @@ -558,7 +558,7 @@ void AudioClient::configureReverb() { p.wetDryMix = 100.0f; p.preDelay = 0.0f; p.earlyGain = -96.0f; // disable ER - p.lateGain -= 6.0f; // quieter than listener reverb + p.lateGain -= 12.0f; // quieter than listener reverb p.lateMixLeft = 0.0f; p.lateMixRight = 0.0f; _sourceReverb.setParameters(&p); @@ -811,7 +811,7 @@ void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArr _desiredOutputFormat, _outputFormat); // apply stereo reverb at the listener, to the received audio - bool hasReverb = _receivedAudioStream.hasReverb(); + bool hasReverb = _reverb || _receivedAudioStream.hasReverb(); if (hasReverb) { assert(_outputFormat.channelCount() == 2); updateReverbOptions(); From e25e6b5003a38ffc1a56035ec9a1b0e5ead2521c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 1 Dec 2015 06:44:56 -0800 Subject: [PATCH 10/12] Test script with sliders for all reverb parameters --- examples/utilities/tools/reverbTest.js | 69 ++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 examples/utilities/tools/reverbTest.js diff --git a/examples/utilities/tools/reverbTest.js b/examples/utilities/tools/reverbTest.js new file mode 100644 index 0000000000..f399a40cc0 --- /dev/null +++ b/examples/utilities/tools/reverbTest.js @@ -0,0 +1,69 @@ +// +// reverbTest.js +// examples +// +// Created by Ken Cooke on 11/23/2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("cookies.js"); + +var audioOptions = new AudioEffectOptions({ + maxRoomSize: 50, + roomSize: 50, + reverbTime: 4, + damping: 0.50, + inputBandwidth: 0.8, + earlyLevel: 0, + tailLevel: 0, + dryLevel: 0, + wetLevel: 0 +}); + +AudioDevice.setReverbOptions(audioOptions); +AudioDevice.setReverb(true); +print("Reverb is ON."); + +var panel = new Panel(10, 200); + +var parameters = [ + { name: "roomSize", min: 0, max: 100, units: " feet" }, + { name: "reverbTime", min: 0, max: 10, units: " sec" }, + { name: "damping", min: 0, max: 1, units: " " }, + { name: "inputBandwidth", min: 0, max: 1, units: " " }, + { name: "earlyLevel", min: -48, max: 0, units: " dB" }, + { name: "tailLevel", min: -48, max: 0, units: " dB" }, + { name: "wetLevel", min: -48, max: 0, units: " dB" }, +] + +function setter(name) { + return function(value) { audioOptions[name] = value; AudioDevice.setReverbOptions(audioOptions); } +} + +function getter(name) { + return function() { return audioOptions[name]; } +} + +function displayer(units) { + return function(value) { return (value).toFixed(1) + units; }; +} + +// create a slider for each parameter +for (var i = 0; i < parameters.length; i++) { + var p = parameters[i]; + panel.newSlider(p.name, p.min, p.max, setter(p.name), getter(p.name), displayer(p.units)); +} + +Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); }); +Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); }); +Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); }); + +function scriptEnding() { + panel.destroy(); + AudioDevice.setReverb(false); + print("Reverb is OFF."); +} +Script.scriptEnding.connect(scriptEnding); From f005afd7c021fe30570d9aa7394162091d597d9f Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 2 Dec 2015 09:33:47 -0800 Subject: [PATCH 11/12] Fix stdint.h includes --- libraries/audio/src/AudioReverb.h | 2 +- libraries/audio/src/AudioSRC.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/audio/src/AudioReverb.h b/libraries/audio/src/AudioReverb.h index 92e9c0009b..639d62d8ec 100644 --- a/libraries/audio/src/AudioReverb.h +++ b/libraries/audio/src/AudioReverb.h @@ -9,7 +9,7 @@ #ifndef hifi_AudioReverb_h #define hifi_AudioReverb_h -#include "stdint.h" +#include typedef struct ReverbParameters { diff --git a/libraries/audio/src/AudioSRC.h b/libraries/audio/src/AudioSRC.h index 5b00ca9e77..920ea8aef0 100644 --- a/libraries/audio/src/AudioSRC.h +++ b/libraries/audio/src/AudioSRC.h @@ -12,7 +12,7 @@ #ifndef hifi_AudioSRC_h #define hifi_AudioSRC_h -#include "stdint.h" +#include class AudioSRC { From fbd754943010c6746fd6a5f34b25dd1773f81f41 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 2 Dec 2015 11:05:06 -0800 Subject: [PATCH 12/12] Better reverb script defaults --- examples/utilities/tools/reverbTest.js | 4 ++-- libraries/audio/src/AudioEffectOptions.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/utilities/tools/reverbTest.js b/examples/utilities/tools/reverbTest.js index f399a40cc0..32c28a993f 100644 --- a/examples/utilities/tools/reverbTest.js +++ b/examples/utilities/tools/reverbTest.js @@ -19,8 +19,8 @@ var audioOptions = new AudioEffectOptions({ inputBandwidth: 0.8, earlyLevel: 0, tailLevel: 0, - dryLevel: 0, - wetLevel: 0 + dryLevel: -6, + wetLevel: -6 }); AudioDevice.setReverbOptions(audioOptions); diff --git a/libraries/audio/src/AudioEffectOptions.h b/libraries/audio/src/AudioEffectOptions.h index 97aac7c82c..be5e1cca5e 100644 --- a/libraries/audio/src/AudioEffectOptions.h +++ b/libraries/audio/src/AudioEffectOptions.h @@ -15,6 +15,8 @@ #include #include +#include "AudioReverb.h" + class AudioEffectOptions : public QObject { Q_OBJECT