mirror of https://github.com/dcoredump/dexed.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
11 KiB
323 lines
11 KiB
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2013 - Raw Material Software Ltd.
|
|
|
|
Permission is granted to use this software under the terms of either:
|
|
a) the GPL v2 (or any later version)
|
|
b) the Affero GPL v3
|
|
|
|
Details of these licenses can be found at: www.gnu.org/licenses
|
|
|
|
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
To release a closed-source product which uses JUCE, commercial licenses are
|
|
available: visit www.juce.com for more information.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#ifndef JUCE_REVERB_H_INCLUDED
|
|
#define JUCE_REVERB_H_INCLUDED
|
|
|
|
|
|
//==============================================================================
|
|
/**
|
|
Performs a simple reverb effect on a stream of audio data.
|
|
|
|
This is a simple stereo reverb, based on the technique and tunings used in FreeVerb.
|
|
Use setSampleRate() to prepare it, and then call processStereo() or processMono() to
|
|
apply the reverb to your audio data.
|
|
|
|
@see ReverbAudioSource
|
|
*/
|
|
class Reverb
|
|
{
|
|
public:
|
|
//==============================================================================
|
|
Reverb()
|
|
{
|
|
setParameters (Parameters());
|
|
setSampleRate (44100.0);
|
|
}
|
|
|
|
//==============================================================================
|
|
/** Holds the parameters being used by a Reverb object. */
|
|
struct Parameters
|
|
{
|
|
Parameters() noexcept
|
|
: roomSize (0.5f),
|
|
damping (0.5f),
|
|
wetLevel (0.33f),
|
|
dryLevel (0.4f),
|
|
width (1.0f),
|
|
freezeMode (0)
|
|
{}
|
|
|
|
float roomSize; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */
|
|
float damping; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */
|
|
float wetLevel; /**< Wet level, 0 to 1.0 */
|
|
float dryLevel; /**< Dry level, 0 to 1.0 */
|
|
float width; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */
|
|
float freezeMode; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5
|
|
put the reverb into a continuous feedback loop. */
|
|
};
|
|
|
|
//==============================================================================
|
|
/** Returns the reverb's current parameters. */
|
|
const Parameters& getParameters() const noexcept { return parameters; }
|
|
|
|
/** Applies a new set of parameters to the reverb.
|
|
Note that this doesn't attempt to lock the reverb, so if you call this in parallel with
|
|
the process method, you may get artifacts.
|
|
*/
|
|
void setParameters (const Parameters& newParams)
|
|
{
|
|
const float wetScaleFactor = 3.0f;
|
|
const float dryScaleFactor = 2.0f;
|
|
|
|
const float wet = newParams.wetLevel * wetScaleFactor;
|
|
wet1 = wet * (newParams.width * 0.5f + 0.5f);
|
|
wet2 = wet * (1.0f - newParams.width) * 0.5f;
|
|
dry = newParams.dryLevel * dryScaleFactor;
|
|
gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f;
|
|
parameters = newParams;
|
|
shouldUpdateDamping = true;
|
|
}
|
|
|
|
//==============================================================================
|
|
/** Sets the sample rate that will be used for the reverb.
|
|
You must call this before the process methods, in order to tell it the correct sample rate.
|
|
*/
|
|
void setSampleRate (const double sampleRate)
|
|
{
|
|
jassert (sampleRate > 0);
|
|
|
|
static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz)
|
|
static const short allPassTunings[] = { 556, 441, 341, 225 };
|
|
const int stereoSpread = 23;
|
|
const int intSampleRate = (int) sampleRate;
|
|
|
|
for (int i = 0; i < numCombs; ++i)
|
|
{
|
|
comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100);
|
|
comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100);
|
|
}
|
|
|
|
for (int i = 0; i < numAllPasses; ++i)
|
|
{
|
|
allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100);
|
|
allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100);
|
|
}
|
|
|
|
shouldUpdateDamping = true;
|
|
}
|
|
|
|
/** Clears the reverb's buffers. */
|
|
void reset()
|
|
{
|
|
for (int j = 0; j < numChannels; ++j)
|
|
{
|
|
for (int i = 0; i < numCombs; ++i)
|
|
comb[j][i].clear();
|
|
|
|
for (int i = 0; i < numAllPasses; ++i)
|
|
allPass[j][i].clear();
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
/** Applies the reverb to two stereo channels of audio data. */
|
|
void processStereo (float* const left, float* const right, const int numSamples) noexcept
|
|
{
|
|
jassert (left != nullptr && right != nullptr);
|
|
|
|
if (shouldUpdateDamping)
|
|
updateDamping();
|
|
|
|
for (int i = 0; i < numSamples; ++i)
|
|
{
|
|
const float input = (left[i] + right[i]) * gain;
|
|
float outL = 0, outR = 0;
|
|
|
|
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
|
|
{
|
|
outL += comb[0][j].process (input);
|
|
outR += comb[1][j].process (input);
|
|
}
|
|
|
|
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
|
|
{
|
|
outL = allPass[0][j].process (outL);
|
|
outR = allPass[1][j].process (outR);
|
|
}
|
|
|
|
left[i] = outL * wet1 + outR * wet2 + left[i] * dry;
|
|
right[i] = outR * wet1 + outL * wet2 + right[i] * dry;
|
|
}
|
|
}
|
|
|
|
/** Applies the reverb to a single mono channel of audio data. */
|
|
void processMono (float* const samples, const int numSamples) noexcept
|
|
{
|
|
jassert (samples != nullptr);
|
|
|
|
if (shouldUpdateDamping)
|
|
updateDamping();
|
|
|
|
for (int i = 0; i < numSamples; ++i)
|
|
{
|
|
const float input = samples[i] * gain;
|
|
float output = 0;
|
|
|
|
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
|
|
output += comb[0][j].process (input);
|
|
|
|
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
|
|
output = allPass[0][j].process (output);
|
|
|
|
samples[i] = output * wet1 + samples[i] * dry;
|
|
}
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
Parameters parameters;
|
|
|
|
volatile bool shouldUpdateDamping;
|
|
float gain, wet1, wet2, dry;
|
|
|
|
inline static bool isFrozen (const float freezeMode) noexcept { return freezeMode >= 0.5f; }
|
|
|
|
void updateDamping() noexcept
|
|
{
|
|
const float roomScaleFactor = 0.28f;
|
|
const float roomOffset = 0.7f;
|
|
const float dampScaleFactor = 0.4f;
|
|
|
|
shouldUpdateDamping = false;
|
|
|
|
if (isFrozen (parameters.freezeMode))
|
|
setDamping (0.0f, 1.0f);
|
|
else
|
|
setDamping (parameters.damping * dampScaleFactor,
|
|
parameters.roomSize * roomScaleFactor + roomOffset);
|
|
}
|
|
|
|
void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept
|
|
{
|
|
for (int j = 0; j < numChannels; ++j)
|
|
for (int i = numCombs; --i >= 0;)
|
|
comb[j][i].setFeedbackAndDamp (roomSizeToUse, dampingToUse);
|
|
}
|
|
|
|
//==============================================================================
|
|
class CombFilter
|
|
{
|
|
public:
|
|
CombFilter() noexcept
|
|
: bufferSize (0), bufferIndex (0),
|
|
feedback (0), last (0), damp1 (0), damp2 (0)
|
|
{}
|
|
|
|
void setSize (const int size)
|
|
{
|
|
if (size != bufferSize)
|
|
{
|
|
bufferIndex = 0;
|
|
buffer.malloc ((size_t) size);
|
|
bufferSize = size;
|
|
}
|
|
|
|
clear();
|
|
}
|
|
|
|
void clear() noexcept
|
|
{
|
|
last = 0;
|
|
buffer.clear ((size_t) bufferSize);
|
|
}
|
|
|
|
void setFeedbackAndDamp (const float f, const float d) noexcept
|
|
{
|
|
damp1 = d;
|
|
damp2 = 1.0f - d;
|
|
feedback = f;
|
|
}
|
|
|
|
inline float process (const float input) noexcept
|
|
{
|
|
const float output = buffer [bufferIndex];
|
|
last = (output * damp2) + (last * damp1);
|
|
JUCE_UNDENORMALISE (last);
|
|
|
|
float temp = input + (last * feedback);
|
|
JUCE_UNDENORMALISE (temp);
|
|
buffer [bufferIndex] = temp;
|
|
bufferIndex = (bufferIndex + 1) % bufferSize;
|
|
return output;
|
|
}
|
|
|
|
private:
|
|
HeapBlock<float> buffer;
|
|
int bufferSize, bufferIndex;
|
|
float feedback, last, damp1, damp2;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (CombFilter)
|
|
};
|
|
|
|
//==============================================================================
|
|
class AllPassFilter
|
|
{
|
|
public:
|
|
AllPassFilter() noexcept : bufferSize (0), bufferIndex (0) {}
|
|
|
|
void setSize (const int size)
|
|
{
|
|
if (size != bufferSize)
|
|
{
|
|
bufferIndex = 0;
|
|
buffer.malloc ((size_t) size);
|
|
bufferSize = size;
|
|
}
|
|
|
|
clear();
|
|
}
|
|
|
|
void clear() noexcept
|
|
{
|
|
buffer.clear ((size_t) bufferSize);
|
|
}
|
|
|
|
inline float process (const float input) noexcept
|
|
{
|
|
const float bufferedValue = buffer [bufferIndex];
|
|
float temp = input + (bufferedValue * 0.5f);
|
|
JUCE_UNDENORMALISE (temp);
|
|
buffer [bufferIndex] = temp;
|
|
bufferIndex = (bufferIndex + 1) % bufferSize;
|
|
return bufferedValue - input;
|
|
}
|
|
|
|
private:
|
|
HeapBlock<float> buffer;
|
|
int bufferSize, bufferIndex;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (AllPassFilter)
|
|
};
|
|
|
|
enum { numCombs = 8, numAllPasses = 4, numChannels = 2 };
|
|
|
|
CombFilter comb [numChannels][numCombs];
|
|
AllPassFilter allPass [numChannels][numAllPasses];
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb)
|
|
};
|
|
|
|
|
|
#endif // JUCE_REVERB_H_INCLUDED
|
|
|