diff --git a/.gitignore b/.gitignore index bbbb742..8545fa1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ build/ *.vst3/ *.component/ .DS_Store +*.kra +*autosave* +*.swp + diff --git a/Source/LcdPanel.cpp b/Source/LcdPanel.cpp index 5f7a721..cc95601 100644 --- a/Source/LcdPanel.cpp +++ b/Source/LcdPanel.cpp @@ -1,6 +1,7 @@ #include "LcdPanel.h" #include "BuildInfo.h" #include +#include // --------------------------------------------------------------------------- // 5x7 dot-matrix font. Each glyph is 5 columns x 7 rows, bit 6 (0x40) of each @@ -180,9 +181,9 @@ const juce::uint8* LcdPanel::glyphFor (int key) LcdPanel::LcdPanel (RMX19AudioProcessor& p) : proc (p) { - dotImage = juce::Image (juce::Image::ARGB, gridW, gridH, false); - displayImage = juce::Image (juce::Image::ARGB, gridW * 2, gridH * 2, false); - backdrop = juce::Image (juce::Image::ARGB, gridW * 2, gridH * 2, false); + displayImage = juce::Image (juce::Image::ARGB, gridW * subsample, gridH * subsample, false); + presentImage = juce::Image (juce::Image::ARGB, gridW * presentScale, gridH * presentScale, false); + backdrop = juce::Image (juce::Image::ARGB, gridW * subsample, gridH * subsample, false); buildBackdrop(); splashUntil = juce::Time::getMillisecondCounter() + 1500; lastMidi = proc.getLastMidiTime(); @@ -216,12 +217,8 @@ void LcdPanel::timerCallback() if (screen == Screen::Home) { - const int v = proc.getActiveVoices(); - if (v != lastVoices) - { - lastVoices = v; - needsRedraw = true; - } + lastVoices = proc.getActiveVoices(); + needsRedraw = true; // drive the MIDI blip + VU meter } if (needsRedraw) @@ -239,13 +236,9 @@ void LcdPanel::paint (juce::Graphics& g) g.drawRect (b, 2); // inner LCD glass (black) - 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; + const auto rect = lcdImageRect(); g.setColour (juce::Colour (0xff020703)); - g.fillRect (ox, oy, w, h); + g.fillRect (rect); if (contentDirty) { @@ -253,7 +246,56 @@ void LcdPanel::paint (juce::Graphics& g) contentDirty = false; } - g.drawImageAt (displayImage, ox, oy); + g.drawImage (presentImage, + rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight(), + 0, 0, presentImage.getWidth(), presentImage.getHeight()); +} + +// the on-screen rectangle (component coords) the dot-matrix image occupies: +// exactly the presentation pixels, centered inside the LCD module (with a small +// extra downward shift to match the bezel). No scaling is applied so the dot +// matrix keeps its intended physical size. +juce::Rectangle LcdPanel::lcdImageRect() const +{ + const int w = presentImage.getWidth(); + const int h = presentImage.getHeight(); + const auto b = getLocalBounds(); + return { (b.getWidth() - w) / 2, (b.getHeight() - h) / 2 + 2, w, h }; +} + +void LcdPanel::mouseDown (const juce::MouseEvent& e) +{ + if (screen != Screen::Home) + return; + + // map the click into logical dot-matrix coordinates + const auto rect = lcdImageRect(); + const float lx = (e.x - rect.getX()) * (float) gridW / (float) rect.getWidth(); + const float ly = (e.y - rect.getY()) * (float) gridH / (float) rect.getHeight(); + + // patch number/name row on the home screen (5x7 font at y=3), shown as ": " + const auto& cur = proc.getBank().get (proc.getPatchIndex()); + const int nameEnd = 2 + (4 + (int) juce::String (cur.name).length()) * (NW + 1) + 2; + if (ly < 0.0f || ly >= 11.0f) return; + if (lx < 0.0f || lx >= (float) nameEnd) return; + + // native mouse menu: one submenu per category, ticking the loaded patch + juce::PopupMenu menu; + const auto& cats = proc.getBank().categories(); + for (size_t c = 0; c < cats.names.size(); ++c) + { + juce::PopupMenu sub; + for (int pi : cats.patchIndices[(size_t) c]) + { + const bool isCurrent = (pi == proc.getPatchIndex()); + sub.addItem (proc.getBank().get (pi).name, true, isCurrent, + [this, pi] { proc.selectPatch (pi); setDirty(); }); + } + menu.addSubMenu (cats.names[(size_t) c], sub); + } + + menu.showMenuAsync (juce::PopupMenu::Options().withTargetScreenArea ( + juce::Rectangle (e.getScreenX(), e.getScreenY(), 1, 1))); } void LcdPanel::buildBackdrop() @@ -268,8 +310,9 @@ void LcdPanel::buildBackdrop() // even, faint backlight (brightest toward the middle of the glass) { const float cx = W * 0.5f, cy = H * 0.5f; - juce::ColourGradient backlight (juce::Colour::fromRGBA (0x28, 0x5a, 0x3c, 22), cx, cy, - juce::Colour::fromRGBA (0x18, 0x2e, 0x20, 0), W * 0.38f, cy, false); + const float radius = std::min (W, H) * 2.5f; + juce::ColourGradient backlight (juce::Colour::fromRGBA (0x40, 0x80, 0x50, 40), cx, cy, + juce::Colour::fromRGBA (0x20, 0x40, 0x28, 0), cx + radius, cy, true); dg.setGradientFill (backlight); dg.fillRect (backdrop.getBounds()); } @@ -301,31 +344,25 @@ void LcdPanel::buildBackdrop() dg.setGradientFill (eR); dg.fillRect (W - 10, 0, 10, H); } - // faint "ghost" dot for every (unlit) segment, exactly like a passive matrix LCD + // faint "ghost" dot for every (unlit) segment, exactly like a passive matrix LCD. + // Alpha is boosted since the 2:1 downsample to the presentation image halves it. for (int y = 0; y < gridH; ++y) for (int x = 0; x < gridW; ++x) { - const float cx = x * 2.0f + 1.0f; - const float cy = y * 2.0f + 1.0f; - dg.setColour (juce::Colour::fromRGBA (0x00, 0x88, 0x55, 6)); - dg.fillEllipse (cx - 0.55f, cy - 0.55f, 1.1f, 1.1f); + const float gx = (x * (float) subsample) + subsample * 0.5f; + const float gy = (y * (float) subsample) + subsample * 0.5f; + const float r = subsample * 0.32f; + dg.setColour (juce::Colour::fromRGBA (0x00, 0x88, 0x55, 14)); + dg.fillEllipse (gx - r, gy - r, r * 2.0f, r * 2.0f); } } void LcdPanel::rebuild() { - // Image::clear() hard-zeros pixels; Graphics::fillAll(transparent black) only blends - // and leaves stale content behind. - dotImage.clear (dotImage.getBounds()); - { - juce::Graphics dg (dotImage); - drawContent (dg); - } - const int W = displayImage.getWidth(); const int H = displayImage.getHeight(); - // start from the pre-rendered base layer (backlight, sheen, vignette, ghost dots) + // start from the pre-rendered 3D glass base (backlight, sheen, vignette, ghost dots) { juce::Image::BitmapData src (backdrop, juce::Image::BitmapData::readOnly); juce::Image::BitmapData dst (displayImage, juce::Image::BitmapData::writeOnly); @@ -333,63 +370,41 @@ void LcdPanel::rebuild() std::memcpy (dst.getLinePointer (y), src.getLinePointer (y), (size_t) W * 4u); } - // blend the lit segments in as crisp round dots with just a hint of bloom. - // The core is near-solid so glyphs stay sharp; the glow is faint and tight - // so it can't wash the dark gaps between rows/columns. - constexpr float coreR2 = 0.36f; // bright core radius ~0.60 cell - constexpr float glowR2 = 1.10f; // faint bloom radius ~1.05 cells + // paint the content dots directly onto the glass base; px() stamps a rounded, + // anti-aliased dot for every lit segment { - juce::Image::BitmapData cells (dotImage, juce::Image::BitmapData::readOnly); - juce::Image::BitmapData out (displayImage, juce::Image::BitmapData::readWrite); + juce::Graphics dg (displayImage); + drawContent (dg); + } - for (int cy = 0; cy < gridH; ++cy) - { - for (int cx = 0; cx < gridW; ++cx) + // exact integer box downsample (2:1) to the presentation resolution. Because + // each dot is still drawn large enough to touch its neighbours only at the + // final 2px/dot image, text reads as crisp, distinct dots. + const int PW = presentImage.getWidth(), PH = presentImage.getHeight(); + const int kx = W / PW, ky = H / PH, n = kx * ky; + { + juce::Image::BitmapData src (displayImage, juce::Image::BitmapData::readOnly); + juce::Image::BitmapData dst (presentImage, juce::Image::BitmapData::writeOnly); + for (int py = 0; py < PH; ++py) + for (int px = 0; px < PW; ++px) { - const float lvl = cells.getPixelColour (cx, cy).getAlpha() / 255.0f; - if (lvl <= 0.02f) continue; - - const float ccx = cx * 2.0f + 1.0f; - const float ccy = cy * 2.0f + 1.0f; - const int x0 = cx * 2 - 1, x1 = cx * 2 + 3; - const int y0 = cy * 2 - 1, y1 = cy * 2 + 3; - - for (int py = y0; py <= y1; ++py) + unsigned r = 0, g = 0, b = 0, a = 0; + for (int yy = 0; yy < ky; ++yy) { - if (py < 0 || py >= H) continue; - for (int px = x0; px <= x1; ++px) + const juce::PixelARGB* srcRow = + reinterpret_cast (src.getPixelPointer (px * kx, py * ky + yy)); + for (int xx = 0; xx < kx; ++xx) { - if (px < 0 || px >= W) continue; - - const float dx = (px - ccx) * 0.5f; - const float dy = (py - ccy) * 0.5f; - const float d2 = dx * dx + dy * dy; - if (d2 > glowR2) continue; - - float a; - juce::uint8 r, g, b; - if (d2 <= coreR2) - { - a = (0.85f + 0.15f * (1.0f - d2 / coreR2)) * lvl; - r = 0xdf; g = 0xff; b = 0xe6; - } - else - { - a = (1.0f - (d2 - coreR2) / (glowR2 - coreR2)) * 0.16f * lvl; - r = 0x90; g = 0xff; b = 0xa8; - } - if (a <= 0.002f) continue; - - const juce::Colour base = out.getPixelColour (px, py); - const float ia = 1.0f - a; - out.setPixelColour (px, py, juce::Colour ( - (juce::uint8) juce::jmin (255.0f, (float) base.getRed() * ia + r * a), - (juce::uint8) juce::jmin (255.0f, (float) base.getGreen() * ia + g * a), - (juce::uint8) juce::jmin (255.0f, (float) base.getBlue() * ia + b * a))); + const juce::PixelARGB p = srcRow[xx]; + r += p.getRed(); g += p.getGreen(); + b += p.getBlue(); a += p.getAlpha(); } } + juce::PixelARGB* dstRow = + reinterpret_cast (dst.getPixelPointer (px, py)); + *dstRow = juce::PixelARGB ((juce::uint8) (a / n), (juce::uint8) (r / n), + (juce::uint8) (g / n), (juce::uint8) (b / n)); } - } } } @@ -397,23 +412,31 @@ 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; + case Screen::Splash: drawSplash (g); break; + case Screen::Home: drawHome (g); break; + case Screen::Menu: drawMenu (g); break; + case Screen::Patches: drawPatches (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; + case Screen::Category: drawCategory (g); break; // legacy } } +// Stamp one lit segment as a solid full-cell dot at subsample resolution. +// Drawing the whole 4x4 subsample cell solid (instead of an anti-aliased circle) +// makes the 2:1 box-downsample produce a perfectly sharp 2px/dot block at full +// brightness. An AA circle here lands centred exactly on the corner of the four +// output pixels and gets smeared to ~half brightness — that read as soft text. 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); + const float s = (float) subsample; + g.setColour (juce::Colour::fromRGBA ( + 0xa0, 0xff, 0xc0, (juce::uint8) (255.0f * juce::jlimit (0.0f, 1.0f, intensity)))); + g.fillRect (x * s, y * s, s, s); } void LcdPanel::pxText (juce::Graphics& g, int x, int y, const juce::String& text, float intensity) @@ -500,18 +523,46 @@ void LcdPanel::drawHome (juce::Graphics& g) 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, 14, 3, p.name, 1.0f); + pxText (g, 2, 3, num + ": " + juce::String (p.name), 1.0f); pxTextM (g, 4, 17, p.category, 0.9f); drawFilled (g, 4, 28, 88, 1, 0.5f); drawHBar (g, 4, 29, 88, proc.getActiveVoices() / 16.0, 0.9f); + + // arp indicator on home + if (auto* arpPar = proc.apvts.getRawParameterValue ("arpon")) + { + if (arpPar->load() >= 0.5f) + { + const int rateIdx = (int) proc.apvts.getRawParameterValue ("arprate")->load(); + const int patIdx = (int) proc.apvts.getRawParameterValue ("arppattern")->load(); + const juce::String arpTxt = "ARP " + juce::String (prm::arpRateName (rateIdx)) + + " " + juce::String (prm::arpPatternName (patIdx)); + pxTextM (g, 96, 17, arpTxt, 0.85f); + } + } + + // bottom status strip: MIDI input blip + output VU meter + pxTextM (g, 4, 58, "MIDI IN", 0.6f); + drawFilled (g, 35, 60, 2, 3, juce::jmax (0.15f, powerGlow)); + + pxTextM (g, 50, 58, "OUT", 0.6f); + const int vw = 84; + const int vx = 68; + const int filled = juce::roundToInt (vw * juce::jlimit (0.0f, 1.0f, proc.getOutputLevel())); + if (filled > 0) + { + const int warm = juce::jmin (filled, vw / 2); + drawFilled (g, vx, 60, warm, 2, 0.65f); + drawFilled (g, vx + warm, 60, filled - warm, 2, 1.0f); + drawFilled (g, vx + filled - 1, 62, 1, 1, 0.4f); // leading-edge tick + } } void LcdPanel::drawMenu (juce::Graphics& g) { static const char* items[] = { - "PATCH", "VOICE", "FILTER", "ENVELOPE", "LFO", "FX", "MASTER", "ABOUT" + "PATCHES", "VOICE", "FILTER", "ENVELOPE", "LFO", "FX", "ARP", "MASTER", "ABOUT" }; const int rows = (int) juce::numElementsInArray (items); for (int i = 0; i < rows; ++i) @@ -523,22 +574,67 @@ void LcdPanel::drawMenu (juce::Graphics& g) } } -void LcdPanel::drawPatchList (juce::Graphics& g) +void LcdPanel::drawCategory (juce::Graphics& g) { - const int bankSize = proc.getBank().size(); + const auto& cats = proc.getBank().categories(); + const int count = (int) cats.names.size() + 1; // +1 = SETTINGS row const int visible = 8; - const int top = juce::jmax (0, juce::jmin (patchCursor - 3, bankSize - visible)); + const int top = juce::jmax (0, juce::jmin (catCursor - 3, count - visible)); for (int i = 0; i < visible; ++i) { const int idx = top + i; - if (idx >= bankSize) break; - const auto& p = proc.getBank().get (idx); + if (idx >= count) break; + const int y = 2 + i * 7; + const bool sel = (idx == catCursor); + if (sel) drawSelBar (g, y - 1); + if (idx == (int) cats.names.size()) + { + pxTextM (g, 6, y, "SETTINGS", sel ? 1.0f : 0.72f); + drawFilled (g, 6, y - 1, gridW - 12, 1, 0.30f); + continue; + } + pxTextM (g, 6, y, cats.names[(size_t) idx], sel ? 1.0f : 0.72f); + const int pcount = (int) cats.patchIndices[(size_t) idx].size(); + pxTextM (g, gridW - 16, y, juce::String (pcount), sel ? 0.85f : 0.55f); + } +} + +void LcdPanel::drawPatches (juce::Graphics& g) +{ + const auto& cats = proc.getBank().categories(); + const int count = (int) cats.names.size(); + const int visible = 8; + const int top = juce::jmax (0, juce::jmin (catCursor - 3, count - visible)); + for (int i = 0; i < visible; ++i) + { + const int idx = top + i; + if (idx >= count) break; + const int y = 2 + i * 7; + const bool sel = (idx == catCursor); + if (sel) drawSelBar (g, y - 1); + pxTextM (g, 6, y, cats.names[(size_t) idx], sel ? 1.0f : 0.72f); + const int pcount = (int) cats.patchIndices[(size_t) idx].size(); + pxTextM (g, gridW - 16, y, juce::String (pcount), sel ? 0.85f : 0.55f); + } +} + +void LcdPanel::drawPatchList (juce::Graphics& g) +{ + const int count = patchCountForCurrentView(); + const int visible = 8; + const int top = juce::jmax (0, juce::jmin (patchCursor - 3, count - visible)); + for (int i = 0; i < visible; ++i) + { + const int idx = top + i; + if (idx >= count) break; + const int pi = patchIndexAtCursor (idx); + const auto& p = proc.getBank().get (pi); const int y = 2 + i * 7; const bool sel = (idx == patchCursor); if (sel) drawSelBar (g, y - 1); pxTextM (g, 6, y, juce::String (idx + 1).paddedLeft ('0', 2) + " " + juce::String (p.name), sel ? 1.0f : 0.72f); - if (idx == proc.getPatchIndex()) + if (pi == proc.getPatchIndex()) pxTextM (g, gridW - 7, y, "*", sel ? 1.0f : 0.9f); } } @@ -553,6 +649,7 @@ void LcdPanel::drawParamScreen (juce::Graphics& g) static const prm::Id envMt[] = { prm::Id::ampA, prm::Id::ampD, prm::Id::ampS, prm::Id::ampR, prm::Id::fenvA, prm::Id::fenvD, prm::Id::fenvS, prm::Id::fenvR }; static const prm::Id lfo[] = { 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 arp[] = { prm::Id::arpOn, prm::Id::arpRate, prm::Id::arpPattern, prm::Id::arpOct, prm::Id::arpGate }; static const prm::Id master[] = { prm::Id::vol, prm::Id::pan, prm::Id::poly, prm::Id::glide, prm::Id::tune, prm::Id::hq }; const prm::Id* rows = nullptr; @@ -564,7 +661,8 @@ void LcdPanel::drawParamScreen (juce::Graphics& g) case 2: rows = envMt; count = (int) juce::numElementsInArray (envMt); break; case 3: rows = lfo; count = (int) juce::numElementsInArray (lfo); break; case 4: rows = fx; count = (int) juce::numElementsInArray (fx); break; - case 5: rows = master; count = (int) juce::numElementsInArray (master); break; + case 5: rows = arp; count = (int) juce::numElementsInArray (arp); break; + case 6: rows = master; count = (int) juce::numElementsInArray (master); break; default: return; } @@ -633,8 +731,10 @@ int LcdPanel::rowCountForScreen() const { switch (screen) { - case Screen::Menu: return 8; - case Screen::System: return 4; + case Screen::Menu: return 9; + case Screen::Patches: return proc.getBank().categories().names.size(); + case Screen::System: return 4; + case Screen::Category: return proc.getBank().categories().names.size() + 1; // +1 = SETTINGS row default: return 0; } } @@ -647,8 +747,9 @@ int LcdPanel::paramCountForScreen (int idx) const case 1: return 5; case 2: return 8; case 3: return 4; - case 4: return 4; - case 5: return 5; + case 4: return 4; // 3 FX params + SYNC row + case 5: return 5; // ARP: on, rate, pattern, oct, gate + case 6: return 6; default: return 0; } } @@ -662,33 +763,56 @@ const char* LcdPanel::paramScreenTitle (int idx) const case 2: return "ENVELOPE"; case 3: return "LFO"; case 4: return "FX"; - case 5: return "MASTER"; + case 5: return "ARP"; + case 6: return "MASTER"; default: return ""; } } void LcdPanel::openParamScreen (int idx) { - paramScreenIdx = juce::jlimit (0, 5, idx); + paramScreenIdx = juce::jlimit (0, 6, idx); cursor = 0; enterScreen (Screen::Param); } +// cyclic cursor step: wrap around the list instead of clamping at the ends +static int cyclicStep (int cur, int count, int dir) +{ + if (count <= 0) return 0; + int n = (cur + dir) % count; + if (n < 0) n += count; + return n; +} + void LcdPanel::moveCursor (int d) { - if (screen == Screen::Patch) + if (screen == Screen::Patches) { - patchCursor = juce::jlimit (0, proc.getBank().size() - 1, patchCursor + d); + const int count = proc.getBank().categories().names.size(); + catCursor = cyclicStep (catCursor, count, d); + setDirty(); + } + else if (screen == Screen::Category) + { + const int count = proc.getBank().categories().names.size() + 1; // +1 = SETTINGS row + catCursor = cyclicStep (catCursor, count, d); + setDirty(); + } + else if (screen == Screen::Patch) + { + const int count = patchCountForCurrentView(); + patchCursor = cyclicStep (patchCursor, count, d); setDirty(); } else if (screen == Screen::Param) { - cursor = juce::jlimit (0, paramCountForScreen (paramScreenIdx) - 1, cursor + d); + cursor = cyclicStep (cursor, paramCountForScreen (paramScreenIdx), d); setDirty(); } else if (rowCountForScreen() > 0) { - cursor = juce::jlimit (0, rowCountForScreen() - 1, cursor + d); + cursor = cyclicStep (cursor, rowCountForScreen(), d); setDirty(); } } @@ -723,6 +847,7 @@ static prm::Id paramForCursor (int screenIdx, int row) static const prm::Id envMt[] = { prm::Id::ampA, prm::Id::ampD, prm::Id::ampS, prm::Id::ampR, prm::Id::fenvA, prm::Id::fenvD, prm::Id::fenvS, prm::Id::fenvR }; static const prm::Id lfo[] = { 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 arp[] = { prm::Id::arpOn, prm::Id::arpRate, prm::Id::arpPattern, prm::Id::arpOct, prm::Id::arpGate }; static const prm::Id master[] = { prm::Id::vol, prm::Id::pan, prm::Id::poly, prm::Id::glide, prm::Id::tune, prm::Id::hq }; switch (screenIdx) @@ -732,7 +857,8 @@ static prm::Id paramForCursor (int screenIdx, int row) case 2: return envMt[row]; case 3: return lfo[row]; case 4: return fx[row]; - case 5: return master[row]; + case 5: return arp[row]; + case 6: return master[row]; default: return prm::Id::vol; } } @@ -766,36 +892,83 @@ void LcdPanel::applyPatchOnBrowserMove() { if (screen == Screen::Patch) { - proc.selectPatch (patchCursor); + const int pi = patchIndexAtCursor (patchCursor); + if (pi >= 0 && pi < proc.getBank().size()) + proc.selectPatch (pi); setDirty(); } } void LcdPanel::navLeft() { adjustCurrent (-1, false); } void LcdPanel::navRight() { adjustCurrent (1, false); } +void LcdPanel::openPatchBrowser() +{ + const auto& cats = proc.getBank().categories(); + int start = 0; + const int cur = proc.getPatchIndex(); + for (size_t c = 0; c < cats.patchIndices.size(); ++c) + { + bool found = false; + for (int pi : cats.patchIndices[(size_t) c]) + if (pi == cur) { found = true; break; } + if (found) { start = (int) c; break; } + } + catCursor = start; + enterScreen (Screen::Patches); +} + void LcdPanel::navEnter() { switch (screen) { case Screen::Splash: enterScreen (Screen::Home); break; - case Screen::Home: enterScreen (Screen::Menu); break; + case Screen::Home: + enterScreen (Screen::Menu); + break; case Screen::Menu: switch (cursor) { - case 0: enterScreen (Screen::Patch); break; + case 0: openPatchBrowser(); 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: enterScreen (Screen::Info); break; + case 7: openParamScreen (6); break; + case 8: enterScreen (Screen::Info); break; default: break; } break; + case Screen::Patches: + { + const auto& cats = proc.getBank().categories(); + if (catCursor >= 0 && catCursor < (int) cats.names.size()) + { + buildFilteredPatches (catCursor); + patchCursor = 0; + enterScreen (Screen::Patch); + } + break; + } + case Screen::Category: + { + const auto& cats = proc.getBank().categories(); + if (catCursor >= 0 && catCursor < (int) cats.names.size()) + { + buildFilteredPatches (catCursor); + patchCursor = 0; + enterScreen (Screen::Patch); + } + else if (catCursor == (int) cats.names.size()) + { + enterScreen (Screen::Menu); // SETTINGS row -> main menu + } + break; + } case Screen::Patch: - proc.selectPatch (patchCursor); - enterScreen (Screen::Menu); + proc.selectPatch (patchIndexAtCursor (patchCursor)); + enterScreen (Screen::Home); // back to the main screen showing the new patch break; case Screen::System: switch (cursor) @@ -820,7 +993,13 @@ void LcdPanel::navEnter() void LcdPanel::navExit() { - if (screen == Screen::Menu) + if (screen == Screen::Patch) + enterScreen (Screen::Patches); + else if (screen == Screen::Patches) + enterScreen (Screen::Menu); + else if (screen == Screen::Category) + enterScreen (Screen::Menu); // legacy: go to main menu + else if (screen == Screen::Menu) enterScreen (Screen::Home); else if (screen != Screen::Home) enterScreen (Screen::Menu); @@ -838,3 +1017,30 @@ bool LcdPanel::keyTrap (const juce::KeyPress& k) { navExit(); return true; } return false; } + +int LcdPanel::patchCountForCurrentView() const +{ + if (! filteredPatches.empty()) + return (int) filteredPatches.size(); + return proc.getBank().size(); +} + +int LcdPanel::patchIndexAtCursor (int viewIdx) const +{ + if (viewIdx < 0) return 0; + if (! filteredPatches.empty()) + { + if (viewIdx < (int) filteredPatches.size()) + return filteredPatches[(size_t) viewIdx]; + return filteredPatches.empty() ? 0 : filteredPatches.back(); + } + return viewIdx; +} + +void LcdPanel::buildFilteredPatches (int catIdx) +{ + filteredPatches.clear(); + const auto& cats = proc.getBank().categories(); + if (catIdx >= 0 && catIdx < cats.names.size()) + filteredPatches = cats.patchIndices[(size_t) catIdx]; +} diff --git a/Source/LcdPanel.h b/Source/LcdPanel.h index 862c90c..c11a55c 100644 --- a/Source/LcdPanel.h +++ b/Source/LcdPanel.h @@ -20,28 +20,39 @@ public: static constexpr int gridW = 160; // dot-matrix cell resolution static constexpr int gridH = 64; + // internal supersampling: dots are rasterized as anti-aliased circles at 4x, + // then box-downsampled 2:1 onto the 2px/dot presentation image. Downsampling + // turned off = chunky squares; 2x direct = boxy edges. 4x gives genuine round, + // separated dots that survive the (fractional) uiScale upscale. + static constexpr int subsample = 4; + static constexpr int presentScale = 2; // presentation pixels per logical dot bool keyTrap (const juce::KeyPress&); + void mouseDown (const juce::MouseEvent&) override; + private: - enum class Screen { Splash, Home, Menu, Patch, Param, System, Info }; + enum class Screen { Splash, Home, Menu, Patches, Patch, Param, System, Info, Category }; RMX19AudioProcessor& proc; Screen screen = Screen::Splash; juce::uint32 splashUntil = 0; - int cursor = 0; // menu / patch / param cursor - int patchCursor = 0; + int cursor = 0; // menu / param / system / catCursor (when in Category) + int patchCursor = 0; // patch cursor (global index) int paramScreenIdx = 0; // which param screen is open + int catCursor = 0; // category browser cursor + + std::vector filteredPatches; // patch indices for current category filter float powerGlow = 0.0f; int lastVoices = -1; juce::uint32 lastMidi = 0; - juce::Image dotImage; // 160x64 content scratch buffer - juce::Image displayImage; // 320x128 cached green-on-black result - juce::Image backdrop; // 320x128 static base (backlight + ghost dots) + juce::Image displayImage; // (gridW*subsample)x(gridH*subsample) render target: glass base + content dots + juce::Image presentImage; // (gridW*presentScale)x(gridH*presentScale) box-downsampled for the panel + juce::Image backdrop; // (gridW*subsample)x(gridH*subsample) static 3D glass base (backlight + sheen + vignette + ghost dots) bool contentDirty = true; static const juce::uint8* glyphFor (int key); @@ -49,6 +60,7 @@ private: void rebuild(); void buildBackdrop(); + juce::Rectangle lcdImageRect() const; void drawContent (juce::Graphics&); void px (juce::Graphics&, int x, int y, float intensity); @@ -61,6 +73,8 @@ private: void drawSplash (juce::Graphics&); void drawHome (juce::Graphics&); void drawMenu (juce::Graphics&); + void drawCategory (juce::Graphics&); + void drawPatches (juce::Graphics&); void drawPatchList (juce::Graphics&); void drawParamScreen (juce::Graphics&); void drawSystem (juce::Graphics&); @@ -75,6 +89,9 @@ private: void setDirty() { contentDirty = true; repaint(); } void enterScreen (Screen s) { screen = s; cursor = 0; setDirty(); } + // open the category/patch browser, positioned on the loaded patch's category + void openPatchBrowser(); + void timerCallback() override; int rowCountForScreen() const; @@ -83,6 +100,9 @@ private: void openParamScreen (int idx); const char* paramScreenTitle (int idx) const; int paramCountForScreen (int idx) const; + int patchCountForCurrentView() const; + int patchIndexAtCursor (int viewIdx) const; + void buildFilteredPatches (int catIdx); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LcdPanel) }; \ No newline at end of file diff --git a/Source/Params.cpp b/Source/Params.cpp index 3a43ea5..6441c36 100644 --- a/Source/Params.cpp +++ b/Source/Params.cpp @@ -35,6 +35,11 @@ static const Desc table[num] = { { "glide", "PORTAMENTO", "GLD", 0, 2, 0, false, 0.02f, 0.2f }, { "tune", "MASTER TUNE", "TUN", -12, 12, 0, true, 1, 1 }, { "hq", "HQ MODE", "HQ", 0, 1, 0, true, 1, 1 }, + { "arpon", "ARP ON", "ARP", 0, 1, 0, true, 1, 1 }, + { "arprate", "ARP RATE", "ART", 0, 7, 2, true, 1, 1 }, + { "arppattern", "ARP PATTERN", "APT", 0, 3, 0, true, 1, 1 }, + { "arpoct", "ARP OCTAVES", "AOC", 1, 4, 1, true, 1, 1 }, + { "arpgate", "ARP GATE", "AGT", 10, 100, 70, false, 1, 10 }, }; const Desc& desc (Id id) { return table[static_cast(id)]; } @@ -95,6 +100,19 @@ const char* fxTypeName (int t) case 3: return "SPACE"; default: return "OFF"; } } +const char* arpRateName (int r) +{ + switch (r) { case 0: return "1/16"; case 1: return "1/8T"; case 2: return "1/8 "; + case 3: return "1/4T"; case 4: return "1/4 "; case 5: return "1/2 "; + case 6: return "3/4 "; case 7: return "1/1 "; default: return "1/8 "; } +} + +const char* arpPatternName (int p) +{ + switch (p) { case 0: return "UP"; case 1: return "DOWN"; case 2: return "UP/DN"; + case 3: return "RANDOM"; default: return "UP"; } +} + const char* delaySyncName (int s) { switch (s) @@ -184,6 +202,11 @@ juce::String formatParam (Id id, float v) return juce::String (t > 0 ? "+" : "") + juce::String (t) + "ST"; } case Id::hq: return v >= 0.5f ? "HQ" : "ECO"; + case Id::arpOn: return v >= 0.5f ? "ON " : "OFF"; + case Id::arpRate: return arpRateName ((int) v); + case Id::arpPattern: return arpPatternName ((int) v); + case Id::arpOct: return juce::String ((int) v) + " OCT"; + case Id::arpGate: return juce::String ((int) v) + "%"; default: return "-----"; } } diff --git a/Source/Params.h b/Source/Params.h index 3d616f3..f074784 100644 --- a/Source/Params.h +++ b/Source/Params.h @@ -11,10 +11,11 @@ enum class Id : int { lfoRate, lfoShape, lfoDepth, lfoTarget, fxType, fxAmt, fxTime, vol, pan, poly, glide, tune, - hq + hq, + arpOn, arpRate, arpPattern, arpOct, arpGate }; -constexpr int num = 30; +constexpr int num = 35; struct Desc { const char* id; @@ -39,6 +40,9 @@ const char* lfoShapeName (int s); const char* lfoTargetName (int t); const char* fxTypeName (int t); +const char* arpRateName (int r); +const char* arpPatternName (int p); + constexpr int delaySyncCount = 11; const char* delaySyncName (int s); // "OFF", "1/16", ... double delaySyncBeats (int s); // beat multiple for sync delay diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 2c44c5c..8bfb2c9 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -145,12 +145,13 @@ void RackEditor::paint (juce::Graphics& g) void RackEditor::resized() { - // UI-scale dropdown lives in real (unscaled) coordinates, bottom right. - scaleBox.setBounds (getWidth() - 112, getHeight() - 30, 104, 20); - - // everything else renders in base 656x248 space, scaled by an affine transform + // everything renders in base 656×248 space, scaled by an affine transform const juce::AffineTransform tf = juce::AffineTransform::scale (uiScale); + // UI-scale dropdown now scales with the rest of the UI + scaleBox.setTransform (tf); + scaleBox.setBounds (baseW - 112, baseH - 30, 104, 20); + // LCD recessed module const juce::Rectangle lcdArea (14, 24, 356, 168); display.setTransform (tf); diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 5690a88..193aed2 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -14,8 +14,7 @@ static inline void dbgNote (const char* ev, int note, float vel) } class RMX19AudioProcessor::FxEngine -{ -public: +{public: void prepare (double sampleRate, int maxSamples) { sr = sampleRate; @@ -93,6 +92,149 @@ private: double chPh = 0.0; }; +// --------------------------------------------------------------------------- +// Arpeggiator. Holds a set of pressed notes (from the real MIDI input) and +// emits a rhythmic sequence of generated notes to the synth engine, synced to +// the host tempo (or a free-running sample clock when not transport-locked). +// --------------------------------------------------------------------------- +class RMX19AudioProcessor::ArpEngine +{ +public: + struct Event { int note; bool on; float vel; }; + + void reset() { stepPosSamples = 0.0; } + void setSampleRate (double s) { sr = s; } + + // Advance the arpeggiator clock by numSamples and produce the note events + // (relative to block start) to feed into the synth engine. + void step (bool on, double bpm, bool playing, + int rateIdx, int patternIdx, int octaves, float gatePct, + int numSamples, std::vector& out) + { + if (! on) + { + finishNote (out); + stepPosSamples = 0.0; + return; + } + + if (heldNotes.isEmpty()) + { + finishNote (out); + stepPosSamples = 0.0; + return; + } + + // step length in samples + const double beats[8] = { 0.25, 1.0/3.0, 0.5, 2.0/3.0, 1.0, 2.0, 3.0, 4.0 }; + const double stepBeats = beats[rateIdx & 7]; + double stepSamples; + if (playing && bpm > 1.0) + stepSamples = (60.0 / bpm) * stepBeats * sr; + else + stepSamples = (stepBeats * 0.5) * sr; // fallback ~120bpm quarter note + + if (stepSamples < 1.0) stepSamples = 1.0; + + // Build note pool (sorted) + juce::Array pool = heldNotes; + pool.sort(); + const int poolSize = pool.size(); + const int seqLen = poolSize * juce::jlimit (1, 4, octaves); + + // absolute sample clock into the running sequence + int cursor = 0; + while (cursor < numSamples) + { + // the current step index (absolute count of completed steps) + const long stepIndex = (long) (stepPosSamples / stepSamples); + const double stepStart = stepIndex * stepSamples; + const double stepEnd = stepStart + stepSamples; + + // trigger the note for this step at the start of the step + const int triggerSample = (int) (stepStart - stepPosSamples); + if (triggerSample >= 0 && triggerSample < numSamples) + { + const int patternIdxMod = patternIdx & 3; + const int playback = arpIndex (stepIndex, seqLen, patternIdxMod); + const int noteIdx = playback % poolSize; + const int oct = playback / poolSize; + const int note = pool[noteIdx] + oct * 12; + + finishNote (out); // release previous arp note (if different handled below) + apNoteOn (note, lastVel > 0 ? lastVel : 0.85f, out); + arpNote = note; + arpNoteActive = true; + } + + // gate-off within this step + const int gateSamples = (int) (stepSamples * juce::jlimit (0.02f, 1.0f, gatePct / 100.0f)); + const int gateEnd = triggerSample + gateSamples; + + // advance the clock through this step + double advance = stepEnd - stepPosSamples; + if (advance < 1.0) + { + stepPosSamples = stepEnd; + cursor = numSamples; + break; + } + const int consumed = juce::jmin (numSamples - cursor, (int) advance); + stepPosSamples += consumed; + cursor += consumed; + + // turn the note off after its gate time within the block + if (arpNoteActive && triggerSample >= 0 && gateEnd > triggerSample && gateEnd <= cursor) + { + apNoteOff (arpNote, out); + arpNoteActive = false; + } + } + } + + void noteOn (int midiNote, float vel) { heldNotes.addIfNotAlreadyThere (midiNote); lastVel = vel; } + void noteOff (int midiNote) { heldNotes.removeAllInstancesOf (midiNote); } + void allNotesOff() { heldNotes.clear(); } + +private: + juce::Array heldNotes; + double sr = 48000.0; + double stepPosSamples = 0.0; + int arpNote = -1; + bool arpNoteActive = false; + float lastVel = 0.85f; + + static int arpIndex (long i, int len, int pattern) + { + switch (pattern) + { + case 0: return (int) (i % len); // UP + case 1: return (int) ((len - 1) - (i % len)); // DOWN + case 2: // UP/DOWN + { + if (len <= 1) return 0; + const int period = len * 2 - 2; + const int t = (int) (i % period); + return t < len ? t : (len - 1) - (t - (len - 1)); + } + default: return (int) (juce::Random::getSystemRandom().nextInt (len)); // RANDOM + } + } + + void apNoteOn (int note, float vel, std::vector& out) { out.push_back ({ note, true, vel }); } + void apNoteOff (int note, std::vector& out) { out.push_back ({ note, false, 0.0f }); } + + void finishNote (std::vector& out) + { + if (arpNoteActive) + { + apNoteOff (arpNote, out); + arpNoteActive = false; + arpNote = -1; + } + } +}; + // --------------------------------------------------------------------------- RMX19AudioProcessor::RMX19AudioProcessor() @@ -116,6 +258,8 @@ void RMX19AudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) engine.allNotesOff (true); fx = std::make_unique(); fx->prepare (sampleRate, samplesPerBlock); + arp = std::make_unique(); + arp->setSampleRate (sampleRate); osBuf.setSize (2, juce::nextPowerOfTwo (samplesPerBlock * 2) + 4, false, true); } @@ -123,12 +267,16 @@ void RMX19AudioProcessor::releaseResources() { engine.allNotesOff (true); if (fx) fx->reset(); + if (arp) arp->allNotesOff(); } void RMX19AudioProcessor::processMidi (juce::MidiBuffer& midi, const VParams& p) { (void) p; constexpr double bendSemis = 2.0; + bool arpOn = false; + if (auto* par = apvts.getRawParameterValue ("arpon")) + arpOn = par->load() >= 0.5f; for (const auto meta : midi) { @@ -137,12 +285,18 @@ void RMX19AudioProcessor::processMidi (juce::MidiBuffer& midi, const VParams& p) if (msg.isNoteOn()) { - engine.noteOn (msg.getNoteNumber(), msg.getFloatVelocity()); + if (arpOn) + arp->noteOn (msg.getNoteNumber(), msg.getFloatVelocity()); + else + engine.noteOn (msg.getNoteNumber(), msg.getFloatVelocity()); dbgNote ("ON", msg.getNoteNumber(), msg.getFloatVelocity()); } else if (msg.isNoteOff()) { - engine.noteOff (msg.getNoteNumber()); + if (arpOn) + arp->noteOff (msg.getNoteNumber()); + else + engine.noteOff (msg.getNoteNumber()); dbgNote ("OFF", msg.getNoteNumber(), -1.0f); } else if (msg.isPitchWheel()) @@ -210,6 +364,33 @@ void RMX19AudioProcessor::processBlock (juce::AudioBuffer& buffer, juce:: processMidi (midi, p); + // advance the arpeggiator and trigger its generated notes + if (arp) + { + const bool arpOn = p.arpOn >= 0.5f; + double playingBpm = 120.0; + bool playing = false; + if (auto* ph = getPlayHead()) + if (auto pos = ph->getPosition()) + if (pos->getIsPlaying()) + { + playing = true; + if (pos->getBpm().hasValue()) + playingBpm = juce::jlimit (20.0, 300.0, *pos->getBpm()); + } + std::vector ae; + arp->step (arpOn, playingBpm, playing, + (int) p.arpRate, (int) p.arpPattern, (int) p.arpOct, + p.arpGate, numSamples, ae); + for (const auto& e : ae) + { + if (e.on) + engine.noteOn (e.note, e.vel); + else + engine.noteOff (e.note); + } + } + const int chL = 0, chR = 1; float* L = buffer.getNumChannels() > 0 ? buffer.getWritePointer (juce::jmin (chL, numChannels - 1)) : nullptr; float* R = buffer.getNumChannels() > 1 ? buffer.getWritePointer (juce::jmin (chR, numChannels - 1)) : nullptr; @@ -250,6 +431,20 @@ void RMX19AudioProcessor::processBlock (juce::AudioBuffer& buffer, juce:: R[s] = oR; } if (p.vol != 1.0f) buffer.applyGain (p.vol); + + // Final output soft-limiter: prevent hard clipping while preserving dynamics + for (int c = 0; c < numChannels; ++c) + { + float* ch = buffer.getWritePointer (c); + for (int s = 0; s < numSamples; ++s) + { + float x = ch[s]; + // Soft knee limiting at ~-1dBFS with smooth tanh curve + x = std::tanh (x * 0.89f) * 1.12f; + if (! std::isfinite (x)) x = 0.0f; + ch[s] = juce::jlimit (-1.0f, 1.0f, x); + } + } } else if (L != nullptr) { @@ -280,8 +475,32 @@ void RMX19AudioProcessor::processBlock (juce::AudioBuffer& buffer, juce:: for (int c = 0; c < numChannels; ++c) buffer.copyFrom (c, 0, dummy, 0, 0, numSamples); buffer.applyGain (p.vol); + + // Final output soft-limiter for mono path + for (int c = 0; c < numChannels; ++c) + { + float* ch = buffer.getWritePointer (c); + for (int s = 0; s < numSamples; ++s) + { + float x = ch[s]; + x = std::tanh (x * 0.89f) * 1.12f; + if (! std::isfinite (x)) x = 0.0f; + ch[s] = juce::jlimit (-1.0f, 1.0f, x); + } + } } + // output meter: instant attack, time-constant release so the LCD VU is readable + float peak = 0.0f; + for (int c = 0; c < numChannels; ++c) + { + const float* d = buffer.getReadPointer (c, 0); + for (int s = 0; s < numSamples; ++s) + peak = juce::jmax (peak, std::abs (d[s])); + } + const float blockSec = (float) numSamples / (float) std::max (1.0, getSampleRate()); + outMeter = juce::jmax (peak, outMeter * std::exp (-blockSec / 0.25f)); + cpu = 0.7f * cpu + 0.3f * (float) (juce::Time::getMillisecondCounter() - start); // real-time health check (offline probe + debug): flag blocks near the limit diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 8e3f6d4..183d7fa 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -37,10 +37,11 @@ public: PresetBank& getBank() { return bank; } SynthEngine& getEngine() { return engine; } -int getPatchIndex() const noexcept { return patchIndex; } + int getPatchIndex() const noexcept { return patchIndex; } void selectPatch (int i); int getActiveVoices() const noexcept { return engine.activeVoices(); } juce::uint32 getLastMidiTime() const noexcept { return lastMidi; } + float getOutputLevel() const noexcept { return outMeter; } float getCpu() const noexcept { return cpu; } private: @@ -51,12 +52,16 @@ private: class FxEngine; std::unique_ptr fx; + class ArpEngine; + std::unique_ptr arp; + PresetBank bank; SynthEngine engine; int patchIndex = 0; int lastPatchParam = 0; juce::uint32 lastMidi = 0; float cpu = 0.0f; + float outMeter = 0.0f; // smoothed output peak, written on the audio thread float dcL = 0.0f, dcR = 0.0f, dcPrevL = 0.0f, dcPrevR = 0.0f; juce::AudioBuffer osBuf; // internal 2x oversample target (HQ mode) diff --git a/Source/PresetBank.cpp b/Source/PresetBank.cpp index dd88d5d..5bdf522 100644 --- a/Source/PresetBank.cpp +++ b/Source/PresetBank.cpp @@ -13,291 +13,690 @@ enum { O_SIN = 0, O_TRI, O_SAW, O_PUL, O_NOI }; // 24 vol 25 pan 26 poly 27 glide 28 tune static const Patch defs[] = { - // 1 Warm Pad --------------------------------------------------------- + // == POLY PAD ========================================================== + // 1 Warm Pad { "Warm Pad", "POLY PAD", { O_SAW, O_SAW, 8, 55, 1, 2600, 0.20f, 60, 35, 0.60f, 1.6f, 0.80f, 1.20f, 0.40f, 0.9f, 0.35f, 0.5f, 4.0f, 0, 0.18f, 0, 1, 0.32f, 0.30f, 0.82f, 0.0f, 12, 0.0f, 0, 0 } }, - // 2 Glass Pad -------------------------------------------------------- - { "Glass Pad", "POLY PAD", { O_TRI, O_TRI, 12, 60, 2, 1400, 0.55f, 75, 30, + // 2 Glass Pad + { "Glass Pad", "POLY PAD", { O_TRI, O_TRI, 12, 60, 1, 1400, 0.55f, 75, 30, 0.80f, 1.8f, 0.90f, 1.60f, 0.50f, 1.2f, 0.40f, 0.8f, 5.2f, 0, 0.16f, 2, 3, 0.38f, 0.40f, 0.92f, 0.0f, 12, 0.0f, 0, 0 } }, - // 3 Dream Pad -------------------------------------------------------- + // 3 Dream Pad { "Dream Pad", "POLY PAD", { O_SAW, O_PUL, -7, 50, 1, 3000, 0.25f, 70, 40, 1.10f, 2.4f, 0.85f, 1.80f, 0.60f, 1.4f, 0.30f, 0.9f, 0.35f, 1, 0.20f, 0, 1, 0.40f, 0.50f, 0.80f, 0.0f, 12, 0.0f, 0, 0 } }, - // 4 Movie Strings ---------------------------------------------------- - { "Movie Strings", "POLY PAD", { O_SAW, O_SAW, 6, 45, 1, 2200, 0.15f, 65, 55, - 0.50f, 1.2f, 0.80f, 0.70f, - 0.40f, 0.8f, 0.35f, 0.5f, - 3.1f, 0, 0.10f, 0, - 3, 0.28f, 0.45f, - 0.82f, 0.0f, 12, 0.0f, 0, 0 } }, - // 5 Electric Piano --------------------------------------------------- - { "Electric Piano", "KEY", { O_PUL, O_TRI, 5, 62, 1, 2800, 0.25f, 45, 40, - 0.004f, 1.4f, 0.30f, 0.40f, - 0.004f, 0.2f, 0.10f, 0.3f, - 6.0f, 0, 0.05f, 2, - 1, 0.35f, 0.30f, - 0.80f, 0.0f, 12, 0.0f, 0, 0 } }, - // 6 Acoustic Piano --------------------------------------------------- - { "Acoustic Piano", "KEY", { O_SAW, O_TRI, 3, 55, 1, 5200, 0.18f, 30, 60, - 0.003f, 2.2f, 0.25f, 0.50f, - 0.002f, 0.25f, 0.05f, 0.35f, - 4.0f, 0, 0.03f, 2, - 1, 0.22f, 0.30f, - 0.80f, 0.0f, 12, 0.0f, 0, 0 } }, - // 7 Toy Piano -------------------------------------------------------- - { "Toy Piano", "KEY", { O_PUL, O_PUL, 0, 70, 2, 2100, 0.50f, 30, 45, - 0.002f, 0.35f, 0.0f, 0.18f, - 0.002f, 0.05f, 0.02f, 0.08f, - 5.0f, 0, 0.03f, 2, - 2, 0.18f, 0.17f, - 0.90f, 0.0f, 12, 0.0f, 0, 0 } }, - // 8 Clavinet 70 ------------------------------------------------------ - { "Clavinet 70", "KEY", { O_PUL, O_PUL, 0, 40, 2, 1500, 0.40f, 60, 30, - 0.002f, 0.50f, 0.45f, 0.12f, - 0.001f, 0.05f, 0.05f, 0.10f, - 4.0f, 0, 0.05f, 0, - 1, 0.30f, 0.28f, - 0.92f, 0.0f, 12, 0.0f, 0, 0 } }, - // 9 Organ Drawbar ---------------------------------------------------- - { "Organ Drawbar", "KEY", { O_SAW, O_SAW, 0, 50, 1, 10000, 0.10f, 20, 80, - 0.010f, 0.10f, 0.98f, 0.10f, - 0.010f, 0.10f, 0.50f, 0.10f, - 8.0f, 3, 0.03f, 1, - 1, 0.20f, 0.28f, - 0.82f, 0.0f, 8, 0.0f, 0, 0 } }, - // 10 FM E.Piano ------------------------------------------------------ - { "FM E.Piano", "KEY", { O_SIN, O_TRI, 0, 30, 1, 4000, 0.12f, 50, 50, - 0.002f, 1.0f, 0.50f, 0.30f, - 0.002f, 0.20f, 0.10f, 0.25f, - 4.5f, 0, 0.04f, 2, - 1, 0.40f, 0.30f, - 0.86f, 0.0f, 12, 0.0f, 0, 0 } }, - // 11 Analog Bass ----------------------------------------------------- - { "Analog Bass", "BASS", { O_PUL, O_SIN, 0, 80, 1, 700, 0.35f, 35, 10, - 0.002f, 0.30f, 0.82f, 0.12f, - 0.002f, 0.10f, 0.15f, 0.10f, - 3.0f, 0, 0.05f, 0, - 0, 0.0f, 0.30f, - 0.94f, 0.0f, 1, 0.04f, 0, 0 } }, - // 12 Sub Rez Bass ---------------------------------------------------- - { "Sub Rez Bass", "BASS", { O_SAW, O_PUL, -4, 45, 1, 520, 0.70f, 55, 15, - 0.004f, 0.40f, 0.70f, 0.10f, - 0.004f, 0.15f, 0.25f, 0.10f, - 2.4f, 3, 0.30f, 0, - 0, 0.0f, 0.30f, - 0.94f, 0.0f, 1, 0.03f, 0, 0 } }, - // 13 Saw Lead -------------------------------------------------------- - { "Saw Lead", "LEAD", { O_SAW, O_SAW, 14, 50, 1, 3800, 0.25f, 30, 60, - 0.005f, 0.30f, 0.80f, 0.20f, - 0.005f, 0.15f, 0.30f, 0.15f, - 5.4f, 0, 0.14f, 1, - 0, 0.0f, 0.28f, - 0.86f, 0.0f, 1, 0.06f, 0, 0 } }, - // 14 Mr. Square ------------------------------------------------------ - { "Mr. Square", "LEAD", { O_PUL, O_PUL, 7, 60, 1, 2400, 0.22f, 20, 70, - 0.003f, 0.25f, 0.72f, 0.18f, - 0.003f, 0.12f, 0.25f, 0.12f, - 6.6f, 0, 0.10f, 1, - 0, 0.0f, 0.25f, - 0.84f, 0.0f, 1, 0.05f, 0, 0 } }, - // 15 Choir Layered --------------------------------------------------- + // 15 Choir Layered { "Choir Layered", "POLY PAD",{ O_SAW, O_PUL, 9, 48, 1, 2600, 0.14f, 62, 45, 0.35f, 0.80f, 0.85f, 1.10f, 0.30f, 0.60f, 0.35f, 0.60f, 3.4f, 1, 0.12f, 0, 1, 0.38f, 0.42f, 0.82f, 0.0f, 12, 0.0f, 0, 0 } }, - // 16 8-bit Chiptune -------------------------------------------------- - { "8bit Chiptune", "LEAD", { O_PUL, O_PUL, 0, 30, 1, 9000, 0.10f, 10, 70, - 0.003f, 0.12f, 0.90f, 0.12f, - 0.003f, 0.06f, 0.10f, 0.08f, - 9.0f, 0, 0.05f, 0, - 0, 0.0f, 0.20f, - 0.80f, 0.0f, 6, 0.0f, 0, 0 } }, - // 17 Shimmer Pad ----------------------------------------------------- - { "Shimmer Pad", "POLY PAD", { O_TRI, O_TRI, 14, 55, 2, 1800, 0.50f, 60, 20, + // 17 Shimmer Pad + { "Shimmer Pad", "POLY PAD", { O_TRI, O_TRI, 14, 55, 1, 1800, 0.50f, 60, 20, 0.50f, 2.0f, 0.85f, 2.0f, 0.30f, 1.5f, 0.40f, 1.0f, 5.2f, 0, 0.20f, 2, 3, 0.38f, 0.55f, 0.90f, 0.0f, 16, 0.0f, 0, 0 } }, - // 18 Strings Ensemble ------------------------------------------------- - { "Strings Ens.", "POLY PAD", { O_SAW, O_PUL, 8, 58, 1, 3000, 0.10f, 65, 55, - 0.25f, 1.2f, 0.82f, 1.8f, - 0.20f, 0.7f, 0.40f, 0.6f, - 3.8f, 0, 0.10f, 0, - 1, 0.30f, 0.30f, - 0.80f, 0.0f, 16, 0.0f, 0, 0 } }, - // 19 Church Bell ----------------------------------------------------- - { "Church Bell", "KEY", { O_SIN, O_SIN, 0, 40, 2, 3200, 0.40f, 40, 30, - 0.002f, 2.5f, 0.10f, 1.8f, - 0.002f, 0.3f, 0.10f, 0.6f, - 4.4f, 0, 0.05f, 2, - 3, 0.30f, 0.60f, - 0.92f, 0.0f, 10, 0.0f, 0, 0 } }, - // 20 E.Piano 2 ------------------------------------------------------- - { "E.Piano 2", "KEY", { O_PUL, O_TRI, 4, 58, 1, 2500, 0.30f, 40, 35, - 0.003f, 1.2f, 0.40f, 0.5f, - 0.003f, 0.25f, 0.20f, 0.3f, - 6.0f, 0, 0.04f, 2, - 1, 0.35f, 0.30f, - 0.80f, 0.0f, 12, 0.0f, 0, 0 } }, - // 21 Acid Bass ------------------------------------------------------- - { "Acid Bass", "BASS", { O_SAW, O_PUL, 0, 72, 1, 650, 0.60f, 70, 0, - 0.002f, 0.30f, 0.75f, 0.15f, - 0.002f, 0.10f, 0.12f, 0.05f, - 0.6f, 1, 0.30f, 0, - 0, 0.0f, 0.30f, - 0.90f, 0.0f, 1, 0.04f, 0, 0 } }, - // 22 Analog Bass II -------------------------------------------------- - { "Analog Bass II", "BASS", { O_PUL, O_SIN, 0, 82, 1, 420, 0.40f, 45, 20, - 0.002f, 0.25f, 0.80f, 0.12f, - 0.002f, 0.12f, 0.20f, 0.08f, - 4.0f, 0, 0.08f, 1, - 1, 0.25f, 0.20f, - 0.88f, 0.0f, 1, 0.08f, 0, 0 } }, - // 23 Detuned Lead ---------------------------------------------------- - { "Detuned Lead", "LEAD", { O_SAW, O_SAW, 18, 45, 1, 4100, 0.20f, 35, 60, - 0.004f, 0.22f, 0.85f, 0.16f, - 0.004f, 0.12f, 0.30f, 0.10f, - 6.8f, 0, 0.09f, 1, - 2, 0.25f, 0.45f, - 0.85f, 0.0f, 1, 0.05f, 0, 0 } }, - // 24 Square Lead ----------------------------------------------------- - { "Square Lead", "LEAD", { O_PUL, O_PUL, 10, 55, 1, 5200, 0.25f, 30, 65, - 0.004f, 0.35f, 0.72f, 0.20f, - 0.004f, 0.15f, 0.25f, 0.12f, - 7.2f, 0, 0.08f, 1, - 1, 0.30f, 0.25f, - 0.85f, 0.0f, 1, 0.06f, 0, 0 } }, - // 25 Deep Space Pad -------------------------------------------------- + // 25 Deep Space Pad { "Deep Space Pad","POLY PAD", { O_SAW, O_SAW, 16, 60, 1, 1500, 0.45f, 70, 35, 0.90f, 2.4f, 0.85f, 2.2f, 0.50f, 1.6f, 0.45f, 1.2f, 5.0f, 0, 0.22f, 2, 3, 0.42f, 0.70f, 0.80f, 0.0f, 16, 0.0f, 0, 0 } }, - // 26 Liquid Keys ----------------------------------------------------- - { "Liquid Keys", "KEY", { O_TRI, O_TRI, 8, 55, 1, 2800, 0.30f, 55, 30, - 0.30f, 1.6f, 0.60f, 0.90f, - 0.20f, 1.0f, 0.30f, 0.60f, - 3.6f, 0, 0.16f, 2, - 3, 0.36f, 0.50f, - 0.84f, 0.0f, 12, 0.0f, 0, 0 } }, - // 27 Techno Bass ----------------------------------------------------- - { "Techno Bass", "BASS", { O_SAW, O_PUL, 0, 88, 1, 480, 0.45f, 60, 10, - 0.003f, 0.40f, 0.75f, 0.10f, - 0.003f, 0.12f, 0.15f, 0.08f, - 2.0f, 2, 0.35f, 0, - 2, 0.32f, 0.28f, - 0.92f, 0.0f, 1, 0.03f, 0, 0 } }, - // 28 Octave Lead ----------------------------------------------------- - { "Octave Lead", "LEAD", { O_PUL, O_SAW, 0, 45, 1, 4200, 0.20f, 25, 65, - 0.004f, 0.28f, 0.78f, 0.18f, - 0.004f, 0.14f, 0.28f, 0.12f, - 6.0f, 0, 0.12f, 1, - 2, 0.28f, 0.35f, - 0.86f, 0.0f, 1, 0.05f, 0, 0 } }, - // 29 Glassy Chime ---------------------------------------------------- - { "Glassy Chime", "KEY", { O_SIN, O_TRI, 20, 65, 2, 4200, 0.35f, 35, 40, - 0.005f, 2.0f, 0.30f, 1.6f, - 0.004f, 0.35f, 0.15f, 0.6f, - 5.6f, 0, 0.08f, 2, - 3, 0.34f, 0.60f, - 0.82f, 0.0f, 10, 0.0f, 0, 0 } }, - // 30 Chorus Organ ---------------------------------------------------- - { "Chorus Organ", "KEY", { O_SAW, O_SAW, 0, 42, 1, 8000, 0.14f, 30, 75, - 0.010f, 0.10f, 0.95f, 0.12f, - 0.010f, 0.12f, 0.60f, 0.15f, - 7.5f, 3, 0.04f, 1, - 1, 0.40f, 0.34f, - 0.86f, 0.0f, 8, 0.0f, 0, 0 } }, - // 31 Night Strings --------------------------------------------------- - { "Night Strings","POLY PAD", { O_SAW, O_TRI, 10, 52, 0, 2600, 0.10f, 60, 50, - 1.10f, 2.6f, 0.85f, 2.0f, - 0.55f, 1.6f, 0.40f, 1.1f, - 3.2f, 0, 0.14f, 0, - 3, 0.40f, 0.55f, - 0.84f, 0.0f, 16, 0.0f, 0, 0 } }, - // 32 Pulse Echo ------------------------------------------------------ - { "Pulse Echo", "LEAD", { O_PUL, O_PUL, 0, 35, 2, 6000, 0.18f, 15, 60, - 0.005f, 0.32f, 0.70f, 0.20f, - 0.005f, 0.16f, 0.30f, 0.15f, - 8.0f, 0, 0.10f, 0, - 2, 0.45f, 0.38f, - 0.84f, 0.0f, 1, 0.04f, 0, 0 } }, - // 33 Sine Sub Bass --------------------------------------------------- - { "Sine Sub Bass","BASS", { O_SIN, O_SIN, 0, 100, 1, 380, 0.20f, 55, 10, - 0.003f, 0.35f, 0.85f, 0.10f, - 0.003f, 0.10f, 0.30f, 0.10f, - 3.5f, 0, 0.06f, 0, - 2, 0.25f, 0.20f, - 0.90f, 0.0f, 1, 0.05f, 0, 0 } }, - // 34 Space Choir ----------------------------------------------------- + // 34 Space Choir { "Space Choir", "POLY PAD", { O_TRI, O_SAW, 12, 50, 1, 2400, 0.35f, 65, 45, 0.45f, 1.4f, 0.82f, 1.7f, 0.30f, 0.90f, 0.40f, 0.9f, 3.8f, 1, 0.15f, 0, 3, 0.46f, 0.65f, 0.82f, 0.0f, 12, 0.0f, 0, 0 } }, - // 35 Retro Clav ------------------------------------------------------ + // 39 Crystal Pad + { "Crystal Pad", "POLY PAD", { O_SIN, O_TRI, 18, 55, 1, 3600, 0.40f, 50, 25, + 0.60f, 2.2f, 0.85f, 2.0f, + 0.40f, 1.4f, 0.40f, 1.2f, + 5.5f, 0, 0.20f, 2, + 3, 0.40f, 0.60f, + 0.80f, 0.0f, 12, 0.0f, 0, 0 } }, + // 41 Infinite Pad + { "Infinite Pad", "POLY PAD", { O_SAW, O_SAW, 20, 50, 1, 1800, 0.30f, 75, 35, + 2.0f, 3.5f, 0.90f, 4.0f, + 1.5f, 2.5f, 0.50f, 3.0f, + 0.25f, 1, 0.25f, 2, + 3, 0.50f, 0.55f, + 0.80f, 0.0f, 16, 0.0f, 0, 0 } }, + // 42 Nebula + { "Nebula", "POLY PAD", { O_TRI, O_PUL, 15, 55, 1, 2000, 0.25f, 65, 30, + 1.8f, 3.0f, 0.88f, 3.5f, + 1.2f, 2.0f, 0.45f, 2.5f, + 0.30f, 0, 0.20f, 0, + 3, 0.55f, 0.65f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // 43 Cathedral Pad + { "Cathedral Pad","POLY PAD", { O_SIN, O_SAW, 12, 45, 2, 1600, 0.45f, 80, 40, + 2.5f, 4.0f, 0.92f, 5.0f, + 1.8f, 3.0f, 0.55f, 3.5f, + 4.0f, 0, 0.15f, 0, + 3, 0.60f, 0.80f, + 0.78f, 0.0f, 16, 0.0f, 0, 0 } }, + // 45 Monolith + { "Monolith", "POLY PAD", { O_SIN, O_SIN, 0, 50, 1, 320, 0.25f, 40, 15, + 2.0f, 3.0f, 0.88f, 4.5f, + 1.5f, 2.5f, 0.50f, 3.0f, + 3.5f, 0, 0.18f, 2, + 3, 0.42f, 0.75f, + 0.78f, 0.0f, 16, 0.0f, 0, 0 } }, + // 46 Quantum Drift + { "Quantum Drift","POLY PAD", { O_SAW, O_SAW, 22, 48, 2, 2200, 0.40f, 70, 30, + 0.80f, 2.2f, 0.88f, 3.0f, + 0.50f, 1.8f, 0.45f, 2.0f, + 5.8f, 0, 0.22f, 0, + 3, 0.45f, 0.65f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // 47 Horizon + { "Horizon", "POLY PAD", { O_TRI, O_SAW, 4, 50, 1, 1800, 0.20f, 65, 35, + 1.5f, 2.8f, 0.90f, 3.5f, + 1.0f, 2.0f, 0.50f, 2.5f, + 0.3f, 1, 0.18f, 0, + 1, 0.40f, 0.55f, + 0.80f, 0.0f, 16, 0.0f, 0, 0 } }, + // 52 Abyss + { "Abyss", "POLY PAD", { O_SAW, O_TRI, 14, 50, 1, 1200, 0.35f, 80, 30, + 2.5f, 3.5f, 0.90f, 5.0f, + 2.0f, 3.0f, 0.50f, 3.5f, + 4.0f, 1, 0.18f, 0, + 3, 0.70f, 0.80f, + 0.78f, 0.0f, 16, 0.0f, 0, 0 } }, + // 53 Shimmer Reverb + { "Shimmer Reverb","POLY PAD",{ O_TRI, O_SIN, 8, 55, 2, 3400, 0.50f, 75, 25, + 3.0f, 4.0f, 0.92f, 5.5f, + 2.2f, 3.5f, 0.55f, 4.0f, + 5.0f, 0, 0.20f, 2, + 3, 0.75f, 0.70f, + 0.76f, 0.0f, 16, 0.0f, 0, 0 } }, + // 54 Temple + { "Temple", "POLY PAD", { O_PUL, O_SAW, 6, 48, 1, 1600, 0.30f, 70, 35, + 2.0f, 3.0f, 0.88f, 4.5f, + 1.5f, 2.5f, 0.48f, 3.0f, + 3.5f, 0, 0.15f, 0, + 3, 0.65f, 0.75f, + 0.80f, 0.0f, 16, 0.0f, 0, 0 } }, + // 55 Silk Pad + { "Silk Pad", "POLY PAD", { O_TRI, O_TRI, 6, 55, 1, 3400, 0.20f, 55, 25, + 1.2f, 2.5f, 0.90f, 3.0f, + 0.8f, 1.8f, 0.45f, 2.0f, + 3.0f, 0, 0.18f, 0, + 1, 0.38f, 0.45f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // 56 Glass Cloud + { "Glass Cloud", "POLY PAD", { O_SIN, O_TRI, 16, 50, 1, 3800, 0.35f, 65, 20, + 1.5f, 2.8f, 0.88f, 3.5f, + 1.0f, 2.0f, 0.40f, 2.5f, + 0.35f, 1, 0.22f, 0, + 3, 0.55f, 0.60f, + 0.80f, 0.0f, 16, 0.0f, 0, 0 } }, + // 57 Velvet Pad + { "Velvet Pad", "POLY PAD", { O_SAW, O_TRI, 10, 52, 1, 1600, 0.25f, 70, 30, + 1.8f, 3.0f, 0.90f, 3.5f, + 1.2f, 2.2f, 0.50f, 2.5f, + 0.30f, 1, 0.20f, 0, + 3, 0.48f, 0.55f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // == KEY =============================================================== + // 5 Electric Piano + { "Electric Piano", "KEY", { O_PUL, O_TRI, 5, 62, 1, 2800, 0.25f, 45, 40, + 0.004f, 1.4f, 0.30f, 0.40f, + 0.004f, 0.2f, 0.10f, 0.3f, + 6.0f, 0, 0.05f, 2, + 1, 0.35f, 0.30f, + 0.80f, 0.0f, 12, 0.0f, 0, 0 } }, + // 6 Acoustic Piano + { "Acoustic Piano", "KEY", { O_SAW, O_TRI, 3, 55, 1, 5200, 0.18f, 30, 60, + 0.003f, 2.2f, 0.25f, 0.50f, + 0.002f, 0.25f, 0.05f, 0.35f, + 4.0f, 0, 0.03f, 2, + 1, 0.22f, 0.30f, + 0.80f, 0.0f, 12, 0.0f, 0, 0 } }, + // 7 Toy Piano + { "Toy Piano", "KEY", { O_PUL, O_PUL, 0, 70, 1, 2100, 0.50f, 30, 45, + 0.002f, 0.35f, 0.0f, 0.18f, + 0.002f, 0.05f, 0.02f, 0.08f, + 5.0f, 0, 0.03f, 2, + 2, 0.18f, 0.17f, + 0.90f, 0.0f, 12, 0.0f, 0, 0 } }, + // 8 Clavinet 70 + { "Clavinet 70", "KEY", { O_PUL, O_PUL, 0, 40, 1, 1500, 0.40f, 60, 30, + 0.002f, 0.50f, 0.45f, 0.12f, + 0.001f, 0.05f, 0.05f, 0.10f, + 4.0f, 0, 0.05f, 0, + 1, 0.30f, 0.28f, + 0.92f, 0.0f, 12, 0.0f, 0, 0 } }, + // 9 Organ Drawbar + { "Organ Drawbar", "KEY", { O_SAW, O_SAW, 0, 50, 1, 10000, 0.10f, 20, 80, + 0.010f, 0.10f, 0.98f, 0.10f, + 0.010f, 0.10f, 0.50f, 0.10f, + 8.0f, 3, 0.03f, 1, + 1, 0.20f, 0.28f, + 0.82f, 0.0f, 8, 0.0f, 0, 0 } }, + // 10 FM E.Piano + { "FM E.Piano", "KEY", { O_SIN, O_TRI, 0, 30, 1, 4000, 0.12f, 50, 50, + 0.002f, 1.0f, 0.50f, 0.30f, + 0.002f, 0.20f, 0.10f, 0.25f, + 4.5f, 0, 0.04f, 2, + 1, 0.40f, 0.30f, + 0.86f, 0.0f, 12, 0.0f, 0, 0 } }, + // 19 Church Bell + { "Church Bell", "KEY", { O_SIN, O_SIN, 0, 40, 1, 3200, 0.40f, 40, 30, + 0.002f, 2.5f, 0.10f, 1.8f, + 0.002f, 0.3f, 0.10f, 0.6f, + 4.4f, 0, 0.05f, 2, + 3, 0.30f, 0.60f, + 0.92f, 0.0f, 10, 0.0f, 0, 0 } }, + // 20 E.Piano 2 + { "E.Piano 2", "KEY", { O_PUL, O_TRI, 4, 58, 1, 2500, 0.30f, 40, 35, + 0.003f, 1.2f, 0.40f, 0.5f, + 0.003f, 0.25f, 0.20f, 0.3f, + 6.0f, 0, 0.04f, 2, + 1, 0.35f, 0.30f, + 0.80f, 0.0f, 12, 0.0f, 0, 0 } }, + // 26 Liquid Keys + { "Liquid Keys", "KEY", { O_TRI, O_TRI, 8, 55, 1, 2800, 0.30f, 55, 30, + 0.30f, 1.6f, 0.60f, 0.90f, + 0.20f, 1.0f, 0.30f, 0.60f, + 3.6f, 0, 0.16f, 2, + 3, 0.36f, 0.50f, + 0.84f, 0.0f, 12, 0.0f, 0, 0 } }, + // 29 Glassy Chime + { "Glassy Chime", "KEY", { O_SIN, O_TRI, 20, 65, 1, 4200, 0.35f, 35, 40, + 0.005f, 2.0f, 0.30f, 1.6f, + 0.004f, 0.35f, 0.15f, 0.6f, + 5.6f, 0, 0.08f, 2, + 3, 0.34f, 0.60f, + 0.82f, 0.0f, 10, 0.0f, 0, 0 } }, + // 30 Chorus Organ + { "Chorus Organ", "KEY", { O_SAW, O_SAW, 0, 42, 1, 8000, 0.14f, 30, 75, + 0.010f, 0.10f, 0.95f, 0.12f, + 0.010f, 0.12f, 0.60f, 0.15f, + 7.5f, 3, 0.04f, 1, + 1, 0.40f, 0.34f, + 0.86f, 0.0f, 8, 0.0f, 0, 0 } }, + // 35 Retro Clav { "Retro Clav", "KEY", { O_PUL, O_TRI, 2, 55, 1, 2600, 0.35f, 40, 35, 0.003f, 0.60f, 0.40f, 0.15f, 0.002f, 0.10f, 0.08f, 0.12f, 5.0f, 0, 0.05f, 2, 3, 0.25f, 0.30f, 0.86f, 0.0f, 10, 0.0f, 0, 0 } }, - // 36 High Strings ---------------------------------------------------- - { "High Strings", "POLY PAD", { O_SAW, O_PUL, 6, 60, 1, 3400, 0.10f, 55, 60, - 0.20f, 1.0f, 0.80f, 1.6f, - 0.15f, 0.60f, 0.40f, 0.7f, - 4.2f, 0, 0.10f, 0, - 1, 0.35f, 0.40f, - 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, - // 37 Echo Flute ------------------------------------------------------ - { "Echo Flute", "LEAD", { O_TRI, O_SIN, 0, 85, 1, 3000, 0.15f, 20, 55, - 0.05f, 0.50f, 0.75f, 0.25f, - 0.04f, 0.30f, 0.40f, 0.20f, - 4.8f, 0, 0.10f, 1, - 2, 0.40f, 0.45f, - 0.86f, 0.0f, 1, 0.06f, 0, 0 } }, - // 38 Dub Rez Bass ---------------------------------------------------- - { "Dub Rez Bass", "BASS", { O_SAW, O_PUL, -5, 60, 1, 500, 0.65f, 45, 5, - 0.004f, 0.35f, 0.80f, 0.12f, - 0.004f, 0.15f, 0.25f, 0.12f, - 0.8f, 3, 0.30f, 0, - 2, 0.40f, 0.36f, - 0.90f, 0.0f, 1, 0.05f, 0, 0 } }, - // 39 Crystal Pad ----------------------------------------------------- - { "Crystal Pad", "POLY PAD", { O_SIN, O_TRI, 18, 55, 2, 3600, 0.40f, 50, 25, - 0.60f, 2.2f, 0.85f, 2.0f, - 0.40f, 1.4f, 0.40f, 1.2f, - 5.5f, 0, 0.20f, 2, - 3, 0.40f, 0.60f, - 0.80f, 0.0f, 12, 0.0f, 0, 0 } }, - // 40 Funk Wah -------------------------------------------------------- + // 40 Funk Wah { "Funk Wah", "KEY", { O_PUL, O_SAW, 0, 40, 1, 1800, 0.55f, 75, 20, 0.004f, 0.70f, 0.50f, 0.20f, 0.004f, 0.20f, 0.20f, 0.15f, 7.0f, 2, 0.40f, 0, 1, 0.32f, 0.30f, 0.88f, 0.0f, 1, 0.03f, 0, 0 } }, + // == BASS ============================================================== + // 11 Analog Bass + { "Analog Bass", "BASS", { O_PUL, O_SIN, 0, 80, 1, 700, 0.35f, 35, 10, + 0.002f, 0.30f, 0.82f, 0.12f, + 0.002f, 0.10f, 0.15f, 0.10f, + 3.0f, 0, 0.05f, 0, + 0, 0.0f, 0.30f, + 0.94f, 0.0f, 1, 0.04f, 0, 0 } }, + // 12 Sub Rez Bass + { "Sub Rez Bass", "BASS", { O_SAW, O_PUL, -4, 45, 1, 520, 0.70f, 55, 15, + 0.004f, 0.40f, 0.70f, 0.10f, + 0.004f, 0.15f, 0.25f, 0.10f, + 2.4f, 3, 0.30f, 0, + 0, 0.0f, 0.30f, + 0.94f, 0.0f, 1, 0.03f, 0, 0 } }, + // 21 Acid Bass + { "Acid Bass", "BASS", { O_SAW, O_PUL, 0, 72, 1, 650, 0.60f, 70, 0, + 0.002f, 0.30f, 0.75f, 0.15f, + 0.002f, 0.10f, 0.12f, 0.05f, + 0.6f, 1, 0.30f, 0, + 0, 0.0f, 0.30f, + 0.90f, 0.0f, 1, 0.04f, 0, 0 } }, + // 22 Analog Bass II + { "Analog Bass II", "BASS", { O_PUL, O_SIN, 0, 82, 1, 420, 0.40f, 45, 20, + 0.002f, 0.25f, 0.80f, 0.12f, + 0.002f, 0.12f, 0.20f, 0.08f, + 4.0f, 0, 0.08f, 1, + 1, 0.25f, 0.20f, + 0.88f, 0.0f, 1, 0.08f, 0, 0 } }, + // 33 Sine Sub Bass + { "Sine Sub Bass","BASS", { O_SIN, O_SIN, 0, 100, 1, 380, 0.20f, 55, 10, + 0.003f, 0.35f, 0.85f, 0.10f, + 0.003f, 0.10f, 0.30f, 0.10f, + 3.5f, 0, 0.06f, 0, + 2, 0.25f, 0.20f, + 0.90f, 0.0f, 1, 0.05f, 0, 0 } }, + // == LEAD ============================================================== + // 13 Saw Lead + { "Saw Lead", "LEAD", { O_SAW, O_SAW, 14, 50, 1, 3800, 0.25f, 30, 60, + 0.005f, 0.30f, 0.80f, 0.20f, + 0.005f, 0.15f, 0.30f, 0.15f, + 5.4f, 0, 0.14f, 1, + 0, 0.0f, 0.28f, + 0.86f, 0.0f, 1, 0.06f, 0, 0 } }, + // 14 Mr. Square + { "Mr. Square", "LEAD", { O_PUL, O_PUL, 7, 60, 1, 2400, 0.22f, 20, 70, + 0.003f, 0.25f, 0.72f, 0.18f, + 0.003f, 0.12f, 0.25f, 0.12f, + 6.6f, 0, 0.10f, 1, + 0, 0.0f, 0.25f, + 0.84f, 0.0f, 1, 0.05f, 0, 0 } }, + // 16 8bit Chiptune + { "8bit Chiptune", "LEAD", { O_PUL, O_PUL, 0, 30, 1, 9000, 0.10f, 10, 70, + 0.003f, 0.12f, 0.90f, 0.12f, + 0.003f, 0.06f, 0.10f, 0.08f, + 9.0f, 0, 0.05f, 0, + 0, 0.0f, 0.20f, + 0.80f, 0.0f, 6, 0.0f, 0, 0 } }, + // 23 Detuned Lead + { "Detuned Lead", "LEAD", { O_SAW, O_SAW, 18, 45, 1, 4100, 0.20f, 35, 60, + 0.004f, 0.22f, 0.85f, 0.16f, + 0.004f, 0.12f, 0.30f, 0.10f, + 6.8f, 0, 0.09f, 1, + 2, 0.25f, 0.45f, + 0.85f, 0.0f, 1, 0.05f, 0, 0 } }, + // 24 Square Lead + { "Square Lead", "LEAD", { O_PUL, O_PUL, 10, 55, 1, 5200, 0.25f, 30, 65, + 0.004f, 0.35f, 0.72f, 0.20f, + 0.004f, 0.15f, 0.25f, 0.12f, + 7.2f, 0, 0.08f, 1, + 1, 0.30f, 0.25f, + 0.85f, 0.0f, 1, 0.06f, 0, 0 } }, + // 28 Octave Lead + { "Octave Lead", "LEAD", { O_PUL, O_SAW, 0, 45, 1, 4200, 0.20f, 25, 65, + 0.004f, 0.28f, 0.78f, 0.18f, + 0.004f, 0.14f, 0.28f, 0.12f, + 6.0f, 0, 0.12f, 1, + 2, 0.28f, 0.35f, + 0.86f, 0.0f, 1, 0.05f, 0, 0 } }, + // 32 Pulse Echo + { "Pulse Echo", "LEAD", { O_PUL, O_PUL, 0, 35, 1, 6000, 0.18f, 15, 60, + 0.005f, 0.32f, 0.70f, 0.20f, + 0.005f, 0.16f, 0.30f, 0.15f, + 8.0f, 0, 0.10f, 0, + 2, 0.45f, 0.38f, + 0.84f, 0.0f, 1, 0.04f, 0, 0 } }, + // 37 Echo Flute + { "Echo Flute", "LEAD", { O_TRI, O_SIN, 0, 85, 1, 3000, 0.15f, 20, 55, + 0.05f, 0.50f, 0.75f, 0.25f, + 0.04f, 0.30f, 0.40f, 0.20f, + 4.8f, 0, 0.10f, 1, + 2, 0.40f, 0.45f, + 0.86f, 0.0f, 1, 0.06f, 0, 0 } }, + // 48 Delay Garden + { "Delay Garden", "LEAD", { O_SAW, O_SAW, 12, 50, 1, 3200, 0.20f, 55, 40, + 0.005f, 0.40f, 0.75f, 0.30f, + 0.15f, 0.80f, 0.35f, 0.60f, + 4.5f, 0, 0.15f, 0, + 2, 0.55f, 0.50f, + 0.85f, 0.0f, 1, 0.06f, 0, 0 } }, + // 49 Cosmic Echo + { "Cosmic Echo", "LEAD", { O_TRI, O_SIN, 0, 70, 1, 2600, 0.30f, 50, 35, + 0.015f, 0.50f, 0.70f, 0.40f, + 0.10f, 1.0f, 0.40f, 0.80f, + 5.5f, 0, 0.12f, 1, + 2, 0.70f, 0.75f, + 0.84f, 0.0f, 1, 0.05f, 0, 0 } }, + // 50 Stellar Delay + { "Stellar Delay", "LEAD", { O_SAW, O_PUL, 10, 45, 1, 2800, 0.35f, 50, 35, + 0.008f, 0.45f, 0.72f, 0.35f, + 0.12f, 0.90f, 0.38f, 0.70f, + 6.0f, 0, 0.10f, 0, + 2, 0.60f, 0.65f, + 0.85f, 0.0f, 1, 0.05f, 0, 0 } }, + // 51 Pulse Train + { "Pulse Train", "LEAD", { O_PUL, O_PUL, 0, 60, 1, 4000, 0.25f, 45, 55, + 0.005f, 0.35f, 0.80f, 0.25f, + 0.08f, 0.50f, 0.30f, 0.40f, + 8.0f, 0, 0.08f, 0, + 2, 0.65f, 0.45f, + 0.86f, 0.0f, 1, 0.04f, 0, 0 } }, + // == STRINGS =========================================================== + // 4 Movie Strings + { "Movie Strings", "STRINGS", { O_SAW, O_SAW, 6, 45, 1, 2200, 0.15f, 65, 55, + 0.50f, 1.2f, 0.80f, 0.70f, + 0.40f, 0.8f, 0.35f, 0.5f, + 3.1f, 0, 0.10f, 0, + 3, 0.28f, 0.45f, + 0.82f, 0.0f, 12, 0.0f, 0, 0 } }, + // 18 Strings Ensemble + { "Strings Ens.", "STRINGS", { O_SAW, O_PUL, 8, 58, 1, 3000, 0.10f, 65, 55, + 0.25f, 1.2f, 0.82f, 1.8f, + 0.20f, 0.7f, 0.40f, 0.6f, + 3.8f, 0, 0.10f, 0, + 1, 0.30f, 0.30f, + 0.80f, 0.0f, 16, 0.0f, 0, 0 } }, + // 31 Night Strings + { "Night Strings","STRINGS", { O_SAW, O_TRI, 10, 52, 0, 2600, 0.10f, 60, 50, + 1.10f, 2.6f, 0.85f, 2.0f, + 0.55f, 1.6f, 0.40f, 1.1f, + 3.2f, 0, 0.14f, 0, + 3, 0.40f, 0.55f, + 0.84f, 0.0f, 16, 0.0f, 0, 0 } }, + // 36 High Strings + { "High Strings", "STRINGS", { O_SAW, O_PUL, 6, 60, 1, 3400, 0.10f, 55, 60, + 0.20f, 1.0f, 0.80f, 1.6f, + 0.15f, 0.60f, 0.40f, 0.7f, + 4.2f, 0, 0.10f, 0, + 1, 0.35f, 0.40f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // 44 Frozen Strings + { "Frozen Strings","STRINGS", { O_SAW, O_PUL, 10, 55, 1, 2400, 0.12f, 60, 55, + 0.20f, 1.0f, 0.80f, 1.6f, + 0.15f, 0.60f, 0.40f, 0.7f, + 4.2f, 0, 0.10f, 0, + 1, 0.35f, 0.40f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // 58 Deep Strings + { "Deep Strings", "STRINGS", { O_SAW, O_SAW, 12, 50, 1, 1800, 0.12f, 65, 50, + 0.40f, 1.4f, 0.82f, 1.5f, + 0.30f, 0.80f, 0.38f, 0.60f, + 3.5f, 0, 0.12f, 0, + 3, 0.38f, 0.50f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // 59 Vinyl Strings + { "Vinyl Strings","STRINGS", { O_SAW, O_TRI, 8, 48, 1, 2000, 0.15f, 60, 45, + 0.35f, 1.2f, 0.78f, 1.4f, + 0.25f, 0.70f, 0.35f, 0.55f, + 3.2f, 0, 0.10f, 0, + 1, 0.32f, 0.42f, + 0.84f, 0.0f, 16, 0.0f, 0, 0 } }, + // 60 Brass Ensemble + { "Brass Ens.", "STRINGS", { O_SAW, O_PUL, 5, 55, 1, 2600, 0.20f, 50, 50, + 0.15f, 0.80f, 0.75f, 0.50f, + 0.10f, 0.50f, 0.30f, 0.40f, + 4.0f, 0, 0.08f, 0, + 1, 0.30f, 0.35f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // == DUB =============================================================== + // 27 Techno Bass + { "Techno Bass", "DUB", { O_SAW, O_PUL, 0, 88, 1, 480, 0.45f, 60, 10, + 0.003f, 0.40f, 0.75f, 0.10f, + 0.003f, 0.12f, 0.15f, 0.08f, + 2.0f, 2, 0.35f, 0, + 2, 0.32f, 0.28f, + 0.92f, 0.0f, 1, 0.03f, 0, 0 } }, + // 38 Dub Rez Bass + { "Dub Rez Bass", "DUB", { O_SAW, O_PUL, -5, 60, 1, 500, 0.65f, 45, 5, + 0.004f, 0.35f, 0.80f, 0.12f, + 0.004f, 0.15f, 0.25f, 0.12f, + 0.8f, 3, 0.30f, 0, + 2, 0.40f, 0.36f, + 0.90f, 0.0f, 1, 0.05f, 0, 0 } }, + // 61 Dub Sub + { "Dub Sub", "DUB", { O_SIN, O_SIN, 0, 100, 1, 350, 0.15f, 30, 15, + 0.003f, 0.40f, 0.85f, 0.15f, + 0.003f, 0.12f, 0.25f, 0.10f, + 3.0f, 0, 0.05f, 0, + 2, 0.45f, 0.35f, + 0.90f, 0.0f, 1, 0.04f, 0, 0 } }, + // 62 Dub Wobble + { "Dub Wobble", "DUB", { O_SAW, O_PUL, 0, 75, 1, 400, 0.55f, 65, 20, + 0.003f, 0.35f, 0.78f, 0.12f, + 0.003f, 0.15f, 0.20f, 0.10f, + 0.5f, 2, 0.40f, 0, + 2, 0.50f, 0.40f, + 0.90f, 0.0f, 1, 0.03f, 0, 0 } }, + // 63 Dub Rumble + { "Dub Rumble", "DUB", { O_SAW, O_SIN, -3, 65, 1, 380, 0.40f, 50, 10, + 0.004f, 0.30f, 0.82f, 0.10f, + 0.004f, 0.10f, 0.18f, 0.08f, + 1.2f, 3, 0.30f, 0, + 2, 0.55f, 0.45f, + 0.88f, 0.0f, 1, 0.04f, 0, 0 } }, + // 64 Dub Delay Bass + { "Dub Delay Bs", "DUB", { O_PUL, O_SIN, 0, 78, 1, 420, 0.50f, 55, 15, + 0.003f, 0.30f, 0.80f, 0.10f, + 0.003f, 0.10f, 0.20f, 0.08f, + 1.5f, 2, 0.35f, 0, + 2, 0.60f, 0.50f, + 0.90f, 0.0f, 1, 0.03f, 0, 0 } }, + // == CHORDS ============================================================ + // 65 Chord Stab + { "Chord Stab", "CHORDS", { O_SAW, O_SAW, 6, 55, 1, 3200, 0.20f, 40, 30, + 0.003f, 0.20f, 0.0f, 0.10f, + 0.002f, 0.10f, 0.02f, 0.08f, + 6.0f, 0, 0.05f, 2, + 1, 0.35f, 0.25f, + 0.84f, 0.0f, 12, 0.0f, 0, 0 } }, + // 66 Super Chord + { "Super Chord", "CHORDS", { O_SAW, O_PUL, 10, 50, 1, 2800, 0.18f, 45, 35, + 0.004f, 0.25f, 0.0f, 0.12f, + 0.003f, 0.12f, 0.03f, 0.10f, + 5.5f, 0, 0.06f, 2, + 1, 0.40f, 0.30f, + 0.84f, 0.0f, 12, 0.0f, 0, 0 } }, + // 67 Poly Brass + { "Poly Brass", "CHORDS", { O_SAW, O_SAW, 8, 52, 1, 3000, 0.25f, 35, 40, + 0.005f, 0.30f, 0.0f, 0.15f, + 0.004f, 0.15f, 0.05f, 0.12f, + 5.0f, 0, 0.08f, 2, + 1, 0.35f, 0.28f, + 0.82f, 0.0f, 12, 0.0f, 0, 0 } }, + // 68 Saw Stack + { "Saw Stack", "CHORDS", { O_SAW, O_SAW, 14, 55, 1, 2600, 0.15f, 50, 30, + 0.003f, 0.18f, 0.0f, 0.08f, + 0.002f, 0.08f, 0.02f, 0.06f, + 6.5f, 0, 0.04f, 2, + 1, 0.30f, 0.22f, + 0.82f, 0.0f, 12, 0.0f, 0, 0 } }, + // 69 Deep Chord + { "Deep Chord", "CHORDS", { O_TRI, O_SAW, 4, 50, 1, 2200, 0.22f, 55, 35, + 0.004f, 0.22f, 0.0f, 0.12f, + 0.003f, 0.10f, 0.03f, 0.10f, + 5.0f, 0, 0.06f, 2, + 1, 0.38f, 0.28f, + 0.84f, 0.0f, 12, 0.0f, 0, 0 } }, + // 70 Synth Stab + { "Synth Stab", "CHORDS", { O_PUL, O_SAW, 5, 58, 1, 3500, 0.30f, 35, 25, + 0.003f, 0.15f, 0.0f, 0.08f, + 0.002f, 0.08f, 0.02f, 0.06f, + 7.0f, 0, 0.04f, 2, + 1, 0.32f, 0.20f, + 0.86f, 0.0f, 12, 0.0f, 0, 0 } }, + // == SYNTH ============================================================= + // 71 Poly Sync + { "Poly Sync", "SYNTH", { O_SAW, O_PUL, 8, 55, 1, 3400, 0.25f, 40, 35, + 0.005f, 0.30f, 0.75f, 0.18f, + 0.004f, 0.15f, 0.25f, 0.12f, + 5.5f, 0, 0.12f, 0, + 1, 0.32f, 0.28f, + 0.84f, 0.0f, 10, 0.0f, 0, 0 } }, + // 72 Dark Synth + { "Dark Synth", "SYNTH", { O_SAW, O_SAW, 12, 48, 1, 2000, 0.35f, 60, 25, + 0.008f, 0.35f, 0.70f, 0.20f, + 0.006f, 0.20f, 0.30f, 0.15f, + 4.0f, 0, 0.15f, 0, + 1, 0.35f, 0.32f, + 0.82f, 0.0f, 10, 0.0f, 0, 0 } }, + // 73 Bright Synth + { "Bright Synth", "SYNTH", { O_SAW, O_TRI, 10, 50, 1, 4200, 0.15f, 30, 45, + 0.004f, 0.25f, 0.80f, 0.15f, + 0.003f, 0.12f, 0.28f, 0.10f, + 6.0f, 0, 0.10f, 1, + 1, 0.28f, 0.25f, + 0.85f, 0.0f, 10, 0.0f, 0, 0 } }, + // 74 Mono Synth + { "Mono Synth", "SYNTH", { O_PUL, O_SAW, 0, 60, 1, 3000, 0.30f, 45, 40, + 0.003f, 0.22f, 0.78f, 0.15f, + 0.003f, 0.10f, 0.25f, 0.10f, + 5.0f, 0, 0.12f, 1, + 0, 0.0f, 0.25f, + 0.85f, 0.0f, 1, 0.06f, 0, 0 } }, + // 75 Retro Synth + { "Retro Synth", "SYNTH", { O_SAW, O_PUL, 6, 52, 1, 2800, 0.22f, 38, 50, + 0.005f, 0.28f, 0.72f, 0.18f, + 0.004f, 0.14f, 0.22f, 0.12f, + 5.5f, 0, 0.10f, 0, + 1, 0.30f, 0.30f, + 0.84f, 0.0f, 10, 0.0f, 0, 0 } }, + // 76 Sine Synth + { "Sine Synth", "SYNTH", { O_SIN, O_TRI, 4, 60, 1, 3600, 0.18f, 35, 30, + 0.006f, 0.30f, 0.78f, 0.20f, + 0.005f, 0.18f, 0.28f, 0.15f, + 4.5f, 0, 0.10f, 0, + 1, 0.32f, 0.28f, + 0.84f, 0.0f, 10, 0.0f, 0, 0 } }, + // == ARP =============================================================== + // 77 Arp Saw + { "Arp Saw", "ARP", { O_SAW, O_SAW, 6, 55, 1, 3800, 0.20f, 35, 45, + 0.005f, 0.15f, 0.70f, 0.08f, + 0.004f, 0.10f, 0.20f, 0.08f, + 6.0f, 0, 0.10f, 0, + 1, 0.28f, 0.20f, + 0.84f, 0.0f, 10, 0.0f, 0, 0, + 1, 2, 0, 2, 70 } }, + // 78 Arp Pulse + { "Arp Pulse", "ARP", { O_PUL, O_PUL, 4, 58, 1, 3200, 0.25f, 40, 50, + 0.004f, 0.12f, 0.65f, 0.06f, + 0.003f, 0.08f, 0.18f, 0.06f, + 7.0f, 0, 0.08f, 0, + 1, 0.30f, 0.18f, + 0.84f, 0.0f, 10, 0.0f, 0, 0, + 1, 2, 0, 2, 70 } }, + // 79 Arp Sine + { "Arp Sine", "ARP", { O_SIN, O_TRI, 0, 70, 1, 4000, 0.15f, 30, 35, + 0.003f, 0.10f, 0.60f, 0.05f, + 0.002f, 0.06f, 0.15f, 0.05f, + 5.5f, 0, 0.06f, 0, + 1, 0.25f, 0.15f, + 0.86f, 0.0f, 10, 0.0f, 0, 0, + 1, 1, 2, 2, 60 } }, + // 80 Arp Space + { "Arp Space", "ARP", { O_TRI, O_SAW, 8, 52, 1, 3000, 0.22f, 45, 40, + 0.005f, 0.18f, 0.70f, 0.10f, + 0.004f, 0.12f, 0.22f, 0.08f, + 5.0f, 0, 0.12f, 0, + 3, 0.45f, 0.40f, + 0.82f, 0.0f, 10, 0.0f, 0, 0, + 1, 4, 0, 2, 70 } }, + // 81 Arp Trance + { "Arp Trance", "ARP", { O_SAW, O_SAW, 10, 50, 1, 3500, 0.18f, 40, 50, + 0.004f, 0.12f, 0.72f, 0.08f, + 0.003f, 0.08f, 0.20f, 0.06f, + 8.0f, 0, 0.10f, 0, + 2, 0.40f, 0.35f, + 0.84f, 0.0f, 10, 0.0f, 0, 0, + 1, 2, 0, 3, 60 } }, + // 82 Arp Square + { "Arp Square", "ARP", { O_PUL, O_SAW, 0, 65, 1, 3000, 0.30f, 35, 45, + 0.003f, 0.10f, 0.65f, 0.05f, + 0.002f, 0.06f, 0.18f, 0.05f, + 7.5f, 0, 0.06f, 0, + 1, 0.30f, 0.18f, + 0.85f, 0.0f, 10, 0.0f, 0, 0, + 1, 2, 1, 2, 60 } }, + // 83 Arp Pluck + { "Arp Pluck", "ARP", { O_TRI, O_TRI, 3, 60, 1, 3200, 0.18f, 30, 40, + 0.002f, 0.08f, 0.55f, 0.04f, + 0.002f, 0.05f, 0.15f, 0.04f, + 6.5f, 0, 0.05f, 0, + 1, 0.28f, 0.15f, + 0.86f, 0.0f, 10, 0.0f, 0, 0, + 1, 1, 2, 2, 50 } }, + // 84 Moon Pad + { "Moon Pad", "POLY PAD", { O_SIN, O_TRI, 14, 55, 1, 2200, 0.30f, 55, 25, + 1.5f, 2.8f, 0.88f, 3.2f, + 1.0f, 2.0f, 0.40f, 2.2f, + 0.4f, 1, 0.20f, 0, + 3, 0.50f, 0.55f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // 85 Orchestral + { "Orchestral", "STRINGS", { O_SAW, O_PUL, 8, 55, 1, 2400, 0.12f, 60, 55, + 0.30f, 1.1f, 0.80f, 1.8f, + 0.20f, 0.70f, 0.38f, 0.60f, + 3.6f, 0, 0.10f, 0, + 3, 0.35f, 0.45f, + 0.82f, 0.0f, 16, 0.0f, 0, 0 } }, + // 86 Arp Bubble + { "Arp Bubble", "ARP", { O_TRI, O_SIN, 0, 70, 1, 3800, 0.12f, 25, 30, + 0.002f, 0.08f, 0.55f, 0.04f, + 0.002f, 0.05f, 0.12f, 0.04f, + 7.5f, 0, 0.05f, 0, + 1, 0.25f, 0.12f, + 0.86f, 0.0f, 10, 0.0f, 0, 0, + 1, 1, 3, 2, 50 } }, + // 87 Arp Pyramid + { "Arp Pyramid", "ARP", { O_PUL, O_SAW, 0, 62, 1, 3000, 0.25f, 30, 45, + 0.003f, 0.10f, 0.60f, 0.05f, + 0.002f, 0.06f, 0.15f, 0.05f, + 6.0f, 0, 0.06f, 0, + 1, 0.28f, 0.15f, + 0.85f, 0.0f, 10, 0.0f, 0, 0, + 1, 3, 2, 3, 60 } }, + // 88 Arp Sweep + { "Arp Sweep", "ARP", { O_SAW, O_SAW, 8, 55, 1, 3400, 0.20f, 45, 35, + 0.004f, 0.15f, 0.68f, 0.08f, + 0.003f, 0.10f, 0.20f, 0.06f, + 5.5f, 0, 0.10f, 0, + 3, 0.42f, 0.35f, + 0.83f, 0.0f, 10, 0.0f, 0, 0, + 1, 4, 0, 2, 65 } }, + // 89 Arp Chimes + { "Arp Chimes", "ARP", { O_SIN, O_TRI, 12, 60, 1, 4200, 0.25f, 35, 30, + 0.002f, 0.06f, 0.50f, 0.03f, + 0.002f, 0.04f, 0.10f, 0.03f, + 6.0f, 0, 0.04f, 0, + 3, 0.38f, 0.40f, + 0.84f, 0.0f, 10, 0.0f, 0, 0, + 1, 2, 2, 2, 45 } }, + // 90 Arp Midnight + { "Arp Midnight", "ARP", { O_PUL, O_PUL, 6, 55, 1, 2600, 0.30f, 40, 40, + 0.004f, 0.12f, 0.62f, 0.06f, + 0.003f, 0.08f, 0.18f, 0.05f, + 5.0f, 0, 0.08f, 0, + 2, 0.35f, 0.48f, + 0.85f, 0.0f, 10, 0.0f, 0, 0, + 1, 2, 1, 2, 55 } }, + // 91 Arp Cascade + { "Arp Cascade", "ARP", { O_SAW, O_TRI, 10, 52, 1, 3200, 0.15f, 35, 45, + 0.003f, 0.10f, 0.65f, 0.06f, + 0.003f, 0.06f, 0.20f, 0.05f, + 7.0f, 0, 0.06f, 0, + 1, 0.30f, 0.28f, + 0.85f, 0.0f, 10, 0.0f, 0, 0, + 1, 2, 0, 3, 65 } }, }; PresetBank::PresetBank() { patches.assign (defs, defs + juce::numElementsInArray (defs)); + buildCategoryIndex(); +} + +void PresetBank::buildCategoryIndex() +{ + catIndex.names.clear(); + catIndex.patchIndices.clear(); + + for (int i = 0; i < (int) patches.size(); ++i) + { + const juce::String cat (patches[i].category); + int idx = catIndex.names.indexOf (cat); + if (idx < 0) + { + idx = catIndex.names.size(); + catIndex.names.add (cat); + catIndex.patchIndices.push_back ({}); + } + catIndex.patchIndices[(size_t) idx].push_back (i); + } } int PresetBank::findByName (const juce::String& name) const diff --git a/Source/PresetBank.h b/Source/PresetBank.h index 84cc295..104896d 100644 --- a/Source/PresetBank.h +++ b/Source/PresetBank.h @@ -10,6 +10,12 @@ struct Patch float v[prm::num]; }; +struct CategoryIndex +{ + juce::StringArray names; + std::vector> patchIndices; // parallel to names +}; + class PresetBank { public: @@ -21,6 +27,10 @@ public: void writeTo (int i, juce::AudioProcessorValueTreeState&) const; + const CategoryIndex& categories() const { return catIndex; } + private: std::vector patches; + CategoryIndex catIndex; + void buildCategoryIndex(); }; \ No newline at end of file diff --git a/Source/SynthEngine.cpp b/Source/SynthEngine.cpp index 5290565..984bfb0 100644 --- a/Source/SynthEngine.cpp +++ b/Source/SynthEngine.cpp @@ -59,7 +59,13 @@ SynthEngine::Voice* SynthEngine::acquireVoice() return &voices[i]; stealIdx = (stealIdx + 1) % lim; - return &voices[stealIdx]; + Voice* v = &voices[stealIdx]; + if (v->active) + { + v->amp.noteOff(); + v->flt.noteOff(); + } + return v; } void SynthEngine::noteOn (int midiNote, float velocity) @@ -121,11 +127,16 @@ void SynthEngine::render (const VParams& p, float* left, float* right, int num) : 1.0f - std::exp (-1.0f / (p.glide * (float) sr)); const float detuneRatio = std::exp2f (p.detune / 1200.0f); const float tuneRatio = std::exp2f (p.tune / 12.0f); - const float maxFc = 18000.0f, minFc = 30.0f; + const float maxFc = 0.4f * (float) sr; + const float minFc = 30.0f; for (int s = 0; s < num; ++s) { float mixL = 0.0f, mixR = 0.0f; + int activeCount = 0; + for (auto& v : voices) + if (v.active) ++activeCount; + const float voiceGain = activeCount > 1 ? 1.0f / std::sqrtf ((float) activeCount) : 1.0f; for (auto& v : voices) { @@ -213,7 +224,7 @@ void SynthEngine::render (const VParams& p, float* left, float* right, int num) ampMod = 1.0f - p.lfoDepth * 0.5f * (1.0f + lfo); v.outSmooth += (filtered - v.outSmooth) * 0.12f; - float o = v.outSmooth * (ae * ampMod) * v.vel; + float o = v.outSmooth * (ae * ampMod) * v.vel * voiceGain; if (! std::isfinite (o)) o = 0.0f; mixL += o * v.panL; @@ -228,8 +239,10 @@ void SynthEngine::render (const VParams& p, float* left, float* right, int num) if (! std::isfinite (mixL)) mixL = 0.0f; if (! std::isfinite (mixR)) mixR = 0.0f; - mixL = std::tanh (mixL); - mixR = std::tanh (mixR); + + // Soft limiter before tanh for cleaner saturation + mixL = std::tanh (mixL * 0.8f); + mixR = std::tanh (mixR * 0.8f); left[s] += mixL; right[s] += mixR; diff --git a/Source/SynthEngine.h b/Source/SynthEngine.h index a2d54ec..38a17df 100644 --- a/Source/SynthEngine.h +++ b/Source/SynthEngine.h @@ -12,6 +12,7 @@ struct VParams float fxType = 1, fxAmt = 0.30f, fxTime = 0.28f; float vol = 0.85f, pan = 0.0f, poly = 10.f, glide = 0.f, tune = 0.f; float hq = 0.0f; + float arpOn = 0, arpRate = 2, arpPattern = 0, arpOct = 1, arpGate = 70; void update (const juce::AudioProcessorValueTreeState& apvts) { @@ -30,6 +31,11 @@ struct VParams vol = get ("vol"); pan = get ("pan"); poly = get ("poly"); glide = get ("glide"); tune = get ("tune"); hq = get ("hq"); + arpOn = get ("arpon"); + arpRate = get ("arprate"); + arpPattern = get ("arppattern"); + arpOct = get ("arpoct"); + arpGate = get ("arpgate"); } }; diff --git a/b2.png b/b2.png deleted file mode 100644 index 1d89fcd..0000000 Binary files a/b2.png and /dev/null differ diff --git a/b3.png b/b3.png new file mode 100644 index 0000000..f7b8718 Binary files /dev/null and b/b3.png differ diff --git a/shot.png b/shot.png index e6943ad..4dfb4ab 100644 Binary files a/shot.png and b/shot.png differ diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..dc4aa5d --- /dev/null +++ b/todo.txt @@ -0,0 +1,3 @@ +- make sure all presets use lowpass, not bandpass, also check why bandpass makes the overall volume MUCH lower +- move all submenus under "settings" into the main menu; make a new submenu for the patches, and put all the patches categories in there +