Files
serumalt/Source/GUI/LFODisplay.cpp
T
biggy e4cea300db chore: land janitor round 1 and graft integration
Round 1 findings and fixes, all previously uncommitted:
- FXProcessor per-slot DSP history repair (HEAD does not compile without it)
- SynthVoice::getModulationValue, editor control-lock guard
- ExactCompare.h naming the deliberate exact float comparisons
- KnobLookAndFeel replacing the never-installed SerumLookAndFeel
- 55 first-party compiler warnings to 0
- AGENT.md renamed to AGENTS.md across Source/
- graft repository context graph and its hooks
- JANITOR_TASKS.md and UI_UX_AUDIT.md records
2026-10-06 08:50:45 +02:00

131 lines
4.3 KiB
C++

#include "LFODisplay.h"
#include "../ExactCompare.h"
namespace serum
{
void LFODisplay::setShapeData (const std::vector<float>& data, int s)
{
const int newSteps = juce::jlimit (2, LFO::kShapePoints, s);
const auto dataSize = (std::ptrdiff_t) std::min (data.size(), size_t (LFO::kShapePoints));
if (steps == newSteps && (int) shapeData.size() == LFO::kShapePoints
&& std::equal (data.begin(), data.begin() + dataSize, shapeData.begin())
&& std::all_of (shapeData.begin() + dataSize, shapeData.end(), [] (float v) { return exactly (v, 0.0f); }))
return;
steps = newSteps;
shapeData.assign (data.begin(), data.begin() + dataSize);
shapeData.resize ((size_t) LFO::kShapePoints, 0.0f);
repaint();
}
float LFODisplay::valueAt (float phase) const noexcept
{
return lfoShapeValue (phase, (LfoShape) shape, steps,
shapeData.data(), (int) shapeData.size(), kSampleHoldLevel);
}
void LFODisplay::paint (juce::Graphics& g)
{
const auto b = getLocalBounds().toFloat();
g.setColour (juce::Colours::black.withAlpha (0.4f));
g.fillRoundedRectangle (b, 4.0f);
g.setColour (theme::outline);
g.drawRoundedRectangle (b.reduced (0.5f), 4.0f, 1.0f);
const float midY = b.getCentreY();
g.setColour (theme::outline);
g.drawLine (b.getX() + 4.0f, midY, b.getRight() - 4.0f, midY, 1.0f);
// Editable shapes show step markers.
if (shape == (int) LfoShape::StepSeq)
{
const float stepW = (b.getWidth() - 8.0f) / (float) steps;
for (int i = 0; i < steps; ++i)
{
const float v = shapeData.empty() ? 0.0f : shapeData[(size_t) i];
const float x = b.getX() + 4.0f + stepW * i;
const float y = midY - v * (b.getHeight() * 0.42f);
g.setColour (theme::amber);
g.fillRect (x + 1.0f, juce::jmin (y, midY), stepW - 2.0f, std::abs (midY - y));
}
}
// Sample and hold is stateful in the audio path, so the preview draws the
// held level as a flat schematic with its hold steps marked, and labels it,
// rather than inventing a sequence the synth will never play.
if (shape == (int) LfoShape::SampleHold)
{
const float stepW = (b.getWidth() - 8.0f) / (float) steps;
g.setColour (theme::outline);
for (int i = 1; i < steps; ++i)
{
const float x = b.getX() + 4.0f + stepW * (float) i;
g.drawLine (x, midY - b.getHeight() * 0.28f, x, midY + b.getHeight() * 0.28f, 1.0f);
}
g.setColour (theme::textDim);
g.setFont (10.0f);
g.drawText ("S&H schematic",
juce::Rectangle<float> (b.getX(), b.getY(), b.getWidth(), 14.0f).toNearestInt(),
juce::Justification::centred, false);
}
juce::Path path;
path.startNewSubPath (b.getX() + 4.0f, midY);
const int n = 256;
for (int i = 0; i <= n; ++i)
{
const float phase = (float) i / (float) n;
const float v = valueAt (phase);
const float x = b.getX() + 4.0f + phase * (b.getWidth() - 8.0f);
const float y = midY - v * (b.getHeight() * 0.42f);
if (i == 0) path.startNewSubPath (x, y);
else path.lineTo (x, y);
}
g.setColour (theme::green);
g.strokePath (path, juce::PathStrokeType (1.5f));
}
void LFODisplay::editAt (float x, float y)
{
const auto b = getLocalBounds().toFloat();
const float midY = b.getCentreY();
const float normX = juce::jlimit (0.0f, 1.0f, (x - b.getX() - 4.0f) / (b.getWidth() - 8.0f));
const float v = juce::jlimit (-1.0f, 1.0f, (midY - y) / (b.getHeight() * 0.42f));
if (shapeData.empty())
shapeData.assign ((size_t) LFO::kShapePoints, 0.0f);
if (shape == (int) LfoShape::StepSeq)
{
const int idx = juce::jlimit (0, steps - 1, (int) (normX * steps));
shapeData[(size_t) idx] = v;
}
else if (shape == (int) LfoShape::Freehand)
{
const int idx = juce::jlimit (0, steps - 1, (int) (normX * steps));
shapeData[(size_t) idx] = v;
}
else
{
return;
}
if (onEdited)
onEdited (shapeData, steps);
repaint();
}
void LFODisplay::mouseDown (const juce::MouseEvent& e)
{
editAt (e.x, e.y);
}
void LFODisplay::mouseDrag (const juce::MouseEvent& e)
{
editAt (e.x, e.y);
}
} // namespace serum