diff --git a/Source/MacroControls.cpp b/Source/MacroControls.cpp new file mode 100644 index 0000000..d1d9e58 --- /dev/null +++ b/Source/MacroControls.cpp @@ -0,0 +1,84 @@ +#include "MacroControls.h" + +namespace serum +{ + +bool MacroControls::addAssignment (int macro, ModTarget target, float depth) +{ + macro = juce::jlimit (0, kNumMacros - 1, macro); + if (target == ModTarget::None || (int) assignments[(size_t) macro].size() >= kMaxAssignments) + return false; + assignments[(size_t) macro].push_back ({ target, depth }); + return true; +} + +void MacroControls::removeAssignment (int macro, int index) +{ + macro = juce::jlimit (0, kNumMacros - 1, macro); + auto& vec = assignments[(size_t) macro]; + if (index >= 0 && index < (int) vec.size()) + vec.erase (vec.begin() + index); +} + +void MacroControls::clear() +{ + for (auto& a : assignments) + a.clear(); +} + +juce::String MacroControls::macroName (int index) +{ + switch (index) + { + case 0: return "Energy"; + case 1: return "Width"; + case 2: return "Drive"; + case 3: return "Atmosphere"; + default: return {}; + } +} + +juce::ValueTree MacroControls::toValueTree() const +{ + juce::ValueTree tree ("MACROS"); + for (int m = 0; m < kNumMacros; ++m) + { + juce::ValueTree mac ("MACRO"); + mac.setProperty ("index", m, nullptr); + for (const auto& a : assignments[(size_t) m]) + { + juce::ValueTree asg ("ASSIGN"); + asg.setProperty ("target", modTargetToString (a.target), nullptr); + asg.setProperty ("depth", a.depth, nullptr); + mac.appendChild (asg, nullptr); + } + tree.appendChild (mac, nullptr); + } + return tree; +} + +void MacroControls::fromValueTree (const juce::ValueTree& tree) +{ + clear(); + if (! tree.isValid()) + return; + + for (const auto& mac : tree) + { + if (! mac.hasType ("MACRO")) + continue; + const int index = juce::jlimit (0, kNumMacros - 1, (int) mac.getProperty ("index", 0)); + for (const auto& asg : mac) + { + if (! asg.hasType ("ASSIGN")) + continue; + MacroAssignment a; + a.target = modTargetFromString (asg.getProperty ("target").toString()); + a.depth = (float) asg.getProperty ("depth", 0.0); + if (a.target != ModTarget::None) + assignments[(size_t) index].push_back (a); + } + } +} + +} // namespace serum diff --git a/Source/MacroControls.h b/Source/MacroControls.h new file mode 100644 index 0000000..b34ada4 --- /dev/null +++ b/Source/MacroControls.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include "Params.h" + +namespace serum +{ + +// =========================================================================== +// One assignable destination for a macro knob. +// =========================================================================== +struct MacroAssignment +{ + ModTarget target = ModTarget::None; + float depth = 0.0f; +}; + +// =========================================================================== +// Four macros (Energy, Width, Drive, Atmosphere). Macro *values* are APVTS +// parameters; this class stores the assignable destinations and serialises +// them with the preset. +// =========================================================================== +class MacroControls +{ +public: + static constexpr int kMaxAssignments = 8; + + std::array, kNumMacros> assignments; + + bool addAssignment (int macro, ModTarget target, float depth); + void removeAssignment (int macro, int index); + void clear(); + + juce::ValueTree toValueTree() const; + void fromValueTree (const juce::ValueTree& tree); + + static juce::String macroName (int index); +}; + +} // namespace serum diff --git a/Source/Params.cpp b/Source/Params.cpp new file mode 100644 index 0000000..559b184 --- /dev/null +++ b/Source/Params.cpp @@ -0,0 +1,156 @@ +#include "Params.h" + +namespace serum +{ + +juce::String modSourceName (ModSource s) +{ + switch (s) + { + case ModSource::Lfo1: return "LFO 1"; + case ModSource::Lfo2: return "LFO 2"; + case ModSource::Lfo3: return "LFO 3"; + case ModSource::Lfo4: return "LFO 4"; + case ModSource::Env1: return "Env 1"; + case ModSource::Env2: return "Env 2"; + case ModSource::Env3: return "Env 3"; + case ModSource::Env4: return "Env 4"; + case ModSource::Velocity: return "Velocity"; + case ModSource::Note: return "Note"; + case ModSource::ModWheel: return "Mod Wheel"; + case ModSource::PitchBend: return "Pitch Bend"; + case ModSource::Macro1: return "Macro 1"; + case ModSource::Macro2: return "Macro 2"; + case ModSource::Macro3: return "Macro 3"; + case ModSource::Macro4: return "Macro 4"; + case ModSource::Random: return "Random"; + default: return {}; + } +} + +juce::String modSourceToString (ModSource s) +{ + switch (s) + { + case ModSource::Lfo1: return "lfo1"; case ModSource::Lfo2: return "lfo2"; + case ModSource::Lfo3: return "lfo3"; case ModSource::Lfo4: return "lfo4"; + case ModSource::Env1: return "env1"; case ModSource::Env2: return "env2"; + case ModSource::Env3: return "env3"; case ModSource::Env4: return "env4"; + case ModSource::Velocity: return "velocity"; case ModSource::Note: return "note"; + case ModSource::ModWheel: return "modwheel"; case ModSource::PitchBend: return "pitchbend"; + case ModSource::Macro1: return "macro1"; case ModSource::Macro2: return "macro2"; + case ModSource::Macro3: return "macro3"; case ModSource::Macro4: return "macro4"; + case ModSource::Random: return "random"; + default: return {}; + } +} + +ModSource modSourceFromString (const juce::String& s) +{ + for (int i = 0; i < kNumModSources; ++i) + if (s == modSourceToString ((ModSource) i)) + return (ModSource) i; + return ModSource::Lfo1; +} + +juce::String modTargetName (ModTarget t) +{ + switch (t) + { + case ModTarget::Master: return "Master Volume"; + case ModTarget::Pitch: return "Pitch"; + case ModTarget::Amp: return "Amp"; + case ModTarget::OscALevel: return "Osc A Level"; + case ModTarget::OscAPan: return "Osc A Pan"; + case ModTarget::OscAWtPos: return "Osc A WT Pos"; + case ModTarget::OscAUnison: return "Osc A Unison"; + case ModTarget::OscADetune: return "Osc A Detune"; + case ModTarget::OscASpread: return "Osc A Spread"; + case ModTarget::OscAWarpAmt: return "Osc A Warp Amt"; + case ModTarget::OscBLevel: return "Osc B Level"; + case ModTarget::OscBPan: return "Osc B Pan"; + case ModTarget::OscBWtPos: return "Osc B WT Pos"; + case ModTarget::OscBUnison: return "Osc B Unison"; + case ModTarget::OscBDetune: return "Osc B Detune"; + case ModTarget::OscBSpread: return "Osc B Spread"; + case ModTarget::OscBWarpAmt: return "Osc B Warp Amt"; + case ModTarget::SubLevel: return "Sub Level"; + case ModTarget::NoiseLevel: return "Noise Level"; + case ModTarget::Filter1Cutoff: return "Filter 1 Cutoff"; + case ModTarget::Filter1Res: return "Filter 1 Res"; + case ModTarget::Filter1Drive: return "Filter 1 Drive"; + case ModTarget::Filter2Cutoff: return "Filter 2 Cutoff"; + case ModTarget::Filter2Res: return "Filter 2 Res"; + case ModTarget::Filter2Drive: return "Filter 2 Drive"; + case ModTarget::FilterMix: return "Filter Mix"; + case ModTarget::FilterOut: return "Filter Out"; + case ModTarget::Fx1Mix: return "FX 1 Mix"; case ModTarget::Fx2Mix: return "FX 2 Mix"; + case ModTarget::Fx3Mix: return "FX 3 Mix"; case ModTarget::Fx4Mix: return "FX 4 Mix"; + case ModTarget::Fx5Mix: return "FX 5 Mix"; case ModTarget::Fx6Mix: return "FX 6 Mix"; + case ModTarget::Fx7Mix: return "FX 7 Mix"; case ModTarget::Fx8Mix: return "FX 8 Mix"; + default: return {}; + } +} + +juce::String modTargetParamId (ModTarget t) +{ + switch (t) + { + case ModTarget::Master: return ids::master; + case ModTarget::Pitch: return {}; + case ModTarget::Amp: return {}; + case ModTarget::OscALevel: return ids::oscALevel; + case ModTarget::OscAPan: return ids::oscAPan; + case ModTarget::OscAWtPos: return ids::oscAWtPos; + case ModTarget::OscAUnison: return ids::oscAUnison; + case ModTarget::OscADetune: return ids::oscADetune; + case ModTarget::OscASpread: return ids::oscASpread; + case ModTarget::OscAWarpAmt: return ids::oscAWarpAmt; + case ModTarget::OscBLevel: return ids::oscBLevel; + case ModTarget::OscBPan: return ids::oscBPan; + case ModTarget::OscBWtPos: return ids::oscBWtPos; + case ModTarget::OscBUnison: return ids::oscBUnison; + case ModTarget::OscBDetune: return ids::oscBDetune; + case ModTarget::OscBSpread: return ids::oscBSpread; + case ModTarget::OscBWarpAmt: return ids::oscBWarpAmt; + case ModTarget::SubLevel: return ids::subLevel; + case ModTarget::NoiseLevel: return ids::noiseLevel; + case ModTarget::Filter1Cutoff: return ids::f1Cutoff; + case ModTarget::Filter1Res: return ids::f1Res; + case ModTarget::Filter1Drive: return ids::f1Drive; + case ModTarget::Filter2Cutoff: return ids::f2Cutoff; + case ModTarget::Filter2Res: return ids::f2Res; + case ModTarget::Filter2Drive: return ids::f2Drive; + case ModTarget::FilterMix: return ids::fMix; + case ModTarget::FilterOut: return ids::fOut; + case ModTarget::Fx1Mix: return ids::fx1Mix; case ModTarget::Fx2Mix: return ids::fx2Mix; + case ModTarget::Fx3Mix: return ids::fx3Mix; case ModTarget::Fx4Mix: return ids::fx4Mix; + case ModTarget::Fx5Mix: return ids::fx5Mix; case ModTarget::Fx6Mix: return ids::fx6Mix; + case ModTarget::Fx7Mix: return ids::fx7Mix; case ModTarget::Fx8Mix: return ids::fx8Mix; + default: return {}; + } +} + +ModTarget modTargetFromParamId (const juce::String& id) +{ + for (int i = 0; i < kNumModTargets; ++i) + if (id.isNotEmpty() && modTargetParamId ((ModTarget) i) == id) + return (ModTarget) i; + return ModTarget::None; +} + +juce::String modTargetToString (ModTarget t) +{ + if (t == ModTarget::Pitch) return "pitch"; + if (t == ModTarget::Amp) return "amp"; + return modTargetParamId (t); +} + +ModTarget modTargetFromString (const juce::String& s) +{ + if (s == "pitch") return ModTarget::Pitch; + if (s == "amp") return ModTarget::Amp; + return modTargetFromParamId (s); +} + +} // namespace serum diff --git a/Source/Params.h b/Source/Params.h new file mode 100644 index 0000000..5c6d0e0 --- /dev/null +++ b/Source/Params.h @@ -0,0 +1,362 @@ +#pragma once + +#include + +// =========================================================================== +// Central registry of parameter IDs, enums and mapping helpers for SerumAlt. +// Every audio parameter (APVTS) and every modulation destination is declared +// here so that the DSP engine, the mod matrix and the GUI share one source of +// truth. +// =========================================================================== + +namespace serum +{ + +// --------------------------------------------------------------------------- +// Parameter IDs (APVTS) +// --------------------------------------------------------------------------- +namespace ids +{ + // Global + inline constexpr const char* master = "master"; + inline constexpr const char* uiScale = "uiScale"; + inline constexpr const char* rave = "rave"; + + // Oscillator A / B + inline constexpr const char* oscAOn = "oscAOn"; + inline constexpr const char* oscAWave = "oscAWave"; + inline constexpr const char* oscAWtPos = "oscAWtPos"; + inline constexpr const char* oscAWarp = "oscAWarp"; + inline constexpr const char* oscAWarpAmt = "oscAWarpAmt"; + inline constexpr const char* oscACoarse = "oscACoarse"; + inline constexpr const char* oscAFine = "oscAFine"; + inline constexpr const char* oscALevel = "oscALevel"; + inline constexpr const char* oscAPan = "oscAPan"; + inline constexpr const char* oscAUnison = "oscAUnison"; + inline constexpr const char* oscADetune = "oscADetune"; + inline constexpr const char* oscASpread = "oscASpread"; + inline constexpr const char* oscAPhase = "oscAPhase"; + inline constexpr const char* oscARandPh = "oscARandPh"; + + inline constexpr const char* oscBOn = "oscBOn"; + inline constexpr const char* oscBWave = "oscBWave"; + inline constexpr const char* oscBWtPos = "oscBWtPos"; + inline constexpr const char* oscBWarp = "oscBWarp"; + inline constexpr const char* oscBWarpAmt = "oscBWarpAmt"; + inline constexpr const char* oscBCoarse = "oscBCoarse"; + inline constexpr const char* oscBFine = "oscBFine"; + inline constexpr const char* oscBLevel = "oscBLevel"; + inline constexpr const char* oscBPan = "oscBPan"; + inline constexpr const char* oscBUnison = "oscBUnison"; + inline constexpr const char* oscBDetune = "oscBDetune"; + inline constexpr const char* oscBSpread = "oscBSpread"; + inline constexpr const char* oscBPhase = "oscBPhase"; + inline constexpr const char* oscBRandPh = "oscBRandPh"; + + // Sub oscillator + inline constexpr const char* subOn = "subOn"; + inline constexpr const char* subShape = "subShape"; + inline constexpr const char* subOct = "subOct"; + inline constexpr const char* subLevel = "subLevel"; + + // Noise + inline constexpr const char* noiseOn = "noiseOn"; + inline constexpr const char* noiseType = "noiseType"; + inline constexpr const char* noiseLevel = "noiseLevel"; + + // Filters + inline constexpr const char* f1On = "f1On"; + inline constexpr const char* f1Type = "f1Type"; + inline constexpr const char* f1Cutoff = "f1Cutoff"; + inline constexpr const char* f1Res = "f1Res"; + inline constexpr const char* f1Drive = "f1Drive"; + inline constexpr const char* f1Key = "f1Key"; + inline constexpr const char* f1Slope = "f1Slope"; + + inline constexpr const char* f2On = "f2On"; + inline constexpr const char* f2Type = "f2Type"; + inline constexpr const char* f2Cutoff = "f2Cutoff"; + inline constexpr const char* f2Res = "f2Res"; + inline constexpr const char* f2Drive = "f2Drive"; + inline constexpr const char* f2Key = "f2Key"; + inline constexpr const char* f2Slope = "f2Slope"; + + inline constexpr const char* fRoute = "fRoute"; + inline constexpr const char* fMix = "fMix"; + inline constexpr const char* fOut = "fOut"; + + // Envelopes 1..4 + inline constexpr const char* env1A = "env1A"; + inline constexpr const char* env1D = "env1D"; + inline constexpr const char* env1S = "env1S"; + inline constexpr const char* env1R = "env1R"; + inline constexpr const char* env1Curve = "env1Curve"; + + inline constexpr const char* env2A = "env2A"; + inline constexpr const char* env2D = "env2D"; + inline constexpr const char* env2S = "env2S"; + inline constexpr const char* env2R = "env2R"; + inline constexpr const char* env2Curve = "env2Curve"; + + inline constexpr const char* env3A = "env3A"; + inline constexpr const char* env3D = "env3D"; + inline constexpr const char* env3S = "env3S"; + inline constexpr const char* env3R = "env3R"; + inline constexpr const char* env3Curve = "env3Curve"; + + inline constexpr const char* env4A = "env4A"; + inline constexpr const char* env4D = "env4D"; + inline constexpr const char* env4S = "env4S"; + inline constexpr const char* env4R = "env4R"; + inline constexpr const char* env4Curve = "env4Curve"; + + // LFOs 1..4 + inline constexpr const char* lfo1Rate = "lfo1Rate"; + inline constexpr const char* lfo1Sync = "lfo1Sync"; + inline constexpr const char* lfo1Beat = "lfo1Beat"; + inline constexpr const char* lfo1Shape = "lfo1Shape"; + inline constexpr const char* lfo1Phase = "lfo1Phase"; + inline constexpr const char* lfo1Fade = "lfo1Fade"; + inline constexpr const char* lfo1Delay = "lfo1Delay"; + + inline constexpr const char* lfo2Rate = "lfo2Rate"; + inline constexpr const char* lfo2Sync = "lfo2Sync"; + inline constexpr const char* lfo2Beat = "lfo2Beat"; + inline constexpr const char* lfo2Shape = "lfo2Shape"; + inline constexpr const char* lfo2Phase = "lfo2Phase"; + inline constexpr const char* lfo2Fade = "lfo2Fade"; + inline constexpr const char* lfo2Delay = "lfo2Delay"; + + inline constexpr const char* lfo3Rate = "lfo3Rate"; + inline constexpr const char* lfo3Sync = "lfo3Sync"; + inline constexpr const char* lfo3Beat = "lfo3Beat"; + inline constexpr const char* lfo3Shape = "lfo3Shape"; + inline constexpr const char* lfo3Phase = "lfo3Phase"; + inline constexpr const char* lfo3Fade = "lfo3Fade"; + inline constexpr const char* lfo3Delay = "lfo3Delay"; + + inline constexpr const char* lfo4Rate = "lfo4Rate"; + inline constexpr const char* lfo4Sync = "lfo4Sync"; + inline constexpr const char* lfo4Beat = "lfo4Beat"; + inline constexpr const char* lfo4Shape = "lfo4Shape"; + inline constexpr const char* lfo4Phase = "lfo4Phase"; + inline constexpr const char* lfo4Fade = "lfo4Fade"; + inline constexpr const char* lfo4Delay = "lfo4Delay"; + + // FX slots (8) + inline constexpr const char* fx1Type = "fx1Type"; + inline constexpr const char* fx1Mix = "fx1Mix"; + inline constexpr const char* fx1P1 = "fx1P1"; + inline constexpr const char* fx1P2 = "fx1P2"; + inline constexpr const char* fx1P3 = "fx1P3"; + inline constexpr const char* fx1P4 = "fx1P4"; + + inline constexpr const char* fx2Type = "fx2Type"; + inline constexpr const char* fx2Mix = "fx2Mix"; + inline constexpr const char* fx2P1 = "fx2P1"; + inline constexpr const char* fx2P2 = "fx2P2"; + inline constexpr const char* fx2P3 = "fx2P3"; + inline constexpr const char* fx2P4 = "fx2P4"; + + inline constexpr const char* fx3Type = "fx3Type"; + inline constexpr const char* fx3Mix = "fx3Mix"; + inline constexpr const char* fx3P1 = "fx3P1"; + inline constexpr const char* fx3P2 = "fx3P2"; + inline constexpr const char* fx3P3 = "fx3P3"; + inline constexpr const char* fx3P4 = "fx3P4"; + + inline constexpr const char* fx4Type = "fx4Type"; + inline constexpr const char* fx4Mix = "fx4Mix"; + inline constexpr const char* fx4P1 = "fx4P1"; + inline constexpr const char* fx4P2 = "fx4P2"; + inline constexpr const char* fx4P3 = "fx4P3"; + inline constexpr const char* fx4P4 = "fx4P4"; + + inline constexpr const char* fx5Type = "fx5Type"; + inline constexpr const char* fx5Mix = "fx5Mix"; + inline constexpr const char* fx5P1 = "fx5P1"; + inline constexpr const char* fx5P2 = "fx5P2"; + inline constexpr const char* fx5P3 = "fx5P3"; + inline constexpr const char* fx5P4 = "fx5P4"; + + inline constexpr const char* fx6Type = "fx6Type"; + inline constexpr const char* fx6Mix = "fx6Mix"; + inline constexpr const char* fx6P1 = "fx6P1"; + inline constexpr const char* fx6P2 = "fx6P2"; + inline constexpr const char* fx6P3 = "fx6P3"; + inline constexpr const char* fx6P4 = "fx6P4"; + + inline constexpr const char* fx7Type = "fx7Type"; + inline constexpr const char* fx7Mix = "fx7Mix"; + inline constexpr const char* fx7P1 = "fx7P1"; + inline constexpr const char* fx7P2 = "fx7P2"; + inline constexpr const char* fx7P3 = "fx7P3"; + inline constexpr const char* fx7P4 = "fx7P4"; + + inline constexpr const char* fx8Type = "fx8Type"; + inline constexpr const char* fx8Mix = "fx8Mix"; + inline constexpr const char* fx8P1 = "fx8P1"; + inline constexpr const char* fx8P2 = "fx8P2"; + inline constexpr const char* fx8P3 = "fx8P3"; + inline constexpr const char* fx8P4 = "fx8P4"; + + // Macros 1..4 + inline constexpr const char* macro1 = "macro1"; + inline constexpr const char* macro2 = "macro2"; + inline constexpr const char* macro3 = "macro3"; + inline constexpr const char* macro4 = "macro4"; +} + +// --------------------------------------------------------------------------- +// Enums +// --------------------------------------------------------------------------- +enum class WarpMode : int { None = 0, BendPlus, BendMinus, Sync, PWM, Asym, Mirror, Fold, Count }; +enum class SubShape : int { Sine = 0, Triangle, Count }; +enum class NoiseType : int { White = 0, Pink, Count }; +enum class FilterModel: int { LadderLP = 0, LadderHP, LadderBP, Diode, Comb, Formant, Screamer, Count }; +enum class FilterRoute: int { Serial = 0, Parallel, Split, Count }; +enum class LfoShape : int { Sine = 0, Triangle, Saw, Square, SampleHold, StepSeq, Freehand, Count }; +enum class FxType : int { Off = 0, Hyper, Chorus, Flanger, Phaser, Distortion, EQ, Compressor, Delay, Reverb, Count }; + +inline constexpr int kNumOscillators = 2; +inline constexpr int kNumEnvelopes = 4; +inline constexpr int kNumLfos = 4; +inline constexpr int kNumMacros = 4; +inline constexpr int kNumFxSlots = 8; +inline constexpr int kMaxUnison = 16; +inline constexpr int kNumVoices = 32; + +// --------------------------------------------------------------------------- +// Modulation sources / destinations +// --------------------------------------------------------------------------- +enum class ModSource : int +{ + Lfo1 = 0, Lfo2, Lfo3, Lfo4, + Env1, Env2, Env3, Env4, + Velocity, Note, ModWheel, PitchBend, + Macro1, Macro2, Macro3, Macro4, + Random, + Count +}; + +enum class ModTarget : int +{ + None = -1, + Master = 0, + Pitch, + Amp, + OscALevel, OscAPan, OscAWtPos, OscAUnison, OscADetune, OscASpread, OscAWarpAmt, + OscBLevel, OscBPan, OscBWtPos, OscBUnison, OscBDetune, OscBSpread, OscBWarpAmt, + SubLevel, NoiseLevel, + Filter1Cutoff, Filter1Res, Filter1Drive, + Filter2Cutoff, Filter2Res, Filter2Drive, + FilterMix, FilterOut, + Fx1Mix, Fx2Mix, Fx3Mix, Fx4Mix, Fx5Mix, Fx6Mix, Fx7Mix, Fx8Mix, + Count +}; + +inline constexpr int kNumModTargets = (int) ModTarget::Count; +inline constexpr int kNumModSources = (int) ModSource::Count; + +// Is a given source per-voice (i.e. needs a value for each voice)? +inline bool isPerVoiceSource (ModSource s) +{ + switch (s) + { + case ModSource::Env1: case ModSource::Env2: + case ModSource::Env3: case ModSource::Env4: + case ModSource::Velocity: case ModSource::Note: + case ModSource::Random: return true; + default: return false; + } +} + +// Does a source already span -1..1 (bipolar range)? +inline bool isBipolarSource (ModSource s) +{ + return (s >= ModSource::Lfo1 && s <= ModSource::Lfo4) || s == ModSource::PitchBend; +} + +// Is a given target a per-voice parameter? +inline bool isPerVoiceTarget (ModTarget t) +{ + switch (t) + { + case ModTarget::Master: + case ModTarget::FilterMix: case ModTarget::FilterOut: + case ModTarget::Fx1Mix: case ModTarget::Fx2Mix: + case ModTarget::Fx3Mix: case ModTarget::Fx4Mix: + case ModTarget::Fx5Mix: case ModTarget::Fx6Mix: + case ModTarget::Fx7Mix: case ModTarget::Fx8Mix: + return false; + default: + return true; + } +} + +juce::String modSourceName (ModSource s); +juce::String modTargetName (ModTarget t); +juce::String modTargetParamId(ModTarget t); // APVTS param id for the target +ModTarget modTargetFromParamId (const juce::String& id); +ModSource modSourceFromString (const juce::String& s); +juce::String modSourceToString (ModSource s); +juce::String modTargetToString (ModTarget t); +ModTarget modTargetFromString (const juce::String& s); + +// --------------------------------------------------------------------------- +// Mapping helpers (raw 0..1 <-> physical units) +// --------------------------------------------------------------------------- +namespace maps +{ + // Envelope times: 0..1 -> 0.5ms .. 12s (cubic taper for fine control at short times) + inline float toSeconds (float x) { return 0.0005f + std::pow (juce::jlimit (0.0f, 1.0f, x), 3.0f) * 12.0f; } + inline float fromSeconds (float s) { return std::pow (juce::jlimit (0.0005f, 12.0f, s) / 12.0f, 1.0f / 3.0f); } + + // Cutoff: 0..1 -> 20Hz .. 20kHz (log) + inline float cutoffToHz (float x) { return 20.0f * std::pow (1000.0f, juce::jlimit (0.0f, 1.0f, x)); } + inline float hzToCutoff (float hz) { return std::log (juce::jlimit (20.0f, 20000.0f, hz) / 20.0f) / std::log (1000.0f); } + + // LFO rate: 0..1 -> 0.02Hz .. 30Hz (log) + inline float rateToHz (float x) { return 0.02f * std::pow (1500.0f, juce::jlimit (0.0f, 1.0f, x)); } + inline float hzToRate (float hz) { return std::log (juce::jlimit (0.02f, 30.0f, hz) / 0.02f) / std::log (1500.0f); } + + // LFO beat division: 0..1 -> 1/32 .. 4 bars (log stepped) + inline float beatToMultiplier (float x) + { + // 0 -> 1/32, 0.5 -> 1/4, 1 -> 4 bars + static constexpr float divisions[16] = + { + 1.0f/32.0f, 1.0f/16.0f, 1.0f/8.0f, 1.0f/6.0f, 1.0f/4.0f, 1.0f/3.0f, + 1.0f/2.0f, 1.0f, 3.0f/2.0f, 2.0f, 3.0f, 4.0f, 6.0f, 8.0f, 12.0f, 16.0f + }; + const int idx = juce::jlimit (0, 15, (int) std::llround (x * 15.0f)); + return divisions[idx]; + } + + // FX delay time: 0..1 -> 1ms .. 2000ms (log) + inline float delayToMs (float x) { return 1.0f * std::pow (2000.0f, juce::jlimit (0.0f, 1.0f, x)); } + inline float msToDelay (float ms) { return std::log (juce::jlimit (1.0f, 2000.0f, ms)) / std::log (2000.0f); } + + // Reverb size: 0..1 -> 0.2 .. 0.99 + inline float sizeToValue (float x) { return 0.2f + juce::jlimit (0.0f, 1.0f, x) * 0.79f; } +} + +// --------------------------------------------------------------------------- +// Factory wavetable names (index == oscWave param value) +// --------------------------------------------------------------------------- +inline const char* const kWavetableNames[] = +{ + "Basic Shapes", + "Saw PWM", + "Square Sync", + "Triangle Fold", + "Vowel", + "Organ", + "Warm Saw", + "Digital", + "Glass", + "Bass" +}; +inline constexpr int kNumWavetables = 10; + +} // namespace serum diff --git a/Source/Presets/FactoryPresets.cpp b/Source/Presets/FactoryPresets.cpp new file mode 100644 index 0000000..6e7ce57 --- /dev/null +++ b/Source/Presets/FactoryPresets.cpp @@ -0,0 +1,250 @@ +#include "FactoryPresets.h" + +namespace serum +{ + +namespace +{ + using P = std::pair; + inline P p (const char* id, float v) { return { id, v }; } + + // Semitones -> normalised coarse value (param range -24..+24). + inline float coarse (int semitones) { return (float) (semitones + 24) / 48.0f; } + // Unison count -> normalised value. + inline float unison (int n) { return (float) (n - 1) / 15.0f; } + // 6/12/24 dB slope -> normalised value. + inline float slope (int db) { return db == 6 ? 0.0f : (db == 12 ? 0.5f : 1.0f); } + // FxType -> normalised value. + inline float fx (FxType t) { return (float) (int) t / 9.0f; } + // LFO shape -> normalised value. + inline float lfoShape (LfoShape s) { return (float) (int) s / 6.0f; } + + inline ModConnection mod (ModSource s, ModTarget t, float d, bool b = false) + { + return { s, t, d, b }; + } +} + +const std::vector& getFactoryPresets() +{ + static const std::vector presets = [] + { + std::vector v; + FactoryPreset pr; + + // 1. Init Saw + pr = {}; + pr.name = "Init Saw"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 0.0f), p (ids::oscAWtPos, 1.0f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.75f), p (ids::f1Res, 0.05f), p (ids::f1Slope, 1.0f) + }; + v.push_back (pr); + + // 2. Supersaw Stack + pr = {}; + pr.name = "Supersaw Stack"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 0.0f), p (ids::oscAWtPos, 1.0f), + p (ids::oscAUnison, unison (7)), p (ids::oscADetune, 0.55f), p (ids::oscASpread, 0.75f), p (ids::oscALevel, 0.85f), + p (ids::oscBOn, 1.0f), p (ids::oscBWave, 0.0f), p (ids::oscBWtPos, 1.0f), + p (ids::oscBUnison, unison (5)), p (ids::oscBDetune, 0.45f), p (ids::oscBSpread, 0.6f), p (ids::oscBLevel, 0.55f), + p (ids::oscBCoarse, coarse (12)), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.68f), p (ids::f1Res, 0.12f), p (ids::f1Slope, 1.0f), + p (ids::fx1Type, fx (FxType::Chorus)), p (ids::fx1Mix, 0.35f), p (ids::fx1P1, 0.4f), p (ids::fx1P2, 0.4f), + p (ids::fx2Type, fx (FxType::Reverb)), p (ids::fx2Mix, 0.25f), p (ids::fx2P1, 0.55f) + }; + v.push_back (pr); + + // 3. Wobble Bass + pr = {}; + pr.name = "Wobble Bass"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 9.0f / 9.0f), p (ids::oscAWtPos, 0.6f), + p (ids::subOn, 1.0f), p (ids::subShape, 0.0f), p (ids::subOct, 0.5f), p (ids::subLevel, 0.6f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.35f), p (ids::f1Res, 0.35f), p (ids::f1Slope, 1.0f), + p (ids::lfo1Shape, lfoShape (LfoShape::Square)), p (ids::lfo1Rate, 0.55f), + p (ids::env1D, 0.35f), p (ids::env1S, 0.8f) + }; + pr.mods = { + mod (ModSource::Lfo1, ModTarget::Filter1Cutoff, 0.5f, false), + mod (ModSource::Env2, ModTarget::Filter1Cutoff, 0.3f, false) + }; + v.push_back (pr); + + // 4. Pluck + pr = {}; + pr.name = "Pluck"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 0.0f), p (ids::oscAWtPos, 0.8f), + p (ids::env1A, 0.02f), p (ids::env1D, 0.18f), p (ids::env1S, 0.0f), p (ids::env1R, 0.2f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.7f), p (ids::f1Res, 0.15f), p (ids::f1Slope, 1.0f), + p (ids::fx1Type, fx (FxType::Delay)), p (ids::fx1Mix, 0.3f), p (ids::fx1P1, 0.45f), p (ids::fx1P2, 0.45f) + }; + v.push_back (pr); + + // 5. Electric Keys + pr = {}; + pr.name = "Electric Keys"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 5.0f / 9.0f), p (ids::oscAWtPos, 0.5f), + p (ids::oscBOn, 1.0f), p (ids::oscBWave, 5.0f / 9.0f), p (ids::oscBWtPos, 0.4f), p (ids::oscBLevel, 0.4f), + p (ids::env1D, 0.4f), p (ids::env1S, 0.7f), p (ids::env1R, 0.35f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.6f), p (ids::f1Res, 0.05f), p (ids::f1Slope, 0.5f), + p (ids::lfo1Shape, lfoShape (LfoShape::Sine)), p (ids::lfo1Rate, 0.7f) + }; + pr.mods = { mod (ModSource::Lfo1, ModTarget::Amp, 0.15f, true) }; + v.push_back (pr); + + // 6. Pad Dreams + pr = {}; + pr.name = "Pad Dreams"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 8.0f / 9.0f), p (ids::oscAWtPos, 0.7f), + p (ids::oscAUnison, unison (5)), p (ids::oscADetune, 0.5f), p (ids::oscASpread, 0.8f), + p (ids::oscBOn, 1.0f), p (ids::oscBWave, 4.0f / 9.0f), p (ids::oscBWtPos, 0.4f), p (ids::oscBLevel, 0.5f), + p (ids::env1A, 0.5f), p (ids::env1D, 0.5f), p (ids::env1S, 0.9f), p (ids::env1R, 0.7f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.5f), p (ids::f1Res, 0.05f), p (ids::f1Slope, 0.5f), + p (ids::fx1Type, fx (FxType::Chorus)), p (ids::fx1Mix, 0.5f), p (ids::fx1P1, 0.3f), p (ids::fx1P2, 0.6f), + p (ids::fx2Type, fx (FxType::Reverb)), p (ids::fx2Mix, 0.5f), p (ids::fx2P1, 0.75f), p (ids::fx2P2, 0.5f) + }; + v.push_back (pr); + + // 7. Lead + pr = {}; + pr.name = "Lead"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 6.0f / 9.0f), p (ids::oscAWtPos, 1.0f), + p (ids::oscAUnison, unison (3)), p (ids::oscADetune, 0.3f), p (ids::oscASpread, 0.5f), + p (ids::oscBOn, 1.0f), p (ids::oscBWave, 1.0f / 9.0f), p (ids::oscBWtPos, 0.5f), p (ids::oscBLevel, 0.4f), p (ids::oscBCoarse, coarse (12)), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.6f), p (ids::f1Res, 0.2f), p (ids::f1Slope, 1.0f), + p (ids::env2D, 0.4f), p (ids::env2S, 0.4f), + p (ids::fx1Type, fx (FxType::Distortion)), p (ids::fx1Mix, 0.2f), p (ids::fx1P1, 0.25f), + p (ids::fx2Type, fx (FxType::Delay)), p (ids::fx2Mix, 0.3f), p (ids::fx2P1, 0.4f), p (ids::fx2P2, 0.4f) + }; + v.push_back (pr); + + // 8. Sub Bass + pr = {}; + pr.name = "Sub Bass"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 9.0f / 9.0f), p (ids::oscAWtPos, 0.0f), + p (ids::subOn, 1.0f), p (ids::subShape, 0.0f), p (ids::subOct, 0.0f), p (ids::subLevel, 0.9f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.4f), p (ids::f1Res, 0.05f), p (ids::f1Slope, 1.0f) + }; + v.push_back (pr); + + // 9. Vowel Morph + pr = {}; + pr.name = "Vowel Morph"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 4.0f / 9.0f), p (ids::oscAWtPos, 0.3f), + p (ids::f1On, 1.0f), p (ids::f1Type, 5.0f / 6.0f), p (ids::f1Cutoff, 0.5f), p (ids::f1Res, 0.2f), + p (ids::lfo1Shape, lfoShape (LfoShape::Sine)), p (ids::lfo1Rate, 0.3f) + }; + pr.mods = { mod (ModSource::Lfo1, ModTarget::OscAWtPos, 0.6f, false) }; + v.push_back (pr); + + // 10. Glass Bell + pr = {}; + pr.name = "Glass Bell"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 8.0f / 9.0f), p (ids::oscAWtPos, 0.6f), + p (ids::env1A, 0.01f), p (ids::env1D, 0.5f), p (ids::env1S, 0.0f), p (ids::env1R, 0.8f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.8f), p (ids::f1Res, 0.1f), p (ids::f1Slope, 0.5f), + p (ids::fx1Type, fx (FxType::Reverb)), p (ids::fx1Mix, 0.6f), p (ids::fx1P1, 0.7f), p (ids::fx1P2, 0.35f) + }; + v.push_back (pr); + + // 11. Sync Riser + pr = {}; + pr.name = "Sync Riser"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 2.0f / 9.0f), p (ids::oscAWtPos, 0.2f), + p (ids::oscAWarp, 3.0f / 7.0f), p (ids::oscAWarpAmt, 0.6f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.3f), p (ids::f1Res, 0.2f), p (ids::f1Slope, 1.0f), + p (ids::lfo1Shape, lfoShape (LfoShape::Saw)), p (ids::lfo1Rate, 0.2f) + }; + pr.mods = { mod (ModSource::Lfo1, ModTarget::OscAWtPos, 0.8f, false) }; + v.push_back (pr); + + // 12. 8-Bit Blast + pr = {}; + pr.name = "8-Bit Blast"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 7.0f / 9.0f), p (ids::oscAWtPos, 0.9f), + p (ids::oscAWarp, 4.0f / 7.0f), p (ids::oscAWarpAmt, 0.5f), + p (ids::env1D, 0.15f), p (ids::env1S, 0.0f), p (ids::env1R, 0.1f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.7f), p (ids::f1Res, 0.05f), p (ids::f1Slope, 1.0f) + }; + v.push_back (pr); + + // 13. Organ + pr = {}; + pr.name = "Cathedral Organ"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 5.0f / 9.0f), p (ids::oscAWtPos, 0.8f), + p (ids::oscBOn, 1.0f), p (ids::oscBWave, 5.0f / 9.0f), p (ids::oscBWtPos, 0.3f), p (ids::oscBLevel, 0.5f), p (ids::oscBCoarse, coarse (12)), + p (ids::env1A, 0.05f), p (ids::env1S, 1.0f), p (ids::env1R, 0.3f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.7f), p (ids::f1Res, 0.0f), p (ids::f1Slope, 0.5f), + p (ids::fx1Type, fx (FxType::Reverb)), p (ids::fx1Mix, 0.35f), p (ids::fx1P1, 0.6f), p (ids::fx1P2, 0.4f) + }; + v.push_back (pr); + + // 14. Choir + pr = {}; + pr.name = "Choir"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 4.0f / 9.0f), p (ids::oscAWtPos, 0.5f), + p (ids::oscAUnison, unison (9)), p (ids::oscADetune, 0.4f), p (ids::oscASpread, 0.7f), + p (ids::env1A, 0.45f), p (ids::env1S, 0.85f), p (ids::env1R, 0.6f), + p (ids::f1On, 1.0f), p (ids::f1Type, 5.0f / 6.0f), p (ids::f1Cutoff, 0.45f), p (ids::f1Res, 0.15f), + p (ids::fx1Type, fx (FxType::Chorus)), p (ids::fx1Mix, 0.5f), p (ids::fx1P1, 0.3f), p (ids::fx1P2, 0.7f), + p (ids::fx2Type, fx (FxType::Reverb)), p (ids::fx2Mix, 0.55f), p (ids::fx2P1, 0.75f), p (ids::fx2P2, 0.5f) + }; + v.push_back (pr); + + // 15. Acid + pr = {}; + pr.name = "Acid"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 0.0f), p (ids::oscAWtPos, 1.0f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.3f), p (ids::f1Res, 0.6f), p (ids::f1Slope, 1.0f), + p (ids::env2A, 0.02f), p (ids::env2D, 0.25f), p (ids::env2S, 0.1f) + }; + pr.mods = { mod (ModSource::Env2, ModTarget::Filter1Cutoff, 0.7f, false) }; + v.push_back (pr); + + // 16. RAVE Anthem Lead + pr = {}; + pr.name = "RAVE Anthem Lead"; + pr.params = { + p (ids::oscAOn, 1.0f), p (ids::oscAWave, 0.0f), p (ids::oscAWtPos, 1.0f), + p (ids::oscAUnison, unison (4)), p (ids::oscADetune, 0.4f), p (ids::oscASpread, 0.6f), p (ids::oscALevel, 0.85f), + p (ids::oscBOn, 1.0f), p (ids::oscBWave, 0.0f), p (ids::oscBWtPos, 1.0f), + p (ids::oscBUnison, unison (4)), p (ids::oscBDetune, 0.35f), p (ids::oscBSpread, 0.5f), p (ids::oscBLevel, 0.5f), p (ids::oscBCoarse, coarse (12)), + p (ids::env1D, 0.4f), p (ids::env1S, 0.8f), p (ids::env1R, 0.3f), + p (ids::f1On, 1.0f), p (ids::f1Type, 0.0f), p (ids::f1Cutoff, 0.6f), p (ids::f1Res, 0.15f), p (ids::f1Drive, 0.15f), p (ids::f1Slope, 1.0f), + p (ids::fx1Type, fx (FxType::Hyper)), p (ids::fx1Mix, 0.35f), p (ids::fx1P1, 0.5f), + p (ids::fx2Type, fx (FxType::Delay)), p (ids::fx2Mix, 0.28f), p (ids::fx2P1, 0.42f), p (ids::fx2P2, 0.4f), + p (ids::fx3Type, fx (FxType::Reverb)), p (ids::fx3Mix, 0.22f), p (ids::fx3P1, 0.55f) + }; + pr.macroAssigns = { + { 0, ModTarget::OscADetune, 0.5f }, + { 0, ModTarget::OscBDetune, 0.4f }, + { 0, ModTarget::Filter1Drive, 0.4f }, + { 1, ModTarget::OscASpread, 0.6f }, + { 1, ModTarget::OscBSpread, 0.5f }, + { 2, ModTarget::Filter1Drive, 0.5f }, + { 2, ModTarget::Filter2Drive, 0.4f }, + { 3, ModTarget::Fx3Mix, 0.4f } + }; + v.push_back (pr); + + return v; + }(); + + return presets; +} + +} // namespace serum diff --git a/Source/Presets/FactoryPresets.h b/Source/Presets/FactoryPresets.h new file mode 100644 index 0000000..e60f2f1 --- /dev/null +++ b/Source/Presets/FactoryPresets.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "../Params.h" +#include "../ModulationMatrix.h" + +namespace serum +{ + +// =========================================================================== +// A factory preset: a name plus a sparse map of parameter values, modulation +// connections and macro assignments. Unset parameters keep their defaults. +// =========================================================================== +struct FactoryPreset +{ + juce::String name; + std::vector> params; + std::vector mods; + + struct MacroAssign + { + int macro = 0; + ModTarget target = ModTarget::None; + float depth = 0.0f; + }; + std::vector macroAssigns; +}; + +// All 16 factory presets, in program order. +const std::vector& getFactoryPresets(); + +} // namespace serum diff --git a/Source/RAVEButton.cpp b/Source/RAVEButton.cpp new file mode 100644 index 0000000..a40dd5b --- /dev/null +++ b/Source/RAVEButton.cpp @@ -0,0 +1,91 @@ +#include "RAVEButton.h" + +namespace serum +{ + +void RaveController::resetSnapshot() +{ + snapshot.clear(); + enabled = false; +} + +void RaveController::snapshotParam (const juce::String& id, juce::AudioProcessorValueTreeState& apvts) +{ + if (auto* p = apvts.getParameter (id)) + snapshot.emplace_back (id, p->getValue()); +} + +void RaveController::setParam (const juce::String& id, float value, juce::AudioProcessorValueTreeState& apvts) +{ + if (auto* p = apvts.getParameter (id)) + p->setValueNotifyingHost (juce::jlimit (0.0f, 1.0f, value)); +} + +void RaveController::setEnabled (bool shouldEnable, juce::AudioProcessorValueTreeState& apvts) +{ + if (shouldEnable == enabled) + return; + + if (shouldEnable) + { + snapshot.clear(); + + // Snapshot the static "boost" parameters. + const char* staticParams[] = + { + ids::oscAUnison, ids::oscBUnison, + ids::oscASpread, ids::oscBSpread, + ids::oscADetune, ids::oscBDetune, + ids::f1Drive, ids::f2Drive + }; + for (auto id : staticParams) + snapshotParam (id, apvts); + + // Snapshot + boost FX-specific params (Hyper intensity / Reverb mix). + const char* fxMixIds[kNumFxSlots] = { ids::fx1Mix, ids::fx2Mix, ids::fx3Mix, ids::fx4Mix, + ids::fx5Mix, ids::fx6Mix, ids::fx7Mix, ids::fx8Mix }; + const char* fxP1Ids[kNumFxSlots] = { ids::fx1P1, ids::fx2P1, ids::fx3P1, ids::fx4P1, + ids::fx5P1, ids::fx6P1, ids::fx7P1, ids::fx8P1 }; + const char* fxTypeIds[kNumFxSlots] = { ids::fx1Type, ids::fx2Type, ids::fx3Type, ids::fx4Type, + ids::fx5Type, ids::fx6Type, ids::fx7Type, ids::fx8Type }; + + for (int i = 0; i < kNumFxSlots; ++i) + { + const auto* typeParam = apvts.getParameter (fxTypeIds[i]); + const int type = typeParam ? (int) (typeParam->getValue() * (int) FxType::Count) : 0; + + if (type == (int) FxType::Hyper) + { + snapshotParam (fxP1Ids[i], apvts); + setParam (fxP1Ids[i], 1.0f, apvts); // OTT intensity 100% + } + else if (type == (int) FxType::Reverb) + { + snapshotParam (fxMixIds[i], apvts); + const float current = apvts.getParameter (fxMixIds[i])->getValue(); + setParam (fxMixIds[i], current + 0.4f, apvts); // reverb send +6dB-ish + } + } + + // Apply boosts. + setParam (ids::oscAUnison, 8.0f / 16.0f, apvts); + setParam (ids::oscBUnison, 8.0f / 16.0f, apvts); + setParam (ids::oscASpread, 1.0f, apvts); + setParam (ids::oscBSpread, 1.0f, apvts); + setParam (ids::oscADetune, 1.0f, apvts); + setParam (ids::oscBDetune, 0.7f, apvts); + setParam (ids::f1Drive, 0.6f, apvts); + setParam (ids::f2Drive, 0.6f, apvts); + + enabled = true; + } + else + { + for (const auto& entry : snapshot) + setParam (entry.first, entry.second, apvts); + snapshot.clear(); + enabled = false; + } +} + +} // namespace serum diff --git a/Source/RAVEButton.h b/Source/RAVEButton.h new file mode 100644 index 0000000..d8f1fe5 --- /dev/null +++ b/Source/RAVEButton.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include "Params.h" + +namespace serum +{ + +// =========================================================================== +// RAVE — one-shot "make it huge" control. Toggling on snapshots the current +// values of the affected parameters and pushes unison, width, drive, OTT and +// reverb to their boosted settings; toggling off restores the snapshot. +// =========================================================================== +class RaveController +{ +public: + void setEnabled (bool enabled, juce::AudioProcessorValueTreeState& apvts); + bool isEnabled() const noexcept { return enabled; } + void resetSnapshot(); + +private: + bool enabled = false; + std::vector> snapshot; + + void snapshotParam (const juce::String& id, juce::AudioProcessorValueTreeState& apvts); + void setParam (const juce::String& id, float value, juce::AudioProcessorValueTreeState& apvts); +}; + +} // namespace serum diff --git a/Source/Resources.cpp b/Source/Resources.cpp new file mode 100644 index 0000000..db96e92 --- /dev/null +++ b/Source/Resources.cpp @@ -0,0 +1,94 @@ +#include "Resources.h" +#include "Params.h" + +namespace serum +{ + +juce::String getLogoSvg() +{ + return R"svg( + + + + + + + + + SerumAlt + +)svg"; +} + +juce::String getBackgroundSvg() +{ + return R"svg( + + + + + + + + +)svg"; +} + +std::unique_ptr createLogoDrawable() +{ + auto xml = juce::parseXML (getLogoSvg()); + return xml != nullptr ? juce::Drawable::createFromSVG (*xml) : nullptr; +} + +std::unique_ptr createBackgroundDrawable() +{ + auto xml = juce::parseXML (getBackgroundSvg()); + return xml != nullptr ? juce::Drawable::createFromSVG (*xml) : nullptr; +} + +juce::String formatPercent (float v) +{ + return juce::String (juce::roundToInt (v * 100.0f)) + " %"; +} + +juce::String formatHz (float v) +{ + const float hz = maps::cutoffToHz (v); + if (hz >= 1000.0f) + return juce::String (hz / 1000.0f, 2) + " kHz"; + return juce::String (juce::roundToInt (hz)) + " Hz"; +} + +juce::String formatSeconds (float v) +{ + const float s = maps::toSeconds (v); + if (s < 1.0f) + return juce::String (juce::roundToInt (s * 1000.0f)) + " ms"; + return juce::String (s, 2) + " s"; +} + +juce::String formatSemis (float v) +{ + const int semi = (int) std::llround (v * 48.0f) - 24; + return juce::String (semi > 0 ? "+" : "") + juce::String (semi) + " st"; +} + +juce::String formatCents (float v) +{ + const int ct = (int) std::llround (v * 200.0f) - 100; + return juce::String (ct > 0 ? "+" : "") + juce::String (ct) + " ct"; +} + +juce::String formatPan (float v) +{ + const int p = (int) std::llround ((v - 0.5f) * 200.0f); + if (p == 0) return "C"; + return (p < 0 ? "L" : "R") + juce::String (std::abs (p)); +} + +juce::String formatInt (float v, int maxValue) +{ + return juce::String (juce::jlimit (0, maxValue, (int) std::llround (v * maxValue))); +} + +} // namespace serum diff --git a/Source/Resources.h b/Source/Resources.h new file mode 100644 index 0000000..07e27a0 --- /dev/null +++ b/Source/Resources.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace serum +{ + +// =========================================================================== +// Central dark theme + static SVG assets (embedded as strings so they need no +// binary-data build step; the same assets also ship under Source/Resources/). +// =========================================================================== +namespace theme +{ + inline const juce::Colour bg { 0xff101117 }; + inline const juce::Colour panel { 0xff1a1c24 }; + inline const juce::Colour panelRaised { 0xff23262f }; + inline const juce::Colour outline { 0xff2c2f39 }; + inline const juce::Colour text { 0xffe8eaf0 }; + inline const juce::Colour textDim { 0xff9095a2 }; + inline const juce::Colour accent { 0xff4fc3f7 }; // cyan + inline const juce::Colour accent2 { 0xffff6e9c }; // pink + inline const juce::Colour amber { 0xffffb74d }; + inline const juce::Colour green { 0xff7ce38b }; + inline const juce::Colour raveGlow { 0xffff3d71 }; +} + +// SVG assets (logo + subtle background). +juce::String getLogoSvg(); +juce::String getBackgroundSvg(); +std::unique_ptr createLogoDrawable(); +std::unique_ptr createBackgroundDrawable(); + +// Small value-formatting helpers used by the GUI controls. +juce::String formatPercent (float v); // 0..1 -> "42 %" +juce::String formatHz (float v); // 0..1 (cutoff) -> "1.2 kHz" +juce::String formatSeconds (float v); // 0..1 (env) -> "320 ms" +juce::String formatSemis (float v); // 0..1 -> "-12 st" +juce::String formatCents (float v); // 0..1 -> "+5 ct" +juce::String formatPan (float v); // 0..1 -> "L50" +juce::String formatInt (float v, int maxValue); + +} // namespace serum diff --git a/Source/Resources/background.svg b/Source/Resources/background.svg new file mode 100644 index 0000000..cda9386 --- /dev/null +++ b/Source/Resources/background.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Source/Resources/logo.svg b/Source/Resources/logo.svg new file mode 100644 index 0000000..8fe0018 --- /dev/null +++ b/Source/Resources/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + SerumAlt +