From d4d3b27e978ed3e2ec847b48dc6011972d77d7a8 Mon Sep 17 00:00:00 2001 From: ed Date: Sun, 26 Oct 2025 10:35:23 +0000 Subject: [PATCH] Upload files to "DrawWavesAndRevisedAudio" --- DrawWavesAndRevisedAudio/PluginProcessor.h | 143 +++++++++ DrawWavesAndRevisedAudio/PresetsCode.cpp | 331 +++++++++++++++++++++ 2 files changed, 474 insertions(+) create mode 100644 DrawWavesAndRevisedAudio/PluginProcessor.h create mode 100644 DrawWavesAndRevisedAudio/PresetsCode.cpp diff --git a/DrawWavesAndRevisedAudio/PluginProcessor.h b/DrawWavesAndRevisedAudio/PluginProcessor.h new file mode 100644 index 0000000..1859c3b --- /dev/null +++ b/DrawWavesAndRevisedAudio/PluginProcessor.h @@ -0,0 +1,143 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// ============================================================ +// A wave frame: 2048 samples with six mip levels (0 = full bandwidth) +struct WavetableFrame +{ + std::array, 6> mip; +}; + +// A complete morph set contains 16 frames +struct WaveMorph +{ + std::array frames; +}; + +// ============================================================ + +class WavetableSynthAudioProcessor : public juce::AudioProcessor +{ +public: + // -------- engine configuration ---------- + static constexpr int kBrowserCols = 4; + static constexpr int kBrowserRows = 10; + static constexpr int kBrowserCapacity = kBrowserCols * kBrowserRows; // 40 + static constexpr int kFactorySlots = 20; // first 20 fixed + static constexpr int kMorphFrames = 16; + static constexpr int kMipLevels = 6; + static constexpr int kTableSize = 2048; + + // --- minimal editor accessors (public) ------------------------------------- + int getBrowserCols() const noexcept { return kBrowserCols; } + int getBrowserRows() const noexcept { return kBrowserRows; } + int getWaveTableCount() const noexcept { return (int) waves.size(); } + const std::vector* getWaveTablePtr (int index) const noexcept + { + // Preview uses the first frame at the widest mip level. + return (index >= 0 && index < (int) waves.size()) + ? &waves[(size_t) index].frames[0].mip[0] : nullptr; + } + + // -------- juce plumbing ---------- + WavetableSynthAudioProcessor(); + ~WavetableSynthAudioProcessor() override = default; + + // AudioProcessor + void prepareToPlay (double sampleRate, int samplesPerBlock) override; + void releaseResources() override {} + void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; + + // Editor + juce::AudioProcessorEditor* createEditor() override; + bool hasEditor() const override { return true; } + + // Meta + const juce::String getName() const override { return "WavetableSynth"; } + bool acceptsMidi() const override { return true; } + bool producesMidi() const override { return false; } + bool isMidiEffect() const override { return false; } + double getTailLengthSeconds() const override { return 0.0; } + + // Programs (unused) + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return {}; } + void changeProgramName (int, const juce::String&) override {} + + // State + void getStateInformation (juce::MemoryBlock& destData) override; + void setStateInformation (const void* data, int sizeInBytes) override; + + // Parameters + juce::AudioProcessorValueTreeState apvts; + static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); + + // -------- public data used by editor ---------- + std::vector waves; // size <= 40; first 20 are factory + int defaultTableCount { kFactorySlots }; + int nextUserInsert { 0 }; // round-robin inside user region + + // For the editor thumbnails (take the widest-band level 0) + const std::vector* getPreviewTablePtr (int index) const; + + // API for editor to push user waves (single cycle, any shape) + // Internally this builds a mip set by partial truncation. + int addOrReplaceUserWavetable (const std::vector& singleCycle01); + void notifyPresetLoaded(); + float getMorphDisplayValue() const noexcept { return morphDisplay.load (std::memory_order_relaxed); } + bool isMorphLoopActive() const noexcept; + +private: + // ---------- synthesis core ---------- + juce::Synthesiser synth; + std::vector morphBuffer; + float morphState { 0.0f }; + float morphLoopPhase { 0.0f }; + int morphLoopDirection { 1 }; + int morphLoopStage { 0 }; + float morphLoopStagePhase { 0.0f }; + std::atomic morphDisplay { 0.0f }; + juce::LinearSmoothedValue presetFade { 1.0f }; + + juce::dsp::Chorus chorus; + juce::dsp::Reverb reverb; + juce::dsp::Reverb::Parameters reverbParams; + + // -------- wave construction helpers ---------- + static void normalize (std::vector& t); + static void addSine (std::vector& t, int harmonic, float amp); + static void removeDC (std::vector& t); + static void enforceZeroStart (std::vector& t); + static WaveMorph buildAdditiveMorph (std::function ampFn, + bool oddOnly = false, + float phaseAlt = +1.0f); + static WaveMorph makeSine(); + static WaveMorph makeSaw(); + static WaveMorph makeSquare(); + static WaveMorph makeTriangle(); + static WaveMorph makePulse (float duty); // 0 +#include +#include +#include +#include + +namespace PresetsCode +{ + struct Preset { juce::String name; std::map values; }; + struct Category{ juce::String name; std::vector presets; }; + + namespace detail + { + constexpr int presetsPerCategory = 30; + constexpr int modeVariantCount = presetsPerCategory / 2; + + using SlotSet = std::array; + using SlotTriplet = std::array; + + struct GMProfile + { + juce::String name; + float master; + float morph; + float cutoff; + float attack; + float decay; + float sustain; + float release; + float lfoRate; + float lfoDepth; + float fEnvAmount; + float fxMix; + SlotTriplet slotSets; + }; + + struct CategoryNameSet + { + std::array solo; + std::array layered; + }; + + static constexpr std::array nameSets + {{ + CategoryNameSet{ + { "Concert Grand", "Studio Upright", "Warm Parlor", "Bright Stage", "Dusty Keys", + "Midnight Felt", "Glass Hammer", "Sunlit Room", "Jazz Touch", "Ringing Octaves", + "Soft Pedal", "Pop Punch", "Wide Hammer", "Twilight Piano", "Crystal Keys" }, + { "Grand Stack", "Hammer Chorus", "Layered Uprights", "Glassy Spread", "Warm Hall Blend", + "Hybrid Grand", "Bright Layer", "Felt Cloud", "Cinema Piano", "Dream Keys", + "Bell Piano", "Wide Hammered", "Velvet Layers", "Studio Swirl", "Concert Bloom" } + }, // Piano + CategoryNameSet{ + { "Solo Vibraphone", "Celesta Light", "Glassy Chimes", "Soft Mallets", "Toy Box", + "Crystal Bars", "Marimba Glow", "Bell Cascade", "Clav Accent", "Metal Steps", + "Trem Vibra", "Dry Mallet", "Shimmer Keys", "Clockwork", "Glass Struck" }, + { "Mallet Ensemble", "Celesta Pad", "Chime Stack", "Bell Layers", "Crystal Curtain", + "Hybrid Mallets", "Wide Glock", "Mallet Dream", "Spectral Bells", "Golden Bars", + "Bright Resonance", "Airy Mallets", "Wide Celesta", "Fantasy Chimes", "Silver Rain" } + }, // Chromatic Percussion + CategoryNameSet{ + { "Pure Drawbar", "Chapel Soft", "Perc Click", "Full Stops", "Hollow Tone", + "Jazz Wheel", "Old Console", "Far Bar", "Vox Accent", "Tremolo Pipes", + "Slow Leslie", "Rock Bars", "Warm Console", "Combo Organ", "Ancient Reed" }, + { "Twin Drawbars", "Cathedral Stack", "Rotary Blend", "Organ Swell", "Thick Console", + "Wide Vox", "Dual Chapel", "Gospel Layers", "Airy Pipes", "Hybrid Harm", + "Bright Rotary", "Deep Drawbar", "Vintage Stack", "Layered Stops", "Electric Chapel" } + }, // Organ + CategoryNameSet{ + { "Clean Pluck", "Bright Strum", "Muted Pick", "Hollow Body", "Steel Shimmer", + "Warm Nylon", "Slide Touch", "Jazz Octave", "Edge Pluck", "Dry Funk", + "Airy Harmonics", "Silver Strings", "Wooden Pick", "Folk Finger", "Soft Trem" }, + { "Guitar Stack", "Stereo Strum", "Pluck Ensemble", "Wide Harmonics", "Chorus Nylon", + "Electric Blend", "Picked Layers", "Dream Guitar", "Shimmer Strings", "Deep Strummer", + "Twin Slides", "Wide Trem", "Hybrid Pluck", "Sparkle Guitar", "Ambient Strum" } + }, // Guitar + CategoryNameSet{ + { "Fingered Bass", "Picked Attack", "Deep Sub", "Warm Round", "Clav Bass", + "Snappy Slap", "Muted Thump", "Synth Pluck", "Punchy Low", "Airless Bass", + "Moog Tone", "Dry Root", "Soft Sub", "Acoustic Body", "Neo Bass" }, + { "Layered Low", "Sub Stack", "Punch Blend", "Bass Split", "Wide Slap", + "Deep Synth", "Hybrid Bass", "Chorus Low", "Twin Thump", "Boom Texture", + "Bass Ensemble", "Stereo Sub", "Growl Stack", "Fundamental Wall", "Airy Sub" } + }, // Bass + CategoryNameSet{ + { "Solo Violin", "Warm Viola", "Cello Bow", "Silk Section", "Muted Strings", + "Spiccato", "Wide Legato", "Bright Ensemble", "Soft Layer", "Orchestral Pad", + "Tremolo Lace", "Vintage Strings", "Glass Harmonics", "Warm Stack", "Solo Cello" }, + { "String Layers", "Cinematic Swell", "Wide Arcs", "Orchestral Cloud", "Hybrid Strings", + "Chamber Blend", "Symphonic Rise", "Dream Swell", "Shimmer Strings", "Soft Orchestra", + "Glass Orchestra", "Air Ensemble", "Epic Layer", "Velvet Section", "Neo Strings" } + }, // Strings + CategoryNameSet{ + { "Light Ensemble", "Soft Choir", "Mixed Section", "Warm Stack", "Bright Choir", + "Octave Layers", "Air Voices", "Analog Ensemble", "Voiced Pad", "Stacked Strings", + "Gentle Chorus", "Full Section", "Ethereal Group", "Pop Chorus", "Glass Ensemble" }, + { "Cathedral Ensemble", "Choir Spread", "Hybrid Voices", "Epic Stack", "Layered Choir", + "Wide Ensemble", "Dream Choir", "Symphonic Pad", "Lush Assembly", "Stereo Ensemble", + "Glorious Layer", "Angelic Swell", "Velvet Choir", "Bright Assembly", "Atmos Ensemble" } + }, // Ensemble + CategoryNameSet{ + { "Solo Trumpet", "Muted Brass", "Warm Horn", "Bright Brass", "Soft Flugel", + "Tenor Bones", "Sharp Brass", "French Stack", "Hero Lead", "Vintage Brass", + "Orchestral Horn", "Fan Brass", "Soft Cornet", "Bold Bones", "Hybrid Brass" }, + { "Brass Wall", "Epic Horns", "Stadium Brass", "Layered Trumpets", "Octave Brass", + "Brass Ensemble", "Hero Stack", "Warm Brass Pad", "Cinema Brass", "Wide Brass", + "Glory Brass", "Tone Tower", "Bright Brass Pad", "Deep Horn Stack", "Hybrid Fanfare" } + }, // Brass + CategoryNameSet{ + { "Solo Clarinet", "Warm Sax", "Bright Reed", "Soft Oboe", "Jazz Alto", + "Bassoon Tone", "Smooth Tenor", "Piercing Reed", "Dry Clar", "Wide Reed", + "Dark Oboe", "Airy Bassoon", "Pop Sax", "Velvet Reed", "Vintage Clar" }, + { "Reed Ensemble", "Twin Sax", "Clarinet Choir", "Hybrid Reed", "Smooth Section", + "Layered Oboes", "Wide Reed Pad", "Cinema Reed", "Stacked Sax", "Woodwind Wall", + "Airy Reed", "Jazz Stack", "Bright Reeds", "Shadow Reed", "Velvet Woodwind" } + }, // Reed + CategoryNameSet{ + { "Concert Flute", "Breathy Flute", "Piccolo Star", "Soft Recorder", "Glass Flute", + "Warm Alto", "Fife Tone", "Wind Whisper", "Sharp Pipe", "Native Flute", + "Bright Piccolo", "Calm Pipes", "Open Whistle", "Night Flute", "Ancient Pipe" }, + { "Pipe Ensemble", "Choir Flutes", "Layered Wind", "Dream Flutes", "Wide Pipes", + "Ethereal Flutes", "Airy Stack", "Silver Cascade", "Mystic Flutes", "Breath Layers", + "Stereo Pipes", "Hybrid Flutes", "Clouded Pipes", "Shining Wind", "Sky Columns" } + }, // Pipe + CategoryNameSet{ + { "Blade Lead", "Sync Razor", "Analog Sizzle", "Digital Edge", "Saw Glide", + "Pulse Sprint", "FM Piercer", "Reso Runner", "Mono Glide", "Square Force", + "Acid Beam", "Metal Lead", "Chiptone", "Warm Solo", "Vox Glide" }, + { "Twin Blades", "Stereo Lead", "Octave Runner", "Layered Saw", "Wide Screamer", + "Poly Stack", "Hybrid Rush", "Air Lead", "Reso Layers", "Arena Solo", + "Neon Drive", "Cosmic Lead", "Lucid Glide", "Turbo Layers", "Sky Blade" } + }, // Synth Lead + CategoryNameSet{ + { "Warm Blanket", "Glass Pad", "Analog Mist", "Digital Halo", "Soft Spread", + "Bright Pad", "Choral Sweep", "Nocturne Pad", "Vintage Pad", "FM Veil", + "Dusty Pad", "Air Sweep", "Silk Bed", "Calm Horizon", "Wide Prism" }, + { "Pad Cathedral", "Luminous Pad", "Dream Orbit", "Velvet Layers", "Motion Cloud", + "Evolving Pad", "Glass Ensemble", "Solar Drift", "Shimmer Sky", "Deep Nebula", + "Aurora Pad", "Liquid Glow", "Expanse Pad", "Twin Horizons", "Ethereal Bloom" } + }, // Synth Pad + CategoryNameSet{ + { "Rise Sweep", "Noise Hit", "Circuit Zap", "Reso Drop", "Granular Wash", + "Bit Spark", "Gated Rush", "Pulse Radar", "Morph Click", "Glitch Line", + "Bleed Pad", "Searing Rise", "Swarm FX", "Vapor Hit", "Spectra Pop" }, + { "FX Cascade", "Binary Storm", "Shimmer FX", "Impact Stack", "Laser Field", + "Wave Crash", "Dual Sweep", "Horizon FX", "Voltage Rain", "Sky Burst", + "Nebula FX", "Falling Stars", "Circuit Bloom", "Phase Impact", "Aurora FX" } + }, // Synth Effects + CategoryNameSet{ + { "Sitar Pluck", "Shamisen Tone", "Kalimba Bell", "Gamelan Fan", "Erhu Bow", + "Santur Ring", "Kora Waves", "Baglama Run", "Oud Mellow", "Bansuri Air", + "Tin Whistle", "Didgeridoo Drone", "Taiko Rim", "Ngoni Pulse", "Guqin Flow" }, + { "World Layers", "Tribal Echo", "Desert Wind", "Eastern Stack", "Cultural Bloom", + "Panorama Folk", "Nomad Chorus", "Festival Mix", "Temple Air", "Jungle Drift", + "Azure Bazaar", "Wide Kalimba", "Storm Ritual", "Mystic Caravan", "Earth Ensemble" } + }, // Ethnic + CategoryNameSet{ + { "Analog Kick", "Tight Snare", "Electro Tom", "Clack Hit", "Wood Block", + "Glass Ping", "Tribal Drum", "Metal Knock", "Click Perc", "Pop Rim", + "Snap Clap", "Deep Boom", "Air Hit", "Short Noise", "Soft Knock" }, + { "Perc Stack", "Rhythm Wall", "Hybrid Drum", "Layered Hit", "Wide Impact", + "Perc Texture", "Motion Perc", "Tribal Layers", "Circuit Beat", "Wide Clap", + "Perc Storm", "Drum Machine", "Stage Impact", "Thunder Perc", "Break Stack" } + }, // Percussive + CategoryNameSet{ + { "Wind Rush", "Ocean Drone", "Metallic Fall", "SciFi Ping", "Air Sweep", + "Machine Loop", "Space Beep", "Rumble FX", "Glass Growl", "Signal Warp", + "Foley Hit", "Distant Boom", "Echo Drops", "Industrial Spin", "Night Noise" }, + { "FX Universe", "Storm Front", "Space Layers", "Dread Drones", "Cinematic Field", + "Signal Swarm", "Nightscape", "Future Machine", "Thunder Field", "Layered Noise", + "Alien Atmos", "Mystery Floor", "Flash Impact", "Vortex FX", "Deep Horizon" } + } // Sound Effects + }}; + + inline juce::String makePresetName (const GMProfile& profile, + size_t profileIndex, + int variantIndex, + bool bitimbral) + { + const int localIndex = bitimbral ? (variantIndex - modeVariantCount) : variantIndex; + const auto& descriptorSet = bitimbral ? nameSets[(size_t) profileIndex].layered + : nameSets[(size_t) profileIndex].solo; + const size_t clamped = (size_t) juce::jlimit (0, (int) descriptorSet.size() - 1, localIndex); + return profile.name + " " + juce::String (descriptorSet[clamped]); + } + + inline Preset makePreset (const juce::String& name, + const GMProfile& profile, + int variantIndex, + bool bitimbral) + { + const float spread = static_cast ((variantIndex % 5) - 2); + + const float master = juce::jlimit (0.50f, 0.95f, profile.master + spread * 0.01f); + const float morph = juce::jlimit (0.00f, 1.00f, profile.morph + spread * 0.02f + (bitimbral ? 0.06f : -0.04f)); + const float cutoff = juce::jlimit (250.0f, 18000.0f, profile.cutoff + 320.0f * static_cast ((variantIndex % 7) - 3)); + const float attack = juce::jlimit (0.001f, 4.000f, profile.attack * (1.0f + spread * 0.03f)); + const float decay = juce::jlimit (0.050f, 3.500f, profile.decay * (1.0f + spread * 0.04f)); + const float sustain = juce::jlimit (0.00f, 1.00f, profile.sustain + spread * 0.02f); + const float release = juce::jlimit (0.050f, 4.000f, profile.release * (1.0f + spread * 0.05f)); + const float lfoRate = juce::jlimit (0.050f, 8.000f, profile.lfoRate + 0.12f * static_cast ((variantIndex % 8) - 3)); + const float lfoDepth = juce::jlimit (0.00f, 1.00f, profile.lfoDepth + spread * 0.03f); + const float fEnvAmt = juce::jlimit (0.10f, 0.90f, profile.fEnvAmount + spread * 0.02f); + const float fxMix = juce::jlimit (0.00f, 1.00f, profile.fxMix + spread * 0.04f); + + const float chRate = juce::jlimit (0.20f, 1.80f, 0.55f + fxMix * 0.80f + spread * 0.03f); + const float chDepth = juce::jlimit (0.00f, 1.00f, 0.18f + fxMix * 0.55f); + const float chDelay = juce::jlimit (5.00f, 20.00f, 8.0f + fxMix * 6.5f + (bitimbral ? 1.5f : -1.0f)); + const float chFb = juce::jlimit (0.00f, 0.60f, 0.04f + fxMix * 0.14f); + const float chMix = juce::jlimit (0.00f, 0.60f, fxMix * 0.45f); + + const float rvRoom = juce::jlimit (0.00f, 1.00f, 0.28f + fxMix * 0.62f); + const float rvDamp = juce::jlimit (0.00f, 1.00f, 0.20f + fxMix * 0.40f); + const float rvWidth = juce::jlimit (0.00f, 1.00f, 0.82f + fxMix * 0.15f); + const float rvWet = juce::jlimit (0.00f, 1.00f, 0.12f + fxMix * 0.36f); + + const auto& slotChoice = profile.slotSets[(size_t) (variantIndex % profile.slotSets.size())]; + + std::map values + { + { "MASTER", master }, + { "MORPH", morph }, + { "MORPH_LOOP_ON", (bitimbral || (variantIndex % 3 == 0)) ? 1.0f : 0.0f }, + { "MORPH_LOOP_MODE", static_cast ((variantIndex / 5) % 3) }, + { "CUTOFF", cutoff }, + { "ATTACK", attack }, + { "DECAY", decay }, + { "SUSTAIN", sustain }, + { "RELEASE", release }, + { "LFO_RATE", lfoRate }, + { "LFO_DEPTH", lfoDepth }, + { "FENV_A", juce::jlimit (0.001f, 2.000f, attack * 0.55f) }, + { "FENV_D", juce::jlimit (0.050f, 2.400f, decay * 1.05f) }, + { "FENV_S", juce::jlimit (0.00f, 1.00f, sustain * 0.48f) }, + { "FENV_R", juce::jlimit (0.050f, 2.800f, release * 1.15f) }, + { "FENV_AMT", fEnvAmt }, + { "OSC2_MUTE", bitimbral ? 0.0f : 1.0f }, + { "CHORUS_ON", fxMix > 0.32f ? 1.0f : 0.0f }, + { "REVERB_ON", fxMix > 0.18f ? 1.0f : 0.0f }, + { "CH_RATE", chRate }, + { "CH_DEPTH", chDepth }, + { "CH_DELAY", chDelay }, + { "CH_FB", chFb }, + { "CH_MIX", chMix }, + { "RV_ROOM", rvRoom }, + { "RV_DAMP", rvDamp }, + { "RV_WIDTH", rvWidth }, + { "RV_WET", rvWet }, + { "SLOT_A", (float) slotChoice[0] }, + { "SLOT_B", (float) slotChoice[1] }, + { "SLOT_C", (float) slotChoice[2] } + }; + + return { name, std::move (values) }; + } + } // namespace detail + + // ----------------------- Library ----------------------------------------- + inline std::vector getCategories() + { + using namespace detail; + + const std::array gmProfiles + {{ + { "Piano", 0.72f, 0.18f, 6800.0f, 0.020f, 0.280f, 0.65f, 0.320f, 0.40f, 0.22f, 0.35f, 0.28f, + SlotTriplet{ SlotSet{ 0, 3, 11 }, SlotSet{ 0, 9, 15 }, SlotSet{ 0, 3, 19 } } }, + { "Chromatic Percussion",0.68f, 0.22f, 7200.0f, 0.012f, 0.240f, 0.50f, 0.300f, 0.45f, 0.30f, 0.42f, 0.24f, + SlotTriplet{ SlotSet{ 11, 12, 14 }, SlotSet{ 11, 9, 13 }, SlotSet{ 11, 12, 5 } } }, + { "Organ", 0.70f, 0.55f, 8400.0f, 0.040f, 0.480f, 0.80f, 0.600f, 0.90f, 0.40f, 0.28f, 0.35f, + SlotTriplet{ SlotSet{ 10, 7, 0 }, SlotSet{ 10, 1, 2 }, SlotSet{ 10, 5, 8 } } }, + { "Guitar", 0.66f, 0.32f, 6200.0f, 0.015f, 0.450f, 0.55f, 0.450f, 0.65f, 0.35f, 0.40f, 0.30f, + SlotTriplet{ SlotSet{ 3, 4, 12 }, SlotSet{ 3, 6, 1 }, SlotSet{ 3, 4, 9 } } }, + { "Bass", 0.75f, 0.25f, 2800.0f, 0.008f, 0.300f, 0.78f, 0.220f, 0.30f, 0.25f, 0.55f, 0.18f, + SlotTriplet{ SlotSet{ 0, 6, 4 }, SlotSet{ 0, 5, 12 }, SlotSet{ 0, 6, 14 } } }, + { "Strings", 0.72f, 0.48f, 5600.0f, 0.180f, 0.850f, 0.70f, 1.100f, 0.28f, 0.60f, 0.36f, 0.45f, + SlotTriplet{ SlotSet{ 3, 11, 15 }, SlotSet{ 3, 9, 18 }, SlotSet{ 3, 11, 0 } } }, + { "Ensemble", 0.74f, 0.50f, 5400.0f, 0.200f, 0.900f, 0.72f, 1.400f, 0.32f, 0.58f, 0.40f, 0.48f, + SlotTriplet{ SlotSet{ 11, 1, 0 }, SlotSet{ 11, 18, 15 }, SlotSet{ 11, 7, 3 } } }, + { "Brass", 0.70f, 0.42f, 7800.0f, 0.060f, 0.550f, 0.68f, 0.500f, 0.45f, 0.30f, 0.50f, 0.30f, + SlotTriplet{ SlotSet{ 2, 10, 15 }, SlotSet{ 2, 11, 1 }, SlotSet{ 2, 9, 13 } } }, + { "Reed", 0.68f, 0.34f, 7600.0f, 0.050f, 0.480f, 0.65f, 0.580f, 0.52f, 0.38f, 0.44f, 0.28f, + SlotTriplet{ SlotSet{ 8, 10, 0 }, SlotSet{ 8, 9, 3 }, SlotSet{ 8, 5, 11 } } }, + { "Pipe", 0.67f, 0.38f, 7200.0f, 0.100f, 0.600f, 0.70f, 0.900f, 0.40f, 0.32f, 0.36f, 0.32f, + SlotTriplet{ SlotSet{ 0, 9, 11 }, SlotSet{ 0, 9, 18 }, SlotSet{ 0, 9, 5 } } }, + { "Synth Lead", 0.73f, 0.62f, 12800.0f, 0.015f, 0.320f, 0.78f, 0.260f, 1.20f, 0.55f, 0.48f, 0.38f, + SlotTriplet{ SlotSet{ 1, 2, 5 }, SlotSet{ 1, 14, 6 }, SlotSet{ 1, 2, 13 } } }, + { "Synth Pad", 0.76f, 0.58f, 6500.0f, 0.350f, 1.100f, 0.75f, 2.100f, 0.22f, 0.60f, 0.40f, 0.55f, + SlotTriplet{ SlotSet{ 15, 18, 11 }, SlotSet{ 15, 19, 0 }, SlotSet{ 15, 9, 3 } } }, + { "Synth Effects", 0.68f, 0.70f, 9000.0f, 0.080f, 1.400f, 0.40f, 1.800f, 0.90f, 0.70f, 0.62f, 0.60f, + SlotTriplet{ SlotSet{ 5, 11, 1 }, SlotSet{ 14, 19, 2 }, SlotSet{ 6, 11, 12 } } }, + { "Ethnic", 0.69f, 0.40f, 6800.0f, 0.030f, 0.650f, 0.58f, 0.700f, 0.58f, 0.36f, 0.42f, 0.34f, + SlotTriplet{ SlotSet{ 11, 3, 12 }, SlotSet{ 11, 9, 4 }, SlotSet{ 11, 3, 0 } } }, + { "Percussive", 0.71f, 0.28f, 5200.0f, 0.012f, 0.420f, 0.30f, 0.380f, 0.75f, 0.44f, 0.60f, 0.26f, + SlotTriplet{ SlotSet{ 5, 12, 14 }, SlotSet{ 5, 13, 4 }, SlotSet{ 5, 9, 6 } } }, + { "Sound Effects", 0.65f, 0.75f, 9900.0f, 0.050f, 1.600f, 0.35f, 2.400f, 1.10f, 0.80f, 0.70f, 0.65f, + SlotTriplet{ SlotSet{ 11, 5, 1 }, SlotSet{ 11, 6, 2 }, SlotSet{ 19, 14, 1 } } } + }}; + + std::vector cats; + cats.reserve (gmProfiles.size()); + + for (size_t profileIndex = 0; profileIndex < gmProfiles.size(); ++profileIndex) + { + const auto& profile = gmProfiles[(size_t) profileIndex]; + Category category; + category.name = profile.name; + category.presets.reserve (presetsPerCategory); + + for (int variant = 0; variant < presetsPerCategory; ++variant) + { + const bool bitimbral = variant >= modeVariantCount; + const auto presetName = makePresetName (profile, profileIndex, variant, bitimbral); + + category.presets.push_back (makePreset (presetName, profile, variant, bitimbral)); + } + + cats.push_back (std::move (category)); + } + + return cats; + } + + // Apply a preset to the APVTS (safe: uses convertTo0to1) + inline void loadPreset (juce::AudioProcessorValueTreeState& apvts, const Preset& p) + { + for (const auto& kv : p.values) + if (auto* prm = apvts.getParameter (kv.first)) + prm->setValueNotifyingHost (prm->convertTo0to1 (kv.second)); + } +} // namespace PresetsCode