mirror of
https://codeberg.org/armin/justasample.git
synced 2026-09-01 04:10:48 +02:00
150 lines
5.2 KiB
C++
150 lines
5.2 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
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);
|
|
}
|
|
}
|