|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2020 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- //==============================================================================
- /**
- 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
-
- @tags{Audio}
- */
- class Reverb
- {
- public:
- //==============================================================================
- Reverb()
- {
- setParameters (Parameters());
- setSampleRate (44100.0);
- }
-
- //==============================================================================
- /** Holds the parameters being used by a Reverb object. */
- struct Parameters
- {
- float roomSize = 0.5f; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */
- float damping = 0.5f; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */
- float wetLevel = 0.33f; /**< Wet level, 0 to 1.0 */
- float dryLevel = 0.4f; /**< Dry level, 0 to 1.0 */
- float width = 1.0f; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */
- float freezeMode = 0.0f; /**< 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;
- dryGain.setTargetValue (newParams.dryLevel * dryScaleFactor);
- wetGain1.setTargetValue (0.5f * wet * (1.0f + newParams.width));
- wetGain2.setTargetValue (0.5f * wet * (1.0f - newParams.width));
-
- gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f;
- parameters = newParams;
- updateDamping();
- }
-
- //==============================================================================
- /** 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);
- }
-
- const double smoothTime = 0.01;
- damping .reset (sampleRate, smoothTime);
- feedback.reset (sampleRate, smoothTime);
- dryGain .reset (sampleRate, smoothTime);
- wetGain1.reset (sampleRate, smoothTime);
- wetGain2.reset (sampleRate, smoothTime);
- }
-
- /** 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);
-
- for (int i = 0; i < numSamples; ++i)
- {
- const float input = (left[i] + right[i]) * gain;
- float outL = 0, outR = 0;
-
- const float damp = damping.getNextValue();
- const float feedbck = feedback.getNextValue();
-
- for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
- {
- outL += comb[0][j].process (input, damp, feedbck);
- outR += comb[1][j].process (input, damp, feedbck);
- }
-
- 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);
- }
-
- const float dry = dryGain.getNextValue();
- const float wet1 = wetGain1.getNextValue();
- const float wet2 = wetGain2.getNextValue();
-
- 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);
-
- for (int i = 0; i < numSamples; ++i)
- {
- const float input = samples[i] * gain;
- float output = 0;
-
- const float damp = damping.getNextValue();
- const float feedbck = feedback.getNextValue();
-
- for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
- output += comb[0][j].process (input, damp, feedbck);
-
- for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
- output = allPass[0][j].process (output);
-
- const float dry = dryGain.getNextValue();
- const float wet1 = wetGain1.getNextValue();
-
- samples[i] = output * wet1 + samples[i] * dry;
- }
- }
-
- private:
- //==============================================================================
- 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;
-
- 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
- {
- damping.setTargetValue (dampingToUse);
- feedback.setTargetValue (roomSizeToUse);
- }
-
- //==============================================================================
- class CombFilter
- {
- public:
- CombFilter() noexcept {}
-
- void setSize (const int size)
- {
- if (size != bufferSize)
- {
- bufferIndex = 0;
- buffer.malloc (size);
- bufferSize = size;
- }
-
- clear();
- }
-
- void clear() noexcept
- {
- last = 0;
- buffer.clear ((size_t) bufferSize);
- }
-
- float process (const float input, const float damp, const float feedbackLevel) noexcept
- {
- const float output = buffer[bufferIndex];
- last = (output * (1.0f - damp)) + (last * damp);
- JUCE_UNDENORMALISE (last);
-
- float temp = input + (last * feedbackLevel);
- JUCE_UNDENORMALISE (temp);
- buffer[bufferIndex] = temp;
- bufferIndex = (bufferIndex + 1) % bufferSize;
- return output;
- }
-
- private:
- HeapBlock<float> buffer;
- int bufferSize = 0, bufferIndex = 0;
- float last = 0.0f;
-
- JUCE_DECLARE_NON_COPYABLE (CombFilter)
- };
-
- //==============================================================================
- class AllPassFilter
- {
- public:
- AllPassFilter() noexcept {}
-
- void setSize (const int size)
- {
- if (size != bufferSize)
- {
- bufferIndex = 0;
- buffer.malloc (size);
- bufferSize = size;
- }
-
- clear();
- }
-
- void clear() noexcept
- {
- buffer.clear ((size_t) bufferSize);
- }
-
- 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 = 0, bufferIndex = 0;
-
- JUCE_DECLARE_NON_COPYABLE (AllPassFilter)
- };
-
- //==============================================================================
- enum { numCombs = 8, numAllPasses = 4, numChannels = 2 };
-
- Parameters parameters;
- float gain;
-
- CombFilter comb [numChannels][numCombs];
- AllPassFilter allPass [numChannels][numAllPasses];
-
- SmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb)
- };
-
- } // namespace juce
|