Initial commit: RMX-19 rack sound module (VST3/AU) with dot-matrix LCD UI

This commit is contained in:
Armin 2026-09-07 21:03:31 +02:00
commit c72c0216fd
16 changed files with 2170 additions and 0 deletions

576
Source/LcdPanel.cpp Normal file
View file

@ -0,0 +1,576 @@
#include "LcdPanel.h"
#include "BuildInfo.h"
// ---------------------------------------------------------------------------
// 5x7 dot-matrix font. Each glyph is 5 columns x 7 rows, bit 6 (0x40) of each
// row byte is the MSB (leftmost) dot.
// ---------------------------------------------------------------------------
namespace {
static constexpr int NW = 5, NH = 7;
struct Entry { int key; const juce::uint8* rows; };
static const juce::uint8 A_[NH] = { 0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001 };
static const juce::uint8 B_[NH] = { 0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110 };
static const juce::uint8 C_[NH] = { 0b01110, 0b10001, 0b10000, 0b10000, 0b10000, 0b10001, 0b01110 };
static const juce::uint8 D_[NH] = { 0b11100, 0b10010, 0b10001, 0b10001, 0b10001, 0b10010, 0b11100 };
static const juce::uint8 E_[NH] = { 0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111 };
static const juce::uint8 F_[NH] = { 0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000 };
static const juce::uint8 G_[NH] = { 0b01110, 0b10001, 0b10000, 0b10111, 0b10001, 0b10001, 0b01111 };
static const juce::uint8 H_[NH] = { 0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001 };
static const juce::uint8 I_[NH] = { 0b01110, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110 };
static const juce::uint8 J_[NH] = { 0b00111, 0b00010, 0b00010, 0b00010, 0b00010, 0b10010, 0b01100 };
static const juce::uint8 K_[NH] = { 0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001 };
static const juce::uint8 L_[NH] = { 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111 };
static const juce::uint8 M_[NH] = { 0b10001, 0b11011, 0b10101, 0b10101, 0b10001, 0b10001, 0b10001 };
static const juce::uint8 N_[NH] = { 0b10001, 0b11001, 0b10101, 0b10011, 0b10001, 0b10001, 0b10001 };
static const juce::uint8 O_[NH] = { 0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110 };
static const juce::uint8 P_[NH] = { 0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000 };
static const juce::uint8 Q_[NH] = { 0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101 };
static const juce::uint8 R_[NH] = { 0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001 };
static const juce::uint8 S_[NH] = { 0b01111, 0b10000, 0b10000, 0b01110, 0b00001, 0b00001, 0b11110 };
static const juce::uint8 T_[NH] = { 0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100 };
static const juce::uint8 U_[NH] = { 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110 };
static const juce::uint8 V_[NH] = { 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100 };
static const juce::uint8 W_[NH] = { 0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b11011, 0b10001 };
static const juce::uint8 X_[NH] = { 0b10001, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0b10001 };
static const juce::uint8 Y_[NH] = { 0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100, 0b00100 };
static const juce::uint8 Z_[NH] = { 0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b11111 };
static const juce::uint8 n0_[NH] = { 0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110 };
static const juce::uint8 n1_[NH] = { 0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110 };
static const juce::uint8 n2_[NH] = { 0b01110, 0b10001, 0b00001, 0b00110, 0b01000, 0b10000, 0b11111 };
static const juce::uint8 n3_[NH] = { 0b11110, 0b00001, 0b00001, 0b01110, 0b00001, 0b00001, 0b11110 };
static const juce::uint8 n4_[NH] = { 0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010 };
static const juce::uint8 n5_[NH] = { 0b11111, 0b10000, 0b10000, 0b11110, 0b00001, 0b00001, 0b11110 };
static const juce::uint8 n6_[NH] = { 0b00110, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110 };
static const juce::uint8 n7_[NH] = { 0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000 };
static const juce::uint8 n8_[NH] = { 0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110 };
static const juce::uint8 n9_[NH] = { 0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b01100 };
static const juce::uint8 sp_[NH] = { 0, 0, 0, 0, 0, 0, 0 };
static const juce::uint8 ex_[NH] = { 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00000, 0b00100 };
static const juce::uint8 dt_[NH] = { 0, 0, 0, 0, 0, 0b00100, 0b00100 };
static const juce::uint8 co_[NH] = { 0, 0, 0, 0, 0, 0b00110, 0b00110 };
static const juce::uint8 cl_[NH] = { 0, 0b00100, 0b00100, 0, 0b00100, 0b00100, 0 };
static const juce::uint8 mi_[NH] = { 0, 0, 0, 0b11111, 0, 0, 0 };
static const juce::uint8 pl_[NH] = { 0, 0b00100, 0b00100, 0b11111, 0b00100, 0b00100, 0 };
static const juce::uint8 sl_[NH] = { 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0, 0 };
static const juce::uint8 pc_[NH] = { 0b10100, 0b10100, 0b01000, 0b10100, 0b00101, 0b00101, 0b00010 };
static const juce::uint8 ap_[NH] = { 0b00100, 0b00100, 0b00100, 0, 0, 0, 0 };
static const juce::uint8 lt_[NH] = { 0b00011, 0b00110, 0b01100, 0b11000, 0b01100, 0b00110, 0b00011 };
static const juce::uint8 gt_[NH] = { 0b11000, 0b01100, 0b00110, 0b00011, 0b00110, 0b01100, 0b11000 };
static const juce::uint8 eq_[NH] = { 0, 0, 0b11111, 0, 0b11111, 0, 0 };
static const juce::uint8 hs_[NH] = { 0b01010, 0b01010, 0b11111, 0b01010, 0b11111, 0b01010, 0b01010 };
static const Entry table[] =
{
{ 'A', A_ }, { 'B', B_ }, { 'C', C_ }, { 'D', D_ }, { 'E', E_ },
{ 'F', F_ }, { 'G', G_ }, { 'H', H_ }, { 'I', I_ }, { 'J', J_ },
{ 'K', K_ }, { 'L', L_ }, { 'M', M_ }, { 'N', N_ }, { 'O', O_ },
{ 'P', P_ }, { 'Q', Q_ }, { 'R', R_ }, { 'S', S_ }, { 'T', T_ },
{ 'U', U_ }, { 'V', V_ }, { 'W', W_ }, { 'X', X_ }, { 'Y', Y_ },
{ 'Z', Z_ }, { '0', n0_ }, { '1', n1_ }, { '2', n2_ }, { '3', n3_ },
{ '4', n4_ }, { '5', n5_ }, { '6', n6_ }, { '7', n7_ }, { '8', n8_ },
{ '9', n9_ }, { ' ', sp_ }, { '.', dt_ }, { ',', co_ }, { ':', cl_ },
{ '-', mi_ }, { '+', pl_ }, { '/', sl_ }, { '%', pc_ }, { '\'', ap_ },
{ '<', lt_ }, { '>', gt_ }, { '=', eq_ }, { '#', hs_ }, { '!', ex_ },
};
} // namespace
const juce::uint8* LcdPanel::glyphFor (int key)
{
if (key >= 'a' && key <= 'z') key = key - 'a' + 'A';
for (const auto& e : table)
if (e.key == key) return e.rows;
return sp_;
}
// ---------------------------------------------------------------------------
LcdPanel::LcdPanel (RMX19AudioProcessor& p)
: proc (p)
{
dotImage = juce::Image (juce::Image::ARGB, gridW, gridH, true);
splashUntil = juce::Time::getMillisecondCounter() + 1500;
lastMidi = proc.getLastMidiTime();
startTimerHz (22);
}
LcdPanel::~LcdPanel()
{
stopTimer();
}
void LcdPanel::timerCallback()
{
float l, r; proc.getMeters (l, r);
meterL = std::max (l, meterL * 0.5f);
meterR = std::max (r, meterR * 0.5f);
if (proc.getLastMidiTime() != lastMidi)
{
lastMidi = proc.getLastMidiTime();
powerGlow = 1.0f;
}
powerGlow = std::max (0.0f, powerGlow - 0.04f);
if (screen == Screen::Splash && juce::Time::getMillisecondCounter() > splashUntil)
screen = Screen::Home;
setDirty();
}
void LcdPanel::paint (juce::Graphics& g)
{
const auto b = getLocalBounds();
// recessed bezel
g.setColour (juce::Colour (0xff0c0a08));
g.fillRect (b);
g.setColour (juce::Colour (0xff4a453c));
g.drawRect (b, 2);
// inner LCD glass (black)
const float scale = (float) b.getWidth() / (float) gridW;
const int cell = 2;
const int w = gridW * cell;
const int h = gridH * cell;
const int ox = (b.getWidth() - w) / 2;
const int oy = (b.getHeight() - h) / 2 + 2;
g.setColour (juce::Colour (0xff020703));
g.fillRect (ox, oy, w, h);
{
juce::Graphics dg (dotImage);
dg.fillAll (juce::Colours::black);
drawContent (dg);
}
for (int y = 0; y < gridH; ++y)
{
for (int x = 0; x < gridW; ++x)
{
const auto px = dotImage.getPixelAt (x, y);
const float lvl = px.getAlpha() / 255.0f;
if (lvl <= 0.0f) continue;
const juce::Colour c = juce::Colour::fromFloatRGBA (0.04f + 0.02f * powerGlow,
0.96f * lvl, 0.25f * lvl, 1.0f);
g.setColour (c);
g.fillRect (ox + x * cell, oy + y * cell, cell, cell);
}
}
(void) scale;
}
void LcdPanel::drawContent (juce::Graphics& g)
{
switch (screen)
{
case Screen::Splash: drawSplash (g); break;
case Screen::Home: drawHome (g); break;
case Screen::Menu: drawMenu (g); break;
case Screen::Patch: drawPatchList (g); break;
case Screen::Param: drawParamScreen (g); break;
case Screen::System: drawSystem (g); break;
case Screen::Info: drawInfo (g); break;
}
drawFooter (g);
}
void LcdPanel::px (juce::Graphics& g, int x, int y, float intensity)
{
if (x < 0 || y < 0 || x >= gridW || y >= gridH) return;
if (intensity <= 0.0f) return;
const int a = (int) (255.0f * juce::jlimit (0.0f, 1.0f, intensity));
g.setColour (juce::Colour::fromRGBA (255, 255, 255, a));
g.fillRect (x, y, 1, 1);
}
void LcdPanel::pxText (juce::Graphics& g, int x, int y, const juce::String& text, float intensity)
{
int cx = x;
for (int i = 0; i < text.length(); ++i)
{
const int key = text[i];
const juce::uint8* rows = glyphFor (key);
for (int row = 0; row < NH; ++row)
{
const juce::uint8 bits = rows[row];
for (int col = 0; col < NW; ++col)
if (bits & (1u << (NW - 1 - col)))
px (g, cx + col, y + row, intensity);
}
cx += NW + 1;
}
}
void LcdPanel::drawFilled (juce::Graphics& g, int x, int y, int w, int h, float intensity)
{
for (int yy = 0; yy < h; ++yy)
for (int xx = 0; xx < w; ++xx)
px (g, x + xx, y + yy, intensity);
}
void LcdPanel::drawHBar (juce::Graphics& g, int x, int y, int w, double level, float intensity)
{
const int filled = juce::roundToInt (w * juce::jlimit (0.0, 1.0, level));
if (filled > 0) drawFilled (g, x, y, filled, 1, intensity);
}
void LcdPanel::drawFooter (juce::Graphics& g)
{
juce::String branch = juce::String (BUILD_GIT_BRANCH);
if (branch.length() > 6) branch = branch.substring (0, 6);
const juce::String git = branch + "@" + juce::String (BUILD_GIT_COMMIT) + juce::String (BUILD_GIT_DIRTY);
const juce::String dt = juce::String (BUILD_DATE).substring (5) + " " + juce::String (BUILD_TIME);
drawFilled (g, 0, 61, gridW, 1, 0.4f);
pxText (g, 2, 63, git, 0.75f);
const auto w = dt.length() * (NW + 1) - 1;
pxText (g, gridW - w - 2, 63, dt, 0.75f);
(void) powerGlow;
}
void LcdPanel::drawSplash (juce::Graphics& g)
{
pxText (g, 46, 6, "* RMX-19 *", 1.0f);
pxText (g, 30, 18, "RACK SYNTHESIZER", 0.9f);
drawFilled (g, 10, 30, gridW - 20, 1, 0.5f);
pxText (g, 44, 34, "INITIALIZING", 0.8f);
pxText (g, 34, 45, "DSP READY", 0.75f);
pxText (g, 56, 56, "REV.B", 0.6f);
}
void LcdPanel::drawHome (juce::Graphics& g)
{
const int patch = proc.getPatchIndex();
const auto& p = proc.getBank().get (patch);
const juce::String num = juce::String (patch + 1).paddedLeft ('0', 2);
pxText (g, 2, 3, num, 1.0f);
pxText (g, 4, 15, p.name, 1.0f);
pxText (g, 4, 28, p.category, 0.8f);
drawFilled (g, 4, 42, 88, 1, 0.5f);
drawHBar (g, 4, 43, 88, proc.getActiveVoices() / 16.0, 0.9f);
pxText (g, 116, 42, "L", 0.6f);
pxText (g, 138, 42, "R", 0.6f);
drawFilled (g, 122, 40, 1, 8, 0.5f);
drawHBar (g, 123, 41, 12, meterL, 1.0f);
drawHBar (g, 123, 44, 12, meterR, 1.0f);
drawFilled (g, 123, 40, 12, 1, 0.4f);
drawFilled (g, 123, 47, 12, 1, 0.4f);
}
void LcdPanel::drawMenu (juce::Graphics& g)
{
static const char* items[] = {
"PATCH SELECT", "OSC SELECT", "FILTER SETUP", "AMP ENVELOPE",
"FILT ENVELOPE", "MODULATION", "EFFECTS", "MASTER", "SYSTEM", "ABOUT"
};
const int rows = (int) juce::numElementsInArray (items);
const int visible = 6;
const int top = juce::jmax (0, juce::jmin (cursor - 3, rows - visible));
for (int i = 0; i < visible; ++i)
{
const int idx = top + i;
if (idx >= rows) break;
const int y = 2 + i * 10 + 1;
const bool sel = (idx == cursor);
pxText (g, 4, y, items[idx], sel ? 1.0f : 0.6f);
if (sel)
{
drawFilled (g, 1, y, 1, NH, 1.0f);
pxText (g, 142, y, ">", 1.0f);
}
}
drawFilled (g, 151, 8, 1, 48, 0.4f);
const double frac = rows > 1 ? cursor / (double) (rows - 1) : 0.0;
const int thumbY = 8 + (int) (frac * 40.0);
drawFilled (g, 150, thumbY - 2, 3, 4, 0.8f);
pxText (g, 4, 62, "UP/DN MOVE OK OPEN", 0.55f);
}
void LcdPanel::drawPatchList (juce::Graphics& g)
{
const int bankSize = proc.getBank().size();
const int visible = 6;
const int top = juce::jmax (0, juce::jmin (patchCursor - 2, bankSize - visible));
for (int i = 0; i < visible; ++i)
{
const int idx = top + i;
if (idx >= bankSize) break;
const auto& p = proc.getBank().get (idx);
const int y = 2 + i * 10 + 1;
const bool sel = (idx == patchCursor);
pxText (g, 4, y, juce::String (idx + 1).paddedLeft ('0', 2) + " " + juce::String (p.name),
sel ? 1.0f : 0.6f);
if (sel) drawFilled (g, 1, y, 1, NH, 1.0f);
if (idx == proc.getPatchIndex()) pxText (g, 138, y, "*", 0.9f);
}
drawFilled (g, 140, 8, 1, 48, 0.4f);
const double frac = bankSize > 1 ? patchCursor / (double) (bankSize - 1) : 0.0;
const int thumbY = 8 + (int) (frac * 40.0);
drawFilled (g, 139, thumbY - 2, 3, 4, 0.8f);
pxText (g, 4, 62, "UP/DN BROWSE OK LOAD", 0.55f);
}
void LcdPanel::drawParamScreen (juce::Graphics& g)
{
pxText (g, 2, 2, paramScreenTitle (paramScreenIdx), 1.0f);
drawFilled (g, 2, 9, gridW - 4, 1, 0.35f);
static const prm::Id voice[] = { prm::Id::osc1Wave, prm::Id::osc2Wave, prm::Id::detune, prm::Id::oscMix };
static const prm::Id filter[] = { prm::Id::fltType, prm::Id::fltCutoff, prm::Id::fltRes, prm::Id::fltEnv, prm::Id::fltKey };
static const prm::Id ampEnv[] = { prm::Id::ampA, prm::Id::ampD, prm::Id::ampS, prm::Id::ampR };
static const prm::Id fltEnv[] = { prm::Id::fenvA, prm::Id::fenvD, prm::Id::fenvS, prm::Id::fenvR };
static const prm::Id mod[] = { prm::Id::lfoRate, prm::Id::lfoShape, prm::Id::lfoDepth, prm::Id::lfoTarget };
static const prm::Id fx[] = { prm::Id::fxType, prm::Id::fxAmt, prm::Id::fxTime };
static const prm::Id master[] = { prm::Id::vol, prm::Id::pan, prm::Id::poly, prm::Id::glide, prm::Id::tune };
const prm::Id* rows = nullptr;
int count = 0;
switch (paramScreenIdx)
{
case 0: rows = voice; count = (int) juce::numElementsInArray (voice); break;
case 1: rows = filter; count = (int) juce::numElementsInArray (filter); break;
case 2: rows = ampEnv; count = (int) juce::numElementsInArray (ampEnv); break;
case 3: rows = fltEnv; count = (int) juce::numElementsInArray (fltEnv); break;
case 4: rows = mod; count = (int) juce::numElementsInArray (mod); break;
case 5: rows = fx; count = (int) juce::numElementsInArray (fx); break;
case 6: rows = master; count = (int) juce::numElementsInArray (master); break;
default: return;
}
const int visible = 5;
const int top = juce::jmax (0, juce::jmin (cursor - 2, count - visible));
for (int i = 0; i < visible; ++i)
{
const int ri = top + i;
if (ri >= count) break;
const prm::Id id = rows[ri];
const auto& d = prm::desc (id);
const float cur = proc.apvts.getRawParameterValue (d.id)->load();
const auto* par = proc.apvts.getParameter (d.id);
const float v01 = par->convertTo0to1 (cur);
const int y = 12 + i * 10;
const bool sel = (ri == cursor);
pxText (g, 3, y, d.shortName, 0.55f);
pxText (g, 22, y, prm::formatParam (id, cur), sel ? 1.0f : 0.7f);
drawFilled (g, 22, y + 6, 130, 1, 0.15f);
drawHBar (g, 22, y + 6, 130, v01, sel ? 1.0f : 0.5f);
if (sel)
{
drawFilled (g, 0, y, 1, NH, 1.0f);
pxText (g, 148, y, "<>", 1.0f);
}
}
pxText (g, 4, 62, "LEFT/RIGHT EDIT OK BACK", 0.55f);
}
void LcdPanel::drawSystem (juce::Graphics& g)
{
static const char* items[] = { "FACTORY INIT", "ALL NOTES OFF", "MIDI TEST", "BACK" };
for (int i = 0; i < 4; ++i)
{
const int y = 2 + i * 10 + 1;
const bool sel = (i == cursor);
pxText (g, 4, y, items[i], sel ? 1.0f : 0.6f);
if (sel) drawFilled (g, 1, y, 1, NH, 1.0f);
}
if (cursor == 2 && powerGlow > 0.5f)
pxText (g, 4, 50, "MIDI RX " + juce::String (lastMidi ? "ACTIVE" : "IDLE"), 0.6f);
}
void LcdPanel::drawInfo (juce::Graphics& g)
{
pxText (g, 2, 3, "RMX-19 RACK SYNTH", 0.9f);
pxText (g, 2, 15, "GIT " + juce::String (BUILD_GIT_BRANCH) + " " + BUILD_GIT_COMMIT + BUILD_GIT_DIRTY, 0.85f);
pxText (g, 2, 27, "BUILT " + juce::String (BUILD_DATE) + " " + BUILD_TIME, 0.85f);
pxText (g, 2, 39, "ARM64 JUCE VERSION " + juce::String (JUCE_MAJOR_VERSION) + "." + juce::String (JUCE_MINOR_VERSION), 0.7f);
pxText (g, 2, 51, "MIDI NOTE + CC CONTROL", 0.7f);
}
// ---- editing helpers ------------------------------------------------------
int LcdPanel::rowCountForScreen() const
{
switch (screen)
{
case Screen::Menu: return 10;
case Screen::System: return 4;
default: return 0;
}
}
int LcdPanel::paramCountForScreen (int idx) const
{
switch (idx)
{
case 0: return 4;
case 1: return 5;
case 2: return 4;
case 3: return 4;
case 4: return 4;
case 5: return 3;
case 6: return 5;
default: return 0;
}
}
const char* LcdPanel::paramScreenTitle (int idx) const
{
switch (idx)
{
case 0: return "OSCILLATOR";
case 1: return "FILTER";
case 2: return "AMP ENVELOPE";
case 3: return "FILT ENVELOPE";
case 4: return "MODULATION";
case 5: return "EFFECTS";
case 6: return "MASTER";
default: return "";
}
}
void LcdPanel::openParamScreen (int idx)
{
paramScreenIdx = juce::jlimit (0, 6, idx);
cursor = 0;
enterScreen (Screen::Param);
}
void LcdPanel::moveCursor (int d)
{
if (screen == Screen::Patch)
{
patchCursor = juce::jlimit (0, proc.getBank().size() - 1, patchCursor + d);
setDirty();
}
else if (screen == Screen::Param)
{
cursor = juce::jlimit (0, paramCountForScreen (paramScreenIdx) - 1, cursor + d);
setDirty();
}
else if (rowCountForScreen() > 0)
{
cursor = juce::jlimit (0, rowCountForScreen() - 1, cursor + d);
setDirty();
}
}
void LcdPanel::stepParam (prm::Id id, int dir, bool coarse)
{
auto* par = dynamic_cast<juce::AudioParameterFloat*> (proc.apvts.getParameter (prm::desc (id).id));
if (par == nullptr) return;
const float cur = proc.apvts.getRawParameterValue (prm::desc (id).id)->load();
const float nv = prm::changeParam (id, cur, dir, coarse);
if (nv != cur)
par->setValueNotifyingHost (par->convertTo0to1 (nv));
setDirty();
}
static prm::Id paramForCursor (int screenIdx, int row)
{
static const prm::Id voice[] = { prm::Id::osc1Wave, prm::Id::osc2Wave, prm::Id::detune, prm::Id::oscMix };
static const prm::Id filter[] = { prm::Id::fltType, prm::Id::fltCutoff, prm::Id::fltRes, prm::Id::fltEnv, prm::Id::fltKey };
static const prm::Id ampEnv[] = { prm::Id::ampA, prm::Id::ampD, prm::Id::ampS, prm::Id::ampR };
static const prm::Id fltEnv[] = { prm::Id::fenvA, prm::Id::fenvD, prm::Id::fenvS, prm::Id::fenvR };
static const prm::Id mod[] = { prm::Id::lfoRate, prm::Id::lfoShape, prm::Id::lfoDepth, prm::Id::lfoTarget };
static const prm::Id fx[] = { prm::Id::fxType, prm::Id::fxAmt, prm::Id::fxTime };
static const prm::Id master[] = { prm::Id::vol, prm::Id::pan, prm::Id::poly, prm::Id::glide, prm::Id::tune };
switch (screenIdx)
{
case 0: return voice[row];
case 1: return filter[row];
case 2: return ampEnv[row];
case 3: return fltEnv[row];
case 4: return mod[row];
case 5: return fx[row];
case 6: return master[row];
default: return prm::Id::vol;
}
}
void LcdPanel::adjustCurrent (int dir, bool coarse)
{
if (screen == Screen::Param)
stepParam (paramForCursor (paramScreenIdx, cursor), dir, coarse);
}
void LcdPanel::navUp() { moveCursor (-1); }
void LcdPanel::navDown() { moveCursor (1); }
void LcdPanel::navLeft() { adjustCurrent (-1, false); }
void LcdPanel::navRight() { adjustCurrent (1, false); }
void LcdPanel::navEnter()
{
switch (screen)
{
case Screen::Splash: enterScreen (Screen::Home); break;
case Screen::Home: enterScreen (Screen::Menu); break;
case Screen::Menu:
switch (cursor)
{
case 0: enterScreen (Screen::Patch); break;
case 1: openParamScreen (0); break;
case 2: openParamScreen (1); break;
case 3: openParamScreen (2); break;
case 4: openParamScreen (3); break;
case 5: openParamScreen (4); break;
case 6: openParamScreen (5); break;
case 7: openParamScreen (6); break;
case 8: enterScreen (Screen::System); break;
case 9: enterScreen (Screen::Info); break;
default: break;
}
break;
case Screen::Patch:
proc.selectPatch (patchCursor);
enterScreen (Screen::Home);
break;
case Screen::System:
switch (cursor)
{
case 0: proc.getBank().writeTo (0, proc.apvts); proc.selectPatch (0); break;
case 1: proc.getEngine().allNotesOff (true); break;
case 2: break;
case 3: enterScreen (Screen::Home); break;
default: break;
}
break;
case Screen::Info:
case Screen::Param:
default:
enterScreen (Screen::Home);
break;
}
}
void LcdPanel::navExit()
{
if (screen == Screen::Menu)
enterScreen (Screen::Home);
else if (screen != Screen::Home)
enterScreen (Screen::Menu);
}
bool LcdPanel::keyTrap (const juce::KeyPress& k)
{
if (k.isKeyCode (juce::KeyPress::upKey)) { navUp(); return true; }
if (k.isKeyCode (juce::KeyPress::downKey)) { navDown(); return true; }
if (k.isKeyCode (juce::KeyPress::leftKey)) { navLeft(); return true; }
if (k.isKeyCode (juce::KeyPress::rightKey)) { navRight(); return true; }
if (k.isKeyCode (juce::KeyPress::returnKey) || k.getTextCharacter() == ' ')
{ navEnter(); return true; }
if (k.isKeyCode (juce::KeyPress::escapeKey))
{ navExit(); return true; }
return false;
}