/* ============================================================================== ReverbResponse.cpp Created: 17 Jan 2024 11:16:38pm Author: binya ============================================================================== */ #include #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 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(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::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); } }