Add change detection to Display, WaveformDisplay, FilterDisplay, EnvelopeDisplay, and LFODisplay so repaint() is only called when values actually change. Gate updateVisuals() by currentTab so only the visible tab's displays are updated each timer tick. Similarly gate updateModList/updateMacroList to their respective tabs. Manage dynamically created controls via ownedControls unique_ptr vector instead of raw new leaks. Null-check parameter lookups before creating attachments. Hold engine control lock when reading matrix/macros/LFO shape data. Fix envelope display curve directions (attack rising from bottom, decay falling toward sustain, release falling toward bottom). Add tooltip to FilterDisplay noting the approximation. Replace ToggleButton raw param pointer with ParameterAttachment for proper gesture handling. Sync preset and scale combos to processor state.
83 lines
2.6 KiB
C++
83 lines
2.6 KiB
C++
#include "EnvelopeDisplay.h"
|
|
|
|
namespace serum
|
|
{
|
|
|
|
void EnvelopeDisplay::setParams (float a, float d, float s, float r, float c)
|
|
{
|
|
if (attack == a && decay == d && sustain == s && release == r && curve == c)
|
|
return;
|
|
attack = a; decay = d; sustain = s; release = r; curve = c;
|
|
repaint();
|
|
}
|
|
|
|
void EnvelopeDisplay::paint (juce::Graphics& g)
|
|
{
|
|
const auto b = getLocalBounds().toFloat().reduced (4.0f);
|
|
g.setColour (juce::Colours::black.withAlpha (0.4f));
|
|
g.fillRoundedRectangle (b.expanded (4.0f), 4.0f);
|
|
g.setColour (theme::outline);
|
|
g.drawRoundedRectangle (b.expanded (4.0f).reduced (0.5f), 4.0f, 1.0f);
|
|
|
|
const float atkShape = 0.3f + curve * 2.7f;
|
|
const float decShape = 3.0f - curve * 2.7f;
|
|
|
|
// Normalise durations for display, allowing a short sustain plateau.
|
|
const float aSec = maps::toSeconds (attack);
|
|
const float dSec = maps::toSeconds (decay);
|
|
const float rSec = maps::toSeconds (release);
|
|
const float total = aSec + dSec + rSec + 0.01f;
|
|
const float ax = (aSec / total);
|
|
const float dx = (dSec / total);
|
|
const float rx = (rSec / total);
|
|
|
|
const float left = b.getX();
|
|
const float right = b.getRight();
|
|
const float bottom = b.getBottom();
|
|
const float top = b.getY();
|
|
const float sustainY = bottom - sustain * b.getHeight();
|
|
|
|
juce::Path path;
|
|
path.startNewSubPath (left, bottom);
|
|
|
|
// Attack (curve-shaped).
|
|
const int steps = 48;
|
|
const float peakX = left + ax * b.getWidth();
|
|
for (int i = 0; i <= steps; ++i)
|
|
{
|
|
const float p = (float) i / steps;
|
|
const float y = bottom - (bottom - top) * std::pow (p, atkShape);
|
|
path.lineTo (left + p * (peakX - left), y);
|
|
}
|
|
|
|
// Decay to sustain.
|
|
const float decX = peakX + dx * b.getWidth();
|
|
for (int i = 0; i <= steps; ++i)
|
|
{
|
|
const float p = (float) i / steps;
|
|
const float y = sustainY + (top - sustainY) * std::pow (1.0f - p, decShape);
|
|
path.lineTo (peakX + p * (decX - peakX), y);
|
|
}
|
|
path.lineTo (decX, sustainY);
|
|
path.lineTo (right - rx * b.getWidth(), sustainY);
|
|
|
|
// Release.
|
|
const float relStartX = right - rx * b.getWidth();
|
|
for (int i = 0; i <= steps; ++i)
|
|
{
|
|
const float p = (float) i / steps;
|
|
const float y = bottom - (bottom - sustainY) * std::pow (1.0f - p, decShape);
|
|
path.lineTo (relStartX + p * (right - relStartX), y);
|
|
}
|
|
|
|
g.setColour (theme::accent2.withAlpha (0.3f));
|
|
juce::Path fill = path;
|
|
fill.lineTo (right, bottom);
|
|
fill.closeSubPath();
|
|
g.fillPath (fill);
|
|
g.setColour (theme::accent2);
|
|
g.strokePath (path, juce::PathStrokeType (1.5f));
|
|
}
|
|
|
|
} // namespace serum
|