justasample/Source/Components/Displays/ReverbResponse.cpp

150 lines
5.2 KiB
C++
Raw Normal View History

2026-07-23 23:40:48 +02:00
/*
==============================================================================
ReverbResponse.cpp
Created: 17 Jan 2024 11:16:38pm
Author: binya
==============================================================================
*/
#include <JuceHeader.h>
#include "ReverbResponse.h"
ReverbResponse::ReverbResponse(APVTS& apvts) : apvts(apvts),
lowsAttachment(*apvts.getParameter(PluginParameters::REVERB_LOWS), [this](float newValue) { lows = newValue; repaint(); }, apvts.undoManager),
highsAttachment(*apvts.getParameter(PluginParameters::REVERB_HIGHS), [this](float newValue) { highs = newValue; repaint(); }, apvts.undoManager),
mixAttachment(*apvts.getParameter(PluginParameters::REVERB_MIX), [this](float newValue) { mix = newValue; repaint(); }, apvts.undoManager),
responseThread(apvts, sampleRate),
response(1, int(ReverbResponseThread::DISPLAY_TIME * sampleRate / ReverbResponseThread::SAMPLE_RATE_RATIO))
{
lowsAttachment.sendInitialUpdate();
highsAttachment.sendInitialUpdate();
mixAttachment.sendInitialUpdate();
response.clear();
responseThread.startThread();
setBufferedToImage(true);
startTimerHz(30);
}
void ReverbResponse::paint(juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat();
auto theme = getTheme();
// Calculate the values
float numPointsPerPixel = 0.5f;
int numPoints = int(numPointsPerPixel * getWidth());
int samplesPerPoint = int(response.getNumSamples() / numPoints);
juce::Array<float> responseValues;
for (int i = 0; i < numPoints; i++)
{
int sampleIndex = i * samplesPerPoint;
float rms = response.getRMSLevel(0, sampleIndex, juce::jmin(samplesPerPoint, response.getNumSamples() - sampleIndex));
float multiplier = 0.2f + 0.4f * lows + 0.4f * highs;
float yPos = juce::jmap<float>(rms * multiplier, 0.f, 0.7f, bounds.getHeight() / 2.f, getHeight() * 0.02f);
responseValues.add(yPos);
}
// Draw the response
juce::Path rmsPath;
for (int i = 0; i < numPoints; i++)
{
float x = float(i) / numPointsPerPixel;
float y = responseValues[i];
if (i == 0)
rmsPath.startNewSubPath(x, y);
else
rmsPath.lineTo(x, y);
}
for (int i = numPoints - 1; i >= 0; i--)
{
float x = float(i) / numPointsPerPixel;
float y = responseValues[i];
rmsPath.lineTo(x, getHeight() - y);
}
rmsPath.closeSubPath();
g.setColour(theme.dark);
g.fillRect(0.f, mix * getHeight() / 2.f, getWidth() * 0.01f, float(getHeight()) * (1 - mix));
g.fillPath(rmsPath);
if (!isEnabled())
g.fillAll(theme.background.withAlpha(0.5f));
}
void ReverbResponse::enablementChanged()
{
repaint();
}
void ReverbResponse::timerCallback()
{
// Process the response thread changes
bool changesMade{ false };
while (responseThread.getResponseChangeQueue().peek() != nullptr)
{
response = *responseThread.getResponseChangeQueue().peek();
responseThread.getResponseChangeQueue().pop();
changesMade = true;
}
if (changesMade)
repaint();
}
ReverbResponseThread::ReverbResponseThread(const APVTS& apvts, int sampleRate) : Thread("Reverb_Response_Thread"), sampleRate(sampleRate),
sizeAttachment(*apvts.getParameter(PluginParameters::REVERB_SIZE), [this](float value) { size = value; reverbChanged = true; notify(); }, apvts.undoManager),
dampingAttachment(*apvts.getParameter(PluginParameters::REVERB_DAMPING), [this](float value) { damping = value; reverbChanged = true; notify(); }, apvts.undoManager),
delayAttachment(*apvts.getParameter(PluginParameters::REVERB_PREDELAY), [this](float value) { delay = value; reverbChanged = true; notify(); }, apvts.undoManager),
mixAttachment(*apvts.getParameter(PluginParameters::REVERB_MIX), [this](float value) { mix = value; reverbChanged = true; notify(); }, apvts.undoManager),
impulse(1, int(DISPLAY_TIME * sampleRate / SAMPLE_RATE_RATIO))
{
sizeAttachment.sendInitialUpdate();
dampingAttachment.sendInitialUpdate();
delayAttachment.sendInitialUpdate();
mixAttachment.sendInitialUpdate();
initializeImpulse();
}
ReverbResponseThread::~ReverbResponseThread()
{
stopThread(5000);
}
void ReverbResponseThread::initializeImpulse()
{
// Exponential chirp (seems to provide most accurate response)
impulse.clear();
auto impulseSize = int(IMPULSE_TIME * sampleRate / SAMPLE_RATE_RATIO);
for (int i = 1; i <= impulseSize; i++)
{
impulse.setSample(0, i - 1,
sinf(juce::MathConstants<float>::twoPi * CHIRP_START * (powf(CHIRP_END / CHIRP_START, float(i) / impulseSize) - 1.f) /
(float(impulseSize) / i * logf(CHIRP_END / CHIRP_START)))
);
}
}
void ReverbResponseThread::run()
{
while (!threadShouldExit())
{
if (!reverbChanged)
wait(-1); // Wait until the RMS value needs to be updated, and notify() is called
reverbChanged = false;
initializeImpulse();
reverb.initialize(1, juce::jmax(1000, int(sampleRate / SAMPLE_RATE_RATIO)));
reverb.updateParams(size, damping, delay, 1.f, 1.f, mix);
reverb.process(impulse, impulse.getNumSamples());
responseChangeQueue.enqueue(impulse);
}
}