2026-08-05 19:55:57 +02:00
|
|
|
#include "SequencerMatrix.h"
|
|
|
|
|
|
|
|
|
|
#include "../PluginProcessor.h"
|
|
|
|
|
#include "../Theme.h"
|
|
|
|
|
|
|
|
|
|
using namespace monostep;
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
SequencerMatrix::SequencerMatrix (MonostepAudioProcessor& processorRef)
|
2026-08-05 19:55:57 +02:00
|
|
|
: processor (processorRef)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SequencerMatrix::rowForSemitone (int semitone) const
|
|
|
|
|
{
|
|
|
|
|
return maxSemitone - semitone;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SequencerMatrix::semitoneForRow (int row) const
|
|
|
|
|
{
|
|
|
|
|
return maxSemitone - row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
juce::Rectangle<int> SequencerMatrix::matrixBounds() const
|
|
|
|
|
{
|
2026-08-05 23:12:17 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
const int gridW = juce::roundToInt (cellW * numSteps);
|
2026-08-05 23:12:17 +02:00
|
|
|
const int gridH = juce::roundToInt (cellH * (numRows + 2));
|
2026-08-05 19:55:57 +02:00
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
return { juce::roundToInt (labelW), juce::roundToInt (headerH), gridW, gridH };
|
2026-08-05 19:55:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SequencerMatrix::setSelectedStep (int stepIdx)
|
|
|
|
|
{
|
|
|
|
|
if (stepIdx < 0 || stepIdx >= numSteps)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
selectedStep = stepIdx;
|
|
|
|
|
repaint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SequencerMatrix::setPlayPosition (int stepIdx)
|
|
|
|
|
{
|
|
|
|
|
if (stepIdx < -1 || stepIdx >= numSteps)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (stepIdx == playPos)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
playPos = stepIdx;
|
|
|
|
|
repaint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SequencerMatrix::paint (juce::Graphics& g)
|
|
|
|
|
{
|
|
|
|
|
g.fillAll (colours::panel);
|
|
|
|
|
|
|
|
|
|
const auto bounds = matrixBounds();
|
|
|
|
|
const float ox = (float) bounds.getX();
|
|
|
|
|
const float oy = (float) bounds.getY();
|
|
|
|
|
|
2026-08-06 15:51:56 +02:00
|
|
|
// Slightly-3D header band above the grid.
|
|
|
|
|
juce::ColourGradient headerGrad (colours::gridLight,
|
|
|
|
|
0.0f, 0.0f,
|
|
|
|
|
colours::panel.darker (0.10f),
|
|
|
|
|
0.0f, headerH, false);
|
|
|
|
|
g.setGradientFill (headerGrad);
|
|
|
|
|
g.fillRect (0.0f, 0.0f, (float) getWidth(), headerH);
|
|
|
|
|
|
|
|
|
|
g.setColour (colours::gridLight.brighter (0.3f).withAlpha (0.4f));
|
|
|
|
|
g.fillRect (0.0f, 0.0f, (float) getWidth(), 1.0f);
|
|
|
|
|
|
|
|
|
|
g.setColour (colours::grid);
|
|
|
|
|
g.drawHorizontalLine (juce::roundToInt (headerH) - 1, 0, (float) getWidth());
|
|
|
|
|
g.setColour (colours::bg);
|
|
|
|
|
g.drawHorizontalLine (juce::roundToInt (headerH), 0, (float) getWidth());
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
// grid background
|
|
|
|
|
g.setColour (colours::bg);
|
|
|
|
|
g.fillRect (bounds.toFloat());
|
|
|
|
|
|
|
|
|
|
// selected column highlight
|
2026-08-05 23:12:17 +02:00
|
|
|
g.setColour (colours::selected.withAlpha (0.02f));
|
2026-08-05 19:55:57 +02:00
|
|
|
g.fillRect (ox + selectedStep * cellW, oy, cellW, (float) bounds.getHeight());
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
// 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());
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
// grid lines
|
|
|
|
|
g.setColour (colours::grid);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i <= numSteps; ++i)
|
|
|
|
|
{
|
|
|
|
|
const float x = ox + i * cellW;
|
|
|
|
|
g.drawVerticalLine (juce::roundToInt (x), oy, oy + (float) bounds.getHeight());
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
for (int i = 0; i <= numRows + 2; ++i)
|
2026-08-05 19:55:57 +02:00
|
|
|
{
|
|
|
|
|
const float y = oy + i * cellH;
|
|
|
|
|
g.drawHorizontalLine (juce::roundToInt (y), ox, ox + (float) bounds.getWidth());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// accent C rows (semitone multiple of 12)
|
|
|
|
|
for (int r = 0; r < numRows; ++r)
|
|
|
|
|
{
|
|
|
|
|
const int st = semitoneForRow (r);
|
|
|
|
|
|
|
|
|
|
if (st % 12 == 0)
|
|
|
|
|
{
|
|
|
|
|
g.setColour (colours::gridLight);
|
|
|
|
|
const float y = oy + r * cellH;
|
|
|
|
|
g.drawHorizontalLine (juce::roundToInt (y), ox, ox + (float) bounds.getWidth());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// step header numbers + selected marker
|
|
|
|
|
const int seqLen = processor.getPatternLength();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < numSteps; ++i)
|
|
|
|
|
{
|
|
|
|
|
const float cx = ox + i * cellW + cellW * 0.5f;
|
|
|
|
|
const float cy = oy - headerH * 0.5f;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pitch labels
|
|
|
|
|
for (int r = 0; r < numRows; ++r)
|
|
|
|
|
{
|
|
|
|
|
const int st = semitoneForRow (r);
|
|
|
|
|
const float ly = oy + r * cellH + cellH * 0.5f;
|
|
|
|
|
|
|
|
|
|
g.setFont (font (8.5f));
|
|
|
|
|
g.setColour (colours::textDim);
|
|
|
|
|
g.drawText (juce::String (st > 0 ? "+" : "") + juce::String (st),
|
|
|
|
|
juce::roundToInt (labelW * 0.1f), juce::roundToInt (ly - 5.0f), juce::roundToInt (labelW * 0.6f), 10,
|
|
|
|
|
juce::Justification::centredRight);
|
|
|
|
|
|
|
|
|
|
g.setColour (st % 12 == 0 ? colours::accent : colours::text);
|
|
|
|
|
g.drawText (noteNameForOffset (st),
|
|
|
|
|
juce::roundToInt (labelW * 0.62f), juce::roundToInt (ly - 5.0f), juce::roundToInt (labelW * 0.4f), 10,
|
|
|
|
|
juce::Justification::centredRight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// active cells
|
|
|
|
|
for (int i = 0; i < numSteps; ++i)
|
|
|
|
|
{
|
|
|
|
|
const auto& s = processor.getSequencer().step (i);
|
|
|
|
|
|
|
|
|
|
if (! s.gate)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const int r = rowForSemitone (s.semitone);
|
|
|
|
|
|
|
|
|
|
const auto cell = juce::Rectangle<float> (ox + i * cellW + 1.5f, oy + r * cellH + 1.5f,
|
|
|
|
|
cellW - 3.0f, cellH - 3.0f);
|
|
|
|
|
|
|
|
|
|
const bool isSelected = (i == selectedStep);
|
|
|
|
|
const float radius = 3.0f;
|
|
|
|
|
|
2026-08-06 15:51:56 +02:00
|
|
|
// cell body: keep a visible top->bottom gradient in both states. The selected
|
|
|
|
|
// note is brighter overall, but still holds its gradient (a flat bright fill
|
|
|
|
|
// would read as "no gradient").
|
|
|
|
|
const juce::Colour noteTop = isSelected ? colours::accent.brighter (0.2f) : colours::accent;
|
|
|
|
|
const juce::Colour noteBottom = colours::accent.darker (0.3f);
|
|
|
|
|
|
|
|
|
|
juce::ColourGradient grad (noteTop,
|
|
|
|
|
cell.getTopLeft().translated (cell.getWidth() * 0.5f, 0.0f),
|
|
|
|
|
noteBottom,
|
|
|
|
|
cell.getBottomLeft().translated (cell.getWidth() * 0.5f, 0.0f),
|
|
|
|
|
false);
|
2026-08-05 19:55:57 +02:00
|
|
|
|
|
|
|
|
g.setGradientFill (grad);
|
|
|
|
|
g.fillRoundedRectangle (cell, radius);
|
|
|
|
|
|
|
|
|
|
// fine-tune indicator
|
|
|
|
|
if (std::fabs (s.cents) > 0.5f)
|
|
|
|
|
{
|
|
|
|
|
g.setColour (colours::bg.withAlpha (0.85f));
|
|
|
|
|
g.setFont (font (7.5f));
|
|
|
|
|
g.drawText (formatCents (s.cents), cell.toNearestInt().reduced (1, 1), juce::Justification::topLeft);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- slide row (bottom row) ----------------------------------------------
|
|
|
|
|
const float slideY = oy + numRows * cellH;
|
|
|
|
|
|
|
|
|
|
g.setColour (colours::accentDim);
|
|
|
|
|
g.drawHorizontalLine (juce::roundToInt (slideY - 1.0f), ox, ox + (float) bounds.getWidth());
|
|
|
|
|
|
|
|
|
|
g.setFont (font (9.0f));
|
|
|
|
|
g.setColour (colours::textDim);
|
|
|
|
|
g.drawText ("SLIDE",
|
|
|
|
|
juce::roundToInt (labelW * 0.1f), juce::roundToInt (slideY + 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).slide)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const auto cell = juce::Rectangle<float> (ox + i * cellW + 1.5f, slideY + 1.5f,
|
2026-08-06 15:51:56 +02:00
|
|
|
cellW - 3.0f, cellH - 3.0f);
|
2026-08-05 19:55:57 +02:00
|
|
|
|
2026-08-06 15:51:56 +02:00
|
|
|
// match the gradient style of the note cells
|
|
|
|
|
juce::ColourGradient grad (colours::accent,
|
|
|
|
|
cell.getTopLeft().translated (cell.getWidth() * 0.5f, 0.0f),
|
|
|
|
|
colours::accent.darker (0.3f),
|
|
|
|
|
cell.getBottomLeft().translated (cell.getWidth() * 0.5f, 0.0f),
|
|
|
|
|
false);
|
|
|
|
|
g.setGradientFill (grad);
|
2026-08-05 19:55:57 +02:00
|
|
|
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 - 3.0f, ay - 2.0f, ax - 3.0f, ay + 2.0f, ax + 3.5f, ay);
|
|
|
|
|
g.fillPath (tri);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
// --- 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);
|
|
|
|
|
|
2026-08-06 15:51:56 +02:00
|
|
|
const bool isSelected = (i == selectedStep);
|
|
|
|
|
|
|
|
|
|
juce::ColourGradient agrad (isSelected ? colours::accent2.brighter (0.2f) : colours::accent2,
|
|
|
|
|
cell.getTopLeft().translated (cell.getWidth() * 0.5f, 0.0f),
|
|
|
|
|
colours::accent2.darker (0.3f),
|
|
|
|
|
cell.getBottomLeft().translated (cell.getWidth() * 0.5f, 0.0f),
|
|
|
|
|
false);
|
|
|
|
|
|
|
|
|
|
g.setGradientFill (agrad);
|
2026-08-05 23:12:17 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
// --- dim columns beyond the pattern length ---------------------------------
|
|
|
|
|
if (seqLen < numSteps)
|
|
|
|
|
{
|
|
|
|
|
g.setColour (colours::bg.withAlpha (0.55f));
|
|
|
|
|
g.fillRect (ox + seqLen * cellW, oy,
|
|
|
|
|
(numSteps - seqLen) * cellW, (float) bounds.getHeight());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SequencerMatrix::CellRect SequencerMatrix::cellAt (const juce::MouseEvent& e) const
|
|
|
|
|
{
|
|
|
|
|
const auto bounds = matrixBounds();
|
|
|
|
|
|
|
|
|
|
const int col = (int) std::floor ((e.getPosition().getX() - bounds.getX()) / cellW);
|
|
|
|
|
const int row = (int) std::floor ((e.getPosition().getY() - bounds.getY()) / cellH);
|
|
|
|
|
|
|
|
|
|
const bool hit = bounds.contains (e.getPosition())
|
|
|
|
|
&& col >= 0 && col < numSteps
|
2026-08-05 23:12:17 +02:00
|
|
|
&& row >= -1 && row < numRows + 2;
|
2026-08-05 19:55:57 +02:00
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
return { juce::jlimit (0, numSteps - 1, col), juce::jlimit (0, numRows + 1, row), hit };
|
2026-08-05 19:55:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SequencerMatrix::mouseDown (const juce::MouseEvent& e)
|
|
|
|
|
{
|
|
|
|
|
const auto cell = cellAt (e);
|
|
|
|
|
|
|
|
|
|
if (! cell.hit)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
if (cell.row == -1)
|
|
|
|
|
{
|
|
|
|
|
selectedStep = cell.step;
|
|
|
|
|
if (onStepSelected)
|
|
|
|
|
onStepSelected (cell.step);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
selectedStep = cell.step;
|
|
|
|
|
|
|
|
|
|
if (onStepSelected)
|
|
|
|
|
onStepSelected (cell.step);
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
if (cell.row == numRows)
|
|
|
|
|
{
|
|
|
|
|
slideDragging = true;
|
|
|
|
|
slideLastStep = cell.step;
|
|
|
|
|
startedSlideActive = processor.getSequencer().step (cell.step).slide;
|
|
|
|
|
|
|
|
|
|
if (onSlideChanged)
|
|
|
|
|
onSlideChanged (cell.step, ! startedSlideActive);
|
|
|
|
|
|
|
|
|
|
repaint();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e.mods.isAltDown())
|
|
|
|
|
{
|
|
|
|
|
fineDragging = true;
|
|
|
|
|
fineRefRow = cell.row;
|
|
|
|
|
fineStartCents = processor.getSequencer().step (cell.step).cents;
|
|
|
|
|
fineLastCents = -1000.0f;
|
|
|
|
|
repaint();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dragging = true;
|
|
|
|
|
moved = false;
|
|
|
|
|
dragStep = cell.step;
|
|
|
|
|
lastRow = cell.row;
|
|
|
|
|
|
|
|
|
|
const auto& s = processor.getSequencer().step (cell.step);
|
|
|
|
|
startedActive = (s.gate && s.semitone == semitoneForRow (cell.row));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SequencerMatrix::mouseDrag (const juce::MouseEvent& e)
|
|
|
|
|
{
|
2026-08-05 23:12:17 +02:00
|
|
|
const auto cell = cellAt (e);
|
|
|
|
|
if (! cell.hit || cell.row == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
if (fineDragging)
|
|
|
|
|
{
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
if (std::fabs (cents - fineLastCents) >= 0.5f)
|
|
|
|
|
{
|
|
|
|
|
fineLastCents = cents;
|
|
|
|
|
|
|
|
|
|
if (onFineChanged)
|
|
|
|
|
onFineChanged (selectedStep, cents);
|
|
|
|
|
|
|
|
|
|
repaint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (slideDragging)
|
|
|
|
|
{
|
|
|
|
|
if (cell.hit && cell.row == numRows && cell.step != slideLastStep)
|
|
|
|
|
{
|
|
|
|
|
slideLastStep = cell.step;
|
|
|
|
|
|
|
|
|
|
if (onSlideChanged)
|
|
|
|
|
onSlideChanged (cell.step, ! startedSlideActive);
|
|
|
|
|
|
|
|
|
|
repaint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
if (accentDragging)
|
|
|
|
|
{
|
|
|
|
|
if (cell.hit && cell.row == numRows + 1 && cell.step != accentLastStep)
|
|
|
|
|
{
|
|
|
|
|
accentLastStep = cell.step;
|
|
|
|
|
|
|
|
|
|
if (onAccentChanged)
|
|
|
|
|
onAccentChanged (cell.step, ! startedAccentActive);
|
|
|
|
|
|
|
|
|
|
repaint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
if (! dragging)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
if (cell.row >= numRows)
|
|
|
|
|
return;
|
2026-08-05 19:55:57 +02:00
|
|
|
|
|
|
|
|
if (cell.hit && cell.row < numRows && cell.row != lastRow)
|
|
|
|
|
{
|
|
|
|
|
lastRow = cell.row;
|
|
|
|
|
moved = true;
|
|
|
|
|
|
|
|
|
|
const int pitch = semitoneForRow (cell.row);
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
const auto& s = processor.getSequencer().step (cell.step);
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
if (startedActive)
|
|
|
|
|
{
|
|
|
|
|
if (onNoteChanged)
|
|
|
|
|
onNoteChanged (dragStep, pitch);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (onGateChanged)
|
|
|
|
|
onGateChanged (dragStep, true);
|
|
|
|
|
|
|
|
|
|
if (onNoteChanged)
|
|
|
|
|
onNoteChanged (dragStep, pitch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repaint();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SequencerMatrix::mouseUp (const juce::MouseEvent& e)
|
|
|
|
|
{
|
|
|
|
|
if (fineDragging)
|
|
|
|
|
{
|
|
|
|
|
fineDragging = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (slideDragging)
|
|
|
|
|
{
|
|
|
|
|
slideDragging = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
if (accentDragging)
|
|
|
|
|
{
|
|
|
|
|
accentDragging = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 19:55:57 +02:00
|
|
|
if (! dragging)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
dragging = false;
|
|
|
|
|
|
|
|
|
|
const auto cell = cellAt (e);
|
|
|
|
|
|
2026-08-05 23:12:17 +02:00
|
|
|
if (! cell.hit || cell.step != dragStep || cell.row >= numRows || cell.row == -1)
|
2026-08-05 19:55:57 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const int pitch = semitoneForRow (cell.row);
|
|
|
|
|
|
|
|
|
|
if (startedActive && ! moved)
|
|
|
|
|
{
|
|
|
|
|
if (onGateChanged)
|
|
|
|
|
onGateChanged (cell.step, false);
|
|
|
|
|
}
|
|
|
|
|
else if (! startedActive && ! moved)
|
|
|
|
|
{
|
|
|
|
|
if (onGateChanged)
|
|
|
|
|
onGateChanged (cell.step, true);
|
|
|
|
|
|
|
|
|
|
if (onNoteChanged)
|
|
|
|
|
onNoteChanged (cell.step, pitch);
|
|
|
|
|
}
|
|
|
|
|
}
|