Prototype

This commit is contained in:
Armin 2026-08-05 23:12:17 +02:00
commit 4271fc34bb
14 changed files with 950 additions and 169 deletions

View file

@ -5,7 +5,7 @@
using namespace monostep;
SequencerMatrix::SequencerMatrix (MonoStepAudioProcessor& processorRef)
SequencerMatrix::SequencerMatrix (MonostepAudioProcessor& processorRef)
: processor (processorRef)
{
}
@ -22,13 +22,19 @@ int SequencerMatrix::semitoneForRow (int row) const
juce::Rectangle<int> SequencerMatrix::matrixBounds() const
{
const int w = getWidth();
const int h = getHeight();
if (w > labelW && h > headerH)
{
cellW = (float) (w - labelW) / (float) numSteps;
cellH = (float) (h - headerH) / (float) (numRows + 2);
}
const int gridW = juce::roundToInt (cellW * numSteps);
const int gridH = juce::roundToInt (cellH * (numRows + 1));
const int gridH = juce::roundToInt (cellH * (numRows + 2));
const int x = juce::roundToInt (labelW + (getWidth() - labelW - gridW) / 2.0f);
const int y = juce::roundToInt (headerH + (getHeight() - headerH - gridH) / 2.0f);
return { x, y, gridW, gridH };
return { juce::roundToInt (labelW), juce::roundToInt (headerH), gridW, gridH };
}
void SequencerMatrix::setSelectedStep (int stepIdx)
@ -65,9 +71,16 @@ void SequencerMatrix::paint (juce::Graphics& g)
g.fillRect (bounds.toFloat());
// selected column highlight
g.setColour (colours::selected.withAlpha (0.07f));
g.setColour (colours::selected.withAlpha (0.02f));
g.fillRect (ox + selectedStep * cellW, oy, cellW, (float) bounds.getHeight());
// play position column highlight (drawn before cells so notes stay vivid)
if (playPos >= 0)
{
g.setColour (colours::accent.withAlpha (0.04f));
g.fillRect (ox + playPos * cellW, oy, cellW, (float) bounds.getHeight());
}
// grid lines
g.setColour (colours::grid);
@ -77,7 +90,7 @@ void SequencerMatrix::paint (juce::Graphics& g)
g.drawVerticalLine (juce::roundToInt (x), oy, oy + (float) bounds.getHeight());
}
for (int i = 0; i <= numRows + 1; ++i)
for (int i = 0; i <= numRows + 2; ++i)
{
const float y = oy + i * cellH;
g.drawHorizontalLine (juce::roundToInt (y), ox, ox + (float) bounds.getWidth());
@ -107,14 +120,6 @@ void SequencerMatrix::paint (juce::Graphics& g)
g.setColour ((i % 4 == 0 && i < seqLen) ? colours::text : colours::textDim);
g.setFont (font (10.0f));
g.drawText (juce::String (i + 1), juce::roundToInt (cx - 8.0f), juce::roundToInt (cy - 6.0f), 16, 12, juce::Justification::centred);
if (i == selectedStep)
{
g.setColour (colours::accent);
juce::Path tri;
tri.addTriangle (cx - 4.0f, cy - 8.0f, cx + 4.0f, cy - 8.0f, cx, cy);
g.fillPath (tri);
}
}
// pitch labels
@ -152,10 +157,10 @@ void SequencerMatrix::paint (juce::Graphics& g)
const float radius = 3.0f;
// cell body
juce::ColourGradient grad (isSelected ? colours::accent : colours::activeCell,
cell.getTopLeft(),
isSelected ? colours::accent2 : colours::activeCell.darker (0.6f),
cell.getBottomRight(),
juce::ColourGradient grad (colours::accent,
cell.getTopLeft().translated (cell.getWidth() * 0.5f, 0.0f),
isSelected ? colours::accent.brighter (0.12f) : colours::accent.darker (0.35f),
cell.getBottomLeft().translated (cell.getWidth() * 0.5f, 0.0f),
false);
g.setGradientFill (grad);
@ -215,6 +220,39 @@ void SequencerMatrix::paint (juce::Graphics& g)
g.fillPath (tri);
}
// --- accent row (bottom row) ----------------------------------------------
const float accentY = oy + (numRows + 1) * cellH;
g.setColour (colours::accentDim);
g.drawHorizontalLine (juce::roundToInt (accentY - 1.0f), ox, ox + (float) bounds.getWidth());
g.setFont (font (9.0f));
g.setColour (colours::textDim);
g.drawText ("ACCENT",
juce::roundToInt (labelW * 0.1f), juce::roundToInt (accentY + cellH * 0.5f - 6.0f),
juce::roundToInt (labelW * 0.8f), 12,
juce::Justification::centredRight);
for (int i = 0; i < numSteps; ++i)
{
if (! processor.getSequencer().step (i).accent)
continue;
const auto cell = juce::Rectangle<float> (ox + i * cellW + 1.5f, accentY + 1.5f,
cellW - 3.0f, cellH - 3.0f);
g.setColour (colours::accent2);
g.fillRoundedRectangle (cell, 3.0f);
const float ax = cell.getX() + cell.getWidth() * 0.5f;
const float ay = cell.getY() + cell.getHeight() * 0.5f;
g.setColour (colours::bg);
juce::Path tri;
tri.addTriangle (ax - 2.0f, ay - 2.5f, ax + 2.0f, ay - 2.5f, ax, ay + 2.5f);
g.fillPath (tri);
}
// --- dim columns beyond the pattern length ---------------------------------
if (seqLen < numSteps)
{
@ -222,21 +260,6 @@ void SequencerMatrix::paint (juce::Graphics& g)
g.fillRect (ox + seqLen * cellW, oy,
(numSteps - seqLen) * cellW, (float) bounds.getHeight());
}
// --- play position marker ---------------------------------------------------
if (playPos >= 0)
{
const float px = ox + playPos * cellW + cellW * 0.5f;
g.setColour (colours::playMarker.withAlpha (0.65f));
g.drawVerticalLine (juce::roundToInt (px - 1.0f), oy, oy + (float) bounds.getHeight());
g.drawVerticalLine (juce::roundToInt (px), oy, oy + (float) bounds.getHeight());
g.setColour (colours::playMarker);
juce::Path tri;
tri.addTriangle (px - 3.0f, oy - headerH + 2.0f, px + 3.0f, oy - headerH + 2.0f, px, oy - 1.0f);
g.fillPath (tri);
}
}
SequencerMatrix::CellRect SequencerMatrix::cellAt (const juce::MouseEvent& e) const
@ -248,9 +271,9 @@ SequencerMatrix::CellRect SequencerMatrix::cellAt (const juce::MouseEvent& e) co
const bool hit = bounds.contains (e.getPosition())
&& col >= 0 && col < numSteps
&& row >= 0 && row < numRows + 1;
&& row >= -1 && row < numRows + 2;
return { juce::jlimit (0, numSteps - 1, col), juce::jlimit (0, numRows, row), hit };
return { juce::jlimit (0, numSteps - 1, col), juce::jlimit (0, numRows + 1, row), hit };
}
void SequencerMatrix::mouseDown (const juce::MouseEvent& e)
@ -260,11 +283,32 @@ void SequencerMatrix::mouseDown (const juce::MouseEvent& e)
if (! cell.hit)
return;
if (cell.row == -1)
{
selectedStep = cell.step;
if (onStepSelected)
onStepSelected (cell.step);
return;
}
selectedStep = cell.step;
if (onStepSelected)
onStepSelected (cell.step);
if (cell.row == numRows + 1)
{
accentDragging = true;
accentLastStep = cell.step;
startedAccentActive = processor.getSequencer().step (cell.step).accent;
if (onAccentChanged)
onAccentChanged (cell.step, ! startedAccentActive);
repaint();
return;
}
if (cell.row == numRows)
{
slideDragging = true;
@ -299,13 +343,12 @@ void SequencerMatrix::mouseDown (const juce::MouseEvent& e)
void SequencerMatrix::mouseDrag (const juce::MouseEvent& e)
{
const auto cell = cellAt (e);
if (! cell.hit || cell.row == -1)
return;
if (fineDragging)
{
const auto cell = cellAt (e);
if (! cell.hit)
return;
// one matrix row (one semitone) maps to 4 cents of fine tuning
const float cents = juce::jlimit (-50.0f, 50.0f,
fineStartCents + (fineRefRow - cell.row) * 4.0f);
@ -325,8 +368,6 @@ void SequencerMatrix::mouseDrag (const juce::MouseEvent& e)
if (slideDragging)
{
const auto cell = cellAt (e);
if (cell.hit && cell.row == numRows && cell.step != slideLastStep)
{
slideLastStep = cell.step;
@ -340,10 +381,26 @@ void SequencerMatrix::mouseDrag (const juce::MouseEvent& e)
return;
}
if (accentDragging)
{
if (cell.hit && cell.row == numRows + 1 && cell.step != accentLastStep)
{
accentLastStep = cell.step;
if (onAccentChanged)
onAccentChanged (cell.step, ! startedAccentActive);
repaint();
}
return;
}
if (! dragging)
return;
const auto cell = cellAt (e);
if (cell.row >= numRows)
return;
if (cell.hit && cell.row < numRows && cell.row != lastRow)
{
@ -352,6 +409,8 @@ void SequencerMatrix::mouseDrag (const juce::MouseEvent& e)
const int pitch = semitoneForRow (cell.row);
const auto& s = processor.getSequencer().step (cell.step);
if (startedActive)
{
if (onNoteChanged)
@ -384,6 +443,12 @@ void SequencerMatrix::mouseUp (const juce::MouseEvent& e)
return;
}
if (accentDragging)
{
accentDragging = false;
return;
}
if (! dragging)
return;
@ -391,7 +456,7 @@ void SequencerMatrix::mouseUp (const juce::MouseEvent& e)
const auto cell = cellAt (e);
if (! cell.hit || cell.step != dragStep || cell.row >= numRows)
if (! cell.hit || cell.step != dragStep || cell.row >= numRows || cell.row == -1)
return;
const int pitch = semitoneForRow (cell.row);