feat(gui): add GUI components and displays

This commit is contained in:
2026-09-08 14:55:22 +02:00
parent 5a80534a2e
commit a9abca9e15
19 changed files with 898 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
#include "LFODisplay.h"
namespace serum
{
void LFODisplay::setShapeData (const std::vector<float>& data, int s)
{
steps = juce::jlimit (2, 64, s);
shapeData = data;
if ((int) shapeData.size() < 64)
shapeData.resize (64, 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;
}
}
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));
}
}
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 (64, 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