Persist engine state in DAW projects, black monochrome panel, expand preset bank

- Save/load the engine's full parameter set alongside the preset index so
  knob and menu tweaks survive relaunching a song project
- Make the plugin panel black: top/surround laser border and bottom deck
  (rotary + nav keys) are now #000000
- Extend factory bank to 320 categorized presets and add LCD click-to-open
  preset category browser
This commit is contained in:
Armin 2026-09-13 22:04:43 +02:00
commit bdd3b38cee
7 changed files with 943 additions and 56 deletions

View file

@ -15,6 +15,7 @@ SM26AudioProcessorEditor::SM26AudioProcessorEditor (SM26AudioProcessor& p)
setResizeLimits (760, 620, 1500, 1300);
addAndMakeVisible (lcd);
lcd.onPressed = [this](int) { if (! menu.isOpen()) openPresetMenu(); };
setupKnobs();
setupNavButtons();
@ -85,12 +86,12 @@ void SM26AudioProcessorEditor::setupNavButtons()
addAndMakeVisible (btn);
};
setup (navUp, juce::Colour (0xFF2A2A2A));
setup (navDown, juce::Colour (0xFF2A2A2A));
setup (navLeft, juce::Colour (0xFF2A2A2A));
setup (navRight, juce::Colour (0xFF2A2A2A));
setup (navEnter, juce::Colour (0xFF3F3F3F));
setup (navEsc, juce::Colour (0xFF333333));
setup (navUp, juce::Colour (0xFF000000));
setup (navDown, juce::Colour (0xFF000000));
setup (navLeft, juce::Colour (0xFF000000));
setup (navRight, juce::Colour (0xFF000000));
setup (navEnter, juce::Colour (0xFF000000));
setup (navEsc, juce::Colour (0xFF000000));
navUp.setTriggeredOnMouseDown (true);
navUp.setRepeatSpeed (250, 50);
@ -120,17 +121,6 @@ void SM26AudioProcessorEditor::buildRootMenu()
s.title = "SM-26 MAIN MENU";
s.selected = 0;
auto addPreset = [&](const char* name, int idx) -> MenuItem
{
MenuItem item;
item.label = name;
item.kind = MenuItem::Action;
item.onEnter = [this, idx]() {
proc.setPresetByIndex (idx);
};
return item;
};
auto addValue = [&](const char* n, int pid) -> MenuItem
{
MenuItem mi;
@ -167,27 +157,7 @@ void SM26AudioProcessorEditor::buildRootMenu()
MenuItem m;
m.label = "PRESETS (" + juce::String (sm26::PresetStore::count()) + ")";
m.kind = MenuItem::Submenu;
m.onEnter = [this, addPreset]() {
MenuScreen ps;
ps.title = "SELECT PRESET";
ps.selected = proc.engine.presetIndex();
juce::String lastCat;
const int count = sm26::PresetStore::count();
for (int i = 0; i < count; ++i)
{
const auto* p = sm26::PresetStore::get (i);
if (lastCat != p->category)
{
MenuItem h;
h.label = "---- " + juce::String (p->category) + " ----";
h.kind = MenuItem::Separator;
ps.items.push_back (h);
lastCat = p->category;
}
ps.items.push_back (addPreset (p->name, i));
}
menu.pushScreen (ps);
};
m.onEnter = [this]() { openPresetMenu(); };
s.items.push_back (m);
}
@ -277,6 +247,64 @@ void SM26AudioProcessorEditor::buildRootMenu()
menu.setRootScreenBuilder (builder);
}
void SM26AudioProcessorEditor::openPresetMenu()
{
struct Cat { juce::String name; int first; int count; };
std::vector<Cat> cats;
const int total = sm26::PresetStore::count();
for (int i = 0; i < total; ++i)
{
const auto* p = sm26::PresetStore::get (i);
if (cats.empty() || cats.back().name != p->category)
cats.push_back ({ juce::String (p->category), i, 0 });
cats.back().count++;
}
MenuScreen s;
s.title = "SELECT PATCH CATEGORY";
s.selected = 0;
const juce::String curCat = sm26::PresetStore::findByIndex (proc.engine.presetIndex())->category;
for (int c = 0; c < (int) cats.size(); ++c)
{
if (cats[(size_t) c].name == curCat)
s.selected = c;
MenuItem mi;
mi.label = cats[(size_t) c].name + " (" + juce::String (cats[(size_t) c].count) + ")";
mi.kind = MenuItem::Submenu;
mi.onEnter = [this, cats, c]() {
const Cat& cat = cats[(size_t) c];
MenuScreen catScreen;
catScreen.title = "PATCHES: " + cat.name;
catScreen.selected = 0;
const juce::String curName = sm26::PresetStore::findByIndex (proc.engine.presetIndex())->name;
for (int i = cat.first; i < cat.first + cat.count; ++i)
{
const auto* p = sm26::PresetStore::get (i);
if (juce::String (p->name) == curName)
catScreen.selected = i - cat.first;
MenuItem pi;
pi.label = p->name;
pi.kind = MenuItem::Action;
pi.onEnter = [this, i]() {
proc.setPresetByIndex (i);
menu.close();
};
catScreen.items.push_back (pi);
}
menu.pushScreen (catScreen);
};
s.items.push_back (mi);
}
if (! menu.isOpen())
menu.open();
menu.pushScreen (s);
}
void SM26AudioProcessorEditor::paint (juce::Graphics& g)
{
g.fillAll (juce::Colour (0xFF000000));
@ -284,12 +312,17 @@ void SM26AudioProcessorEditor::paint (juce::Graphics& g)
const float totalW = (float) getWidth();
const float totalH = (float) getHeight();
g.setColour (juce::Colour (0xFF1A1A1A));
g.setColour (juce::Colour (0xFF000000));
g.fillRoundedRectangle (8.0f, 8.0f, totalW - 16.0f, totalH - 16.0f, 6.0f);
g.setColour (juce::Colour (0xFF333333));
g.setColour (juce::Colour (0xFF000000));
g.drawRoundedRectangle (8.0f, 8.0f, totalW - 16.0f, totalH - 16.0f, 6.0f, 1.5f);
const float lcdBottom = 16.0f + 302.0f;
g.setColour (juce::Colour (0xFF000000));
g.fillRoundedRectangle (8.0f, lcdBottom, totalW - 16.0f,
juce::jmax (0.0f, totalH - 16.0f - lcdBottom), 6.0f);
g.setColour (juce::Colour (0xFFC8C8C8));
g.setFont (juce::Font (juce::FontOptions { juce::Font::getDefaultMonospacedFontName(), 14.0f, juce::Font::bold }));
g.drawText ("SM-26", 20.0f, 16.0f, 100.0f, 22.0f, juce::Justification::centredLeft);
@ -417,7 +450,7 @@ void SM26AudioProcessorEditor::updateLCD()
juce::String notesStr = notes > 0 ? "NOTES:" + juce::String (notes) : "";
lcd.setLine (6, bpmStr + (notesStr.isNotEmpty() ? " " + notesStr : ""));
lcd.setLine (7, juce::String ("SM-26") + " UP/DN=PATCH OK=MENU");
lcd.setLine (7, juce::String ("SM-26") + " UP/DN=PATCH CLICK=BROWSE OK=MENU");
for (int r = 0; r < LcdDisplay::kRows; ++r)
lcd.setInverted (r, false);
lcd.showCursor (false);