2026-07-21 02:06:45 +02:00
|
|
|
#include "PianoRollDisplay.h"
|
|
|
|
|
|
|
|
|
|
PianoRollDisplay::PianoRollDisplay(SliceManager& sm) : sliceManager(sm)
|
|
|
|
|
{
|
|
|
|
|
startTimerHz(20);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PianoRollDisplay::~PianoRollDisplay() { stopTimer(); }
|
|
|
|
|
|
|
|
|
|
void PianoRollDisplay::timerCallback() { repaint(); }
|
|
|
|
|
|
|
|
|
|
void PianoRollDisplay::setActiveSlice(int index) { activeSlice = index; }
|
|
|
|
|
void PianoRollDisplay::setSelectedSlice(int index) { selectedSlice = index; }
|
|
|
|
|
void PianoRollDisplay::setTotalSamples(int total) { totalSamples = total; }
|
|
|
|
|
void PianoRollDisplay::setOnKeyClick(std::function<void(int)> callback) { onKeyClick = std::move(callback); }
|
|
|
|
|
|
|
|
|
|
juce::Colour PianoRollDisplay::sliceColour(int sliceIndex) const
|
|
|
|
|
{
|
|
|
|
|
juce::Colour colours[] = {
|
|
|
|
|
juce::Colour(0xffff6b6b), juce::Colour(0xff4ecdc4), juce::Colour(0xffffe66d),
|
|
|
|
|
juce::Colour(0xffa8e6cf), juce::Colour(0xff8b94ff), juce::Colour(0xffff8b94),
|
|
|
|
|
juce::Colour(0xffdda0dd), juce::Colour(0xff87ceeb), juce::Colour(0xff98fb98),
|
|
|
|
|
juce::Colour(0xffffb347), juce::Colour(0xffc39bd3), juce::Colour(0xff76d7c4)
|
|
|
|
|
};
|
|
|
|
|
return colours[sliceIndex % 12];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int PianoRollDisplay::rowToSliceIndex(int row) const
|
|
|
|
|
{
|
2026-07-22 23:10:55 +02:00
|
|
|
const int baseMidiNote = 60;
|
|
|
|
|
int midiNote = baseMidiNote + (numRows - 1 - row);
|
2026-07-21 02:06:45 +02:00
|
|
|
const auto& slices = sliceManager.getSlices();
|
|
|
|
|
for (int i = 0; i < sliceManager.getNumSlices(); ++i)
|
|
|
|
|
{
|
2026-07-22 23:10:55 +02:00
|
|
|
if (slices[static_cast<size_t>(i)].midiNote == midiNote)
|
2026-07-21 02:06:45 +02:00
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PianoRollDisplay::paint(juce::Graphics& g)
|
|
|
|
|
{
|
|
|
|
|
auto bounds = getLocalBounds();
|
|
|
|
|
float w = static_cast<float>(bounds.getWidth());
|
|
|
|
|
float h = static_cast<float>(bounds.getHeight());
|
|
|
|
|
float rowHeight = h / static_cast<float>(numRows);
|
|
|
|
|
|
|
|
|
|
g.fillAll(juce::Colour(0xff1a1a2e));
|
|
|
|
|
|
|
|
|
|
const auto& slices = sliceManager.getSlices();
|
|
|
|
|
int numSlices = sliceManager.getNumSlices();
|
|
|
|
|
|
|
|
|
|
int total = totalSamples;
|
|
|
|
|
if (total <= 0 && numSlices > 0)
|
|
|
|
|
total = slices[static_cast<size_t>(numSlices - 1)].endSample;
|
|
|
|
|
|
|
|
|
|
// Draw grid lines
|
|
|
|
|
for (int row = 0; row < numRows; ++row)
|
|
|
|
|
{
|
|
|
|
|
float y = static_cast<float>(row) * rowHeight;
|
|
|
|
|
g.setColour(juce::Colour(0x20ffffff));
|
|
|
|
|
g.drawHorizontalLine(static_cast<int>(y), 0.0f, w);
|
|
|
|
|
}
|
|
|
|
|
g.drawHorizontalLine(static_cast<int>(h), 0.0f, w);
|
|
|
|
|
|
|
|
|
|
// Draw slice blocks
|
|
|
|
|
if (numSlices > 0 && total > 0)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < numSlices; ++i)
|
|
|
|
|
{
|
|
|
|
|
const auto& slice = slices[static_cast<size_t>(i)];
|
2026-07-22 23:10:55 +02:00
|
|
|
const int baseMidiNote = 60;
|
|
|
|
|
int noteRow = slice.midiNote - baseMidiNote;
|
2026-07-21 02:06:45 +02:00
|
|
|
int row = numRows - 1 - noteRow;
|
|
|
|
|
|
|
|
|
|
float xFrac = static_cast<float>(slice.startSample) / static_cast<float>(total);
|
|
|
|
|
float endFrac = static_cast<float>(slice.endSample) / static_cast<float>(total);
|
|
|
|
|
float x = xFrac * w;
|
|
|
|
|
float bw = (endFrac - xFrac) * w;
|
|
|
|
|
|
|
|
|
|
float y = static_cast<float>(row) * rowHeight + 2.0f;
|
|
|
|
|
float bh = rowHeight - 4.0f;
|
|
|
|
|
|
|
|
|
|
bw = juce::jmax(bw, 2.0f);
|
|
|
|
|
|
|
|
|
|
juce::Colour col = sliceColour(i);
|
|
|
|
|
bool isActive = (i == activeSlice);
|
|
|
|
|
bool isSelected = (i == selectedSlice);
|
|
|
|
|
|
|
|
|
|
g.setColour(isActive ? col.brighter(0.3f) : col.withAlpha(0.8f));
|
|
|
|
|
g.fillRoundedRectangle(x + 1.0f, y, bw - 2.0f, bh, 3.0f);
|
|
|
|
|
|
|
|
|
|
if (isSelected && !isActive)
|
|
|
|
|
{
|
|
|
|
|
g.setColour(col.brighter(0.5f));
|
|
|
|
|
g.drawRoundedRectangle(x + 1.0f, y, bw - 2.0f, bh, 3.0f, 2.0f);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g.setColour(col.brighter(0.5f).withAlpha(0.6f));
|
|
|
|
|
g.drawRoundedRectangle(x + 1.0f, y, bw - 2.0f, bh, 3.0f, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bw > 16.0f)
|
|
|
|
|
{
|
|
|
|
|
g.setColour(juce::Colours::white);
|
|
|
|
|
g.setFont(10.0f);
|
|
|
|
|
g.drawText(juce::String(i + 1), static_cast<int>(x + 3.0f), static_cast<int>(y),
|
|
|
|
|
static_cast<int>(bw - 6.0f), static_cast<int>(bh),
|
|
|
|
|
juce::Justification::centred);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PianoRollDisplay::resized() {}
|
|
|
|
|
|
|
|
|
|
void PianoRollDisplay::mouseDown(const juce::MouseEvent& e)
|
|
|
|
|
{
|
|
|
|
|
float rowHeight = static_cast<float>(getHeight()) / static_cast<float>(numRows);
|
|
|
|
|
int row = static_cast<int>(static_cast<float>(e.getPosition().getY()) / rowHeight);
|
|
|
|
|
row = juce::jlimit(0, numRows - 1, row);
|
|
|
|
|
|
|
|
|
|
pressedKey = row;
|
|
|
|
|
repaint();
|
|
|
|
|
|
|
|
|
|
int sliceIdx = rowToSliceIndex(row);
|
|
|
|
|
if (sliceIdx >= 0 && onKeyClick)
|
|
|
|
|
onKeyClick(sliceIdx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PianoRollDisplay::mouseUp(const juce::MouseEvent&)
|
|
|
|
|
{
|
|
|
|
|
pressedKey = -1;
|
|
|
|
|
repaint();
|
|
|
|
|
}
|