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
This commit is contained in:
biggy committed 2026-10-06 08:50:45 +02:00
1 parent 5c942cad0c
commit e4cea300db
63 files changed
+2360 -805

No files matched your search

+29 -33
View File
@@ -1,52 +1,28 @@
#include "LFODisplay.h"
#include "../ExactCompare.h"
namespace serum
{
void LFODisplay::setShapeData (const std::vector<float>& data, int s)
{
const int newSteps = juce::jlimit (2, 64, s);
const size_t dataSize = std::min (data.size(), size_t (64));
if (steps == newSteps && shapeData.size() == 64
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 v == 0.0f; }))
&& 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 (64, 0.0f);
shapeData.resize ((size_t) LFO::kShapePoints, 0.0f);
repaint();
}
float LFODisplay::valueAt (float phase) const noexcept
{
switch ((LfoShape) shape)
{
case LfoShape::Sine: return std::sin (phase * 6.2831853f);
case LfoShape::Triangle: return 1.0f - 4.0f * std::abs (phase - 0.5f);
case LfoShape::Saw: return 2.0f * phase - 1.0f;
case LfoShape::Square: return (phase < 0.5f) ? 1.0f : -1.0f;
case LfoShape::SampleHold:
{
const int step = juce::jlimit (0, 15, (int) (phase * 16.0f));
return ((step * 2654435761u) & 0xffffu) / 32768.0f - 1.0f;
}
case LfoShape::StepSeq:
{
const int idx = juce::jlimit (0, steps - 1, (int) (phase * steps));
return shapeData.empty() ? 0.0f : shapeData[(size_t) idx];
}
case LfoShape::Freehand:
{
if (shapeData.empty()) return 0.0f;
const float pos = phase * (float) (steps - 1);
const int i0 = (int) pos;
const int i1 = juce::jmin (i0 + 1, steps - 1);
const float frac = pos - (float) i0;
return shapeData[(size_t) i0] * (1.0f - frac) + shapeData[(size_t) i1] * frac;
}
default: return 0.0f;
}
return lfoShapeValue (phase, (LfoShape) shape, steps,
shapeData.data(), (int) shapeData.size(), kSampleHoldLevel);
}
void LFODisplay::paint (juce::Graphics& g)
@@ -75,6 +51,26 @@ void LFODisplay::paint (juce::Graphics& g)
}
}
// 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;
@@ -99,7 +95,7 @@ void LFODisplay::editAt (float x, float y)
const float v = juce::jlimit (-1.0f, 1.0f, (midY - y) / (b.getHeight() * 0.42f));
if (shapeData.empty())
shapeData.assign (64, 0.0f);
shapeData.assign ((size_t) LFO::kShapePoints, 0.0f);
if (shape == (int) LfoShape::StepSeq)
{