Fix menu scaling issues, fix preset menu layout, add LED display (to

show patch), add prev/next patch buttons
This commit is contained in:
Armin 2026-07-14 00:35:26 +02:00
commit 79be724e5d
5 changed files with 281 additions and 175 deletions

View file

@ -291,9 +291,7 @@ void SpectrumAnalyzer::paint(juce::Graphics& g) {
juce::Graphics::ScopedSaveState saved(g);
g.setOpacity(0.7f);
juce::ColourGradient barGrad(juce::Colour(0xff00ff66), 0.0f, by,
juce::Colour(0xff005522), 0.0f, barBottom, false);
g.setGradientFill(barGrad);
g.setColour(juce::Colour(0xff00ff66));
g.fillRect(bx, by, bw, barH);
}
}
@ -303,6 +301,102 @@ void SpectrumAnalyzer::paint(juce::Graphics& g) {
g.drawText("SPECTRUM", bounds.withBottom(bounds.getY() + 22.0f).translated(0, 6), juce::Justification::centred);
}
// --- Patch LCD (green dot-matrix) ---
namespace {
struct Glyph { char c; unsigned char rows[7]; };
// 5x7 font; each byte is a row, bits 4..0 are columns left->right.
const Glyph font5x7[] = {
{' ', {0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
{'-', {0x00,0x00,0x00,0x1F,0x00,0x00,0x00}},
{'A', {0x0E,0x11,0x11,0x1F,0x11,0x11,0x11}},
{'B', {0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E}},
{'C', {0x0E,0x11,0x10,0x10,0x10,0x11,0x0E}},
{'D', {0x1E,0x11,0x11,0x11,0x11,0x11,0x1E}},
{'E', {0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F}},
{'F', {0x1F,0x10,0x10,0x1E,0x10,0x10,0x10}},
{'G', {0x0E,0x11,0x10,0x17,0x11,0x11,0x0F}},
{'H', {0x11,0x11,0x11,0x1F,0x11,0x11,0x11}},
{'I', {0x0E,0x04,0x04,0x04,0x04,0x04,0x0E}},
{'J', {0x07,0x02,0x02,0x02,0x02,0x12,0x0C}},
{'K', {0x11,0x12,0x14,0x18,0x14,0x12,0x11}},
{'L', {0x10,0x10,0x10,0x10,0x10,0x10,0x1F}},
{'M', {0x11,0x1B,0x15,0x15,0x11,0x11,0x11}},
{'N', {0x11,0x11,0x19,0x15,0x13,0x11,0x11}},
{'O', {0x0E,0x11,0x11,0x11,0x11,0x11,0x0E}},
{'P', {0x1E,0x11,0x11,0x1E,0x10,0x10,0x10}},
{'Q', {0x0E,0x11,0x11,0x11,0x15,0x12,0x0D}},
{'R', {0x1E,0x11,0x11,0x1E,0x14,0x12,0x11}},
{'S', {0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E}},
{'T', {0x1F,0x04,0x04,0x04,0x04,0x04,0x04}},
{'U', {0x11,0x11,0x11,0x11,0x11,0x11,0x0E}},
{'V', {0x11,0x11,0x11,0x11,0x11,0x0A,0x04}},
{'W', {0x11,0x11,0x11,0x15,0x15,0x1B,0x11}},
{'X', {0x11,0x11,0x0A,0x04,0x0A,0x11,0x11}},
{'Y', {0x11,0x11,0x0A,0x04,0x04,0x04,0x04}},
{'Z', {0x1F,0x01,0x02,0x04,0x08,0x10,0x1F}},
{'0', {0x0E,0x13,0x15,0x15,0x19,0x11,0x0E}},
{'1', {0x04,0x0C,0x04,0x04,0x04,0x04,0x0E}},
{'2', {0x0E,0x11,0x01,0x06,0x08,0x10,0x1F}},
{'3', {0x1F,0x02,0x04,0x02,0x01,0x11,0x0E}},
{'4', {0x02,0x06,0x0A,0x12,0x1F,0x02,0x02}},
{'5', {0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E}},
{'6', {0x06,0x08,0x10,0x1E,0x11,0x11,0x0E}},
{'7', {0x1F,0x01,0x02,0x04,0x08,0x08,0x08}},
{'8', {0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E}},
{'9', {0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C}},
};
const unsigned char* getFontGlyph(char c) {
for (const auto& g : font5x7)
if (g.c == c) return g.rows;
return font5x7[0].rows; // space fallback
}
}
void PatchLCD::paint(juce::Graphics& g) {
auto b = getLocalBounds();
// Dark LCD panel
g.setColour(juce::Colour(0xff061206));
g.fillRoundedRectangle(b.toFloat(), 3.0f);
g.setColour(juce::Colour(0xff114411));
g.drawRoundedRectangle(b.toFloat(), 3.0f, 1.0f);
const int pitch = 3;
const int glyphW = 5, glyphH = 7, glyphGap = 1;
// Faint "off" LED grid
g.setColour(juce::Colour(0xff0a200a));
for (int y = pitch / 2; y < b.getHeight(); y += pitch)
for (int x = pitch / 2; x < b.getWidth(); x += pitch)
g.fillRect(static_cast<float>(x) - 0.5f, static_cast<float>(y) - 0.5f, 1.0f, 1.0f);
int y0 = (b.getHeight() - glyphH * pitch) / 2;
int cx = 5;
int cy = y0;
if (cy < 0) cy = 0;
juce::String u = text.toUpperCase();
for (auto chr : u) {
const unsigned char* g7 = getFontGlyph(chr);
for (int row = 0; row < glyphH; ++row) {
unsigned char bits = g7[row];
for (int col = 0; col < glyphW; ++col) {
if (bits & (1 << (glyphW - 1 - col))) {
float px = static_cast<float>(cx + col * pitch + pitch / 2);
float py = static_cast<float>(cy + row * pitch + pitch / 2);
g.setColour(juce::Colour(0xff1a330a));
g.fillEllipse(px - pitch * 0.5f, py - pitch * 0.5f, pitch, pitch);
g.setColour(juce::Colour(0xff33ff66));
g.fillEllipse(px - pitch * 0.4f, py - pitch * 0.4f, pitch * 0.8f, pitch * 0.8f);
}
}
}
cx += (glyphW + glyphGap) * pitch;
if (cx > b.getWidth()) break;
}
}
MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
: processorRef(p), vuMeter(p), waveformDisplay(p), spectrumAnalyzer(p), pianoRoll(p) {
@ -386,19 +480,47 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
setupParam(delayTimeKnob, delayTimeAttach, "delayTime", "TIME");
setupParam(delayFbKnob, delayFbAttach, "delayFeedback", "FBACK");
setupParam(delayMixKnob, delayMixAttach, "delayMix", "MIX");
setupCB(delayPingPongBox, delayPingPongAttach, "delayPingPong", {"Off", "On"});
setupParam(delaySpreadKnob, delaySpreadAttach, "delaySpread", "SPREAD");
setupParam(reverbSizeKnob, reverbSizeAttach, "reverbSize", "SIZE");
setupParam(reverbDampKnob, reverbDampAttach, "reverbDamping", "DAMP");
setupParam(reverbMixKnob, reverbMixAttach, "reverbMix", "MIX");
// Preset button
// Preset section: title button + dot-matrix LCD + prev/next
presetButton.setColour(juce::TextButton::buttonColourId, juce::Colour(0xff2a2a2a));
presetButton.setColour(juce::TextButton::textColourOffId, juce::Colour(0xffccaa44));
presetButton.setLookAndFeel(&comboLaf);
presetButton.onClick = [this]() { showPresetMenu(); };
addAndMakeVisible(presetButton);
addAndMakeVisible(patchLCD);
auto setupArrow = [&](juce::TextButton& btn) {
btn.setColour(juce::TextButton::buttonColourId, juce::Colour(0xff2a2a2a));
btn.setColour(juce::TextButton::textColourOffId, juce::Colour(0xffccaa44));
btn.setLookAndFeel(&comboLaf);
addAndMakeVisible(btn);
};
setupArrow(prevPresetButton);
setupArrow(nextPresetButton);
prevPresetButton.onClick = [this]() {
auto& presets = processorRef.presetManager.getPresets();
if (presets.empty()) return;
currentPresetIndex = (currentPresetIndex - 1 + static_cast<int>(presets.size())) % static_cast<int>(presets.size());
applyCurrentPreset();
};
nextPresetButton.onClick = [this]() {
auto& presets = processorRef.presetManager.getPresets();
if (presets.empty()) return;
currentPresetIndex = (currentPresetIndex + 1) % static_cast<int>(presets.size());
applyCurrentPreset();
};
auto& presets = processorRef.presetManager.getPresets();
if (!presets.empty()) {
currentPresetIndex = 0;
patchLCD.setText(presets[currentPresetIndex].name);
}
scaleLabel.setText("SCALE", juce::dontSendNotification);
scaleLabel.setFont(juce::Font(9.0f));
scaleLabel.setColour(juce::Label::textColourId, juce::Colour(0xff888888));
@ -462,29 +584,41 @@ void MainContentComponent::setupCombo(juce::ComboBox& combo) {
void MainContentComponent::showPresetMenu() {
juce::PopupMenu menu;
std::vector<juce::String> menuOrder;
auto categories = processorRef.presetManager.getCategories();
int menuId = 1;
for (auto& cat : categories) {
juce::PopupMenu subMenu;
auto presets = processorRef.presetManager.getPresetsInCategory(cat);
for (auto& preset : presets) {
subMenu.addItem(menuId++, preset.name);
menuOrder.push_back(preset.name);
subMenu.addItem(static_cast<int>(menuOrder.size()), preset.name);
}
menu.addSubMenu(cat, subMenu);
}
menu.showMenuAsync(juce::PopupMenu::Options(), [this](int result) {
if (result > 0) {
auto& presets = processorRef.presetManager.getPresets();
int idx = 0;
for (int i = 0; i < result - 1 && i < static_cast<int>(presets.size()); ++i)
++idx;
if (idx < static_cast<int>(presets.size()))
processorRef.presetManager.applyPreset(presets[idx].name, processorRef.apvts);
}
});
menu.showMenuAsync(juce::PopupMenu::Options().withTargetComponent(&presetButton),
[this, menuOrder](int result) {
if (result > 0 && result <= static_cast<int>(menuOrder.size())) {
auto name = menuOrder[static_cast<size_t>(result) - 1];
auto& presets = processorRef.presetManager.getPresets();
for (int i = 0; i < static_cast<int>(presets.size()); ++i) {
if (presets[i].name == name) {
currentPresetIndex = i;
break;
}
}
applyCurrentPreset();
}
});
}
void MainContentComponent::applyCurrentPreset() {
auto& presets = processorRef.presetManager.getPresets();
if (presets.empty()) return;
currentPresetIndex = juce::jlimit(0, static_cast<int>(presets.size()) - 1, currentPresetIndex);
processorRef.presetManager.applyPreset(presets[currentPresetIndex].name, processorRef.apvts);
patchLCD.setText(presets[currentPresetIndex].name);
}
void MainContentComponent::paint(juce::Graphics& g) {
@ -682,10 +816,6 @@ void MainContentComponent::resized() {
delayTimeKnob.setBounds(1110, fx2KnobY, lfoKnobSize, lfoKnobSize);
delayFbKnob.setBounds(1198, fx2KnobY, lfoKnobSize, lfoKnobSize);
delayMixKnob.setBounds(1286, fx2KnobY, lfoKnobSize, lfoKnobSize);
// Row 2: PINGPONG combo, SPREAD knob
int delayRow2Y = fx2KnobY + lfoKnobSize + 4;
delayPingPongBox.setBounds(1110, delayRow2Y, 100, 36);
delaySpreadKnob.setBounds(1220, delayRow2Y, lfoKnobSize, lfoKnobSize);
// Reverb sub-section (X=1375, Y=690, W=265, H=130)
reverbSizeKnob.setBounds(1383, fx2KnobY, lfoKnobSize, lfoKnobSize);
@ -693,7 +823,10 @@ void MainContentComponent::resized() {
reverbMixKnob.setBounds(1553, fx2KnobY, lfoKnobSize, lfoKnobSize);
// --- PRESET + SCALE ---
presetButton.setBounds(20, 8, 120, 28);
presetButton.setBounds(20, 8, 90, 28);
patchLCD.setBounds(118, 8, 360, 28);
prevPresetButton.setBounds(486, 8, 28, 28);
nextPresetButton.setBounds(518, 8, 28, 28);
scaleLabel.setBounds(1520, 10, 42, 20);
uiScaleBox.setBounds(1566, 8, 80, 24);