/* ============================================================================== PluginParameters.h Created: 4 Oct 2023 10:40:47am Author: binya ============================================================================== */ #pragma once #include #include "Utilities/ListenableValue.h" #ifndef JAS_DARKMODE_DEFAULT #define JAS_DARKMODE_DEFAULT true #endif #ifndef JAS_VST3_REAPER_INTEGRATION #define JAS_VST3_REAPER_INTEGRATION false #endif /** This namespace contains all APVTS parameter IDs, the other plugin state, various plugin configuration settings, and the parameter layout */ namespace PluginParameters { using String = juce::String; using StringArray = juce::StringArray; using NormalisableRange = juce::NormalisableRange; using Range = juce::Range; /** Stores the non-parameter state of the plugin */ struct State { ListenableAtomic width{ 0 }; inline static const String WIDTH{ "Width" }; ListenableAtomic height{ 0 }; inline static const String HEIGHT{ "Height" }; ListenableMutex filePath{ "" }; inline static const String FILE_PATH{ "File Path" }; ListenableMutex sampleHash{ "" }; inline static const String SAMPLE_HASH{ "Sample Hash" }; ListenableAtomic usingFileReference{ false }; inline static const String USING_FILE_REFERENCE{ "Using File Reference" }; ListenableMutex recentFiles; inline static const String RECENT_FILES{ "Recent Files" }; inline static const String SAVED_DEVICE_SETTINGS{ "Saved Device Settings" }; ListenableAtomic viewStart{ 0 }; inline static const String UI_VIEW_START{ "UI View Start" }; ListenableAtomic viewEnd{ 0 }; inline static const String UI_VIEW_END{ "UI View End" }; ListenableAtomic pinView{ false }; inline static const String PIN_VIEW{ "Pin View" }; ListenableAtomic primaryChannel{ 0 }; inline static const String PRIMARY_CHANNEL{ "Primary Channel" }; ListenableAtomic sampleStart{ 0 }; inline static const String SAMPLE_START{ "Sample Start" }; ListenableAtomic sampleEnd{ 0 }; inline static const String SAMPLE_END{ "Sample End" }; ListenableAtomic loopStart{ 0 }; inline static const String LOOP_START{ "Loop Start" }; ListenableAtomic loopEnd{ 0 }; inline static const String LOOP_END{ "Loop End" }; ListenableAtomic showFX{ false }; inline static const String SHOW_FX{ "Show FX" }; ListenableAtomic darkMode{ JAS_DARKMODE_DEFAULT }; inline static const String DARK_MODE{ "Dark Mode" }; inline static const String UI_DUMMY_PARAM{ "UI Update" }; }; // Misc inline static constexpr int FRAME_RATE{ 60 }; inline static constexpr bool REAPER_INTEGRATION_ENABLED{ JAS_VST3_REAPER_INTEGRATION }; // Sample storage inline static constexpr bool USE_FILE_REFERENCE{ true }; inline static constexpr int STORED_BITRATE{ 16 }; inline static constexpr double MAX_FILE_SIZE{ 320000000.0 }; // in bits, 40MB // Tuning inline static const String SEMITONE_TUNING{ "Semitone Tuning" }; inline static const String CENT_TUNING{ "Cent Tuning" }; inline static const String PITCH_WHEEL_RANGE{ "Pitch Wheel Range" }; inline static const String WIDE_TUNING{ "Wide Tuning" }; inline static const String A4_HZ{ "A4 Frequency" }; inline static const String WAVEFORM_SEMITONE_TUNING{ "Waveform Semitone Tuning" }; inline static const String WAVEFORM_CENT_TUNING{ "Waveform Cent Tuning" }; // Sample envelope inline static const String ATTACK{ "Attack Time" }; inline static const String RELEASE{ "Release Time" }; inline static const NormalisableRange ENVELOPE_TIME_RANGE{ 0.f, 5000.f, 1.f }; inline static const String ATTACK_SHAPE{ "Attack Curve Shape" }; inline static const String RELEASE_SHAPE{ "Release Curve Shape" }; inline static const String CROSSFADE_SAMPLES{"Crossfade Samples"}; // Sample playback inline static const String PLAY_UNTIL_END{ "Play Until End" }; inline static const String IS_LOOPING{ "Loop" }; inline static const String LOOPING_HAS_START{ "Loop With Start" }; inline static const String LOOPING_HAS_END{ "Loop With End" }; inline static const String IS_PING_PONG{ "Ping Pong" }; inline static const String PLAYBACK_MODE{ "Playback Mode" }; inline static const StringArray PLAYBACK_MODE_LABELS{ "Basic", "Bungee" }; // for IDs and display enum PLAYBACK_MODES : std::uint8_t { BASIC, BUNGEE, }; /** Returns an enum representation of a playback mode given a float */ inline PLAYBACK_MODES getPlaybackMode(int value) { return static_cast(value); } /** Skipping antialiasing can be an interesting effect */ inline static const String SKIP_ANTIALIASING{ "Lo-fi Resampling" }; // Some controls for advanced playback inline static const String SPEED_FACTOR{ "Playback Speed" }; inline static const String OCTAVE_SPEED_FACTOR{ "Octave Speed Factor" }; inline static constexpr float WAVETABLE_CUTOFF_HZ{ 20 }; // The cutoff frequency for "wavetable mode" inline static const String DISABLE_WAVETABLE_MODE{ "Disable Waveform Mode" }; inline static const String SAMPLE_GAIN{ "Sample Gain" }; inline static const String MONO_OUTPUT{ "Mono Output" }; inline static const String DISABLE_VELOCITY{ "Disable Velocity" }; inline static constexpr int MAX_VOICES{ 256 }; inline static const String NUM_VOICES{ "Voice Count" }; inline static constexpr juce::Range MIDI_NOTE_RANGE{ 0, 127 }; inline static const String MIDI_START{ "MIDI Range Start" }; inline static const String MIDI_END{ "MIDI Range End" }; inline static const String MIDI_ROOT{ "MIDI Root Note" }; inline static const String FOLLOW_MIDI_PITCH{ "Follow MIDI Pitch" }; // FX parameters inline static const String REVERB_ENABLED{ "Reverb Enabled" }; inline static const String REVERB_MIX{ "Reverb Mix" }; inline static const String REVERB_SIZE{ "Reverb Size" }; inline static constexpr Range REVERB_SIZE_RANGE{ 5.f, 100.f}; inline static const String REVERB_DAMPING{ "Reverb Damping" }; inline static constexpr Range REVERB_DAMPING_RANGE{ 0.f, 95.f }; inline static const String REVERB_LOWS{ "Reverb Lows" }; // These controls map to filters built into Gin's SimpleVerb inline static constexpr Range REVERB_LOWS_RANGE{ 0.f, 1.f }; inline static const String REVERB_HIGHS{ "Reverb Highs" }; inline static constexpr Range REVERB_HIGHS_RANGE{ 0.f, 1.f }; inline static const String REVERB_PREDELAY{ "Reverb Predelay" }; inline static const String DISTORTION_ENABLED{ "Distortion Enabled" }; inline static const String DISTORTION_DENSITY{ "Distortion Density" }; inline static constexpr Range DISTORTION_DENSITY_RANGE{ -0.5f, 1.f }; inline static const String DISTORTION_HIGHPASS{ "Distortion Highpass" }; inline static constexpr Range DISTORTION_HIGHPASS_RANGE{ 0.f, 0.999f }; inline static const String DISTORTION_MIX{ "Distortion Mix" }; inline static const String EQ_ENABLED{ "EQ Enabled" }; inline static const String EQ_LOW_GAIN{ "EQ Low Gain" }; inline static const NormalisableRange EQ_GAIN_RANGE{ -18.f, 12.f, 0.1f }; // Decibels inline static const String EQ_MID_GAIN{ "EQ Mid Gain" }; inline static const String EQ_HIGH_GAIN{ "EQ High Gain" }; inline static const String EQ_LOW_FREQ{ "EQ Low Cutoff" }; inline static constexpr Range EQ_LOW_FREQ_RANGE{ 25.f, 600.f }; inline static constexpr float EQ_LOW_FREQ_DEFAULT{ 200.f }; inline static const String EQ_HIGH_FREQ{ "EQ High Cutoff" }; inline static constexpr Range EQ_HIGH_FREQ_RANGE{ 700.f, 15500.f }; inline static constexpr float EQ_HIGH_FREQ_DEFAULT{ 2000.f }; inline static const String CHORUS_ENABLED{ "Chorus Enabled" }; inline static const String CHORUS_RATE{ "Chorus Rate" }; inline static const NormalisableRange CHORUS_RATE_RANGE{ 0.1f, 20.f, 0.1f, 0.7f }; // in hz, upper range could be extended to 100hz inline static const String CHORUS_DEPTH{ "Chorus Depth" }; inline static const NormalisableRange CHORUS_DEPTH_RANGE{ 0.01f, 1.f, 0.01f, 0.5f }; inline static const String CHORUS_FEEDBACK{ "Chorus Feedback" }; inline static constexpr Range CHORUS_FEEDBACK_RANGE{ -0.95f, 0.95f }; inline static const String CHORUS_CENTER_DELAY{ "Chorus Center Delay" }; inline static constexpr Range CHORUS_CENTER_DELAY_RANGE{ 0.f, 100.f }; // in ms inline static const String CHORUS_MIX{ "Chorus Mix" }; inline static const String FX_PERM{ "FX Ordering" }; enum FxTypes : std::uint8_t { DISTORTION, CHORUS, REVERB, EQ }; inline static const String PRE_FX{ "FX Before Envelope" }; inline static constexpr float FX_TAIL_OFF_MAX{ 0.0001f }; // The cutoff RMS value for tailing off effects //============================================================================== /** Returns a permutation of FxTypes, given a representative integer */ inline std::array paramToPerm(int fxParam) { std::array types{ DISTORTION, CHORUS, REVERB, EQ }; std::array perm{}; int factorial = 6; // 3! for (int i = 0; i < 4; i++) { int index = fxParam / factorial; perm[i] = types[index]; for (int j = index; j < 3; j++) types[j] = types[j + 1]; fxParam %= factorial; if (i < 3) factorial /= 3 - i; } return perm; } /** Returns a representative integer, given a permutation of FxTypes */ static int permToParam(std::array fxPerm) { std::array types{ DISTORTION, CHORUS, REVERB, EQ }; int result = 0; int factorial = 6; // 3! for (int i = 0; i < 3; i++) { int type = fxPerm[i]; int index; for (index = 0; index < types.size(); index++) if (type == types[index]) break; result += factorial * index; for (int j = index; j < 3 - i; j++) types[j] = types[j + 1]; if (i < 3) factorial /= 3 - i; } return result; } // Units inline static const String SEMITONE_UNIT{ "sm" }; inline static const String CENT_UNIT{ "%" }; inline static const String TIME_UNIT{ "ms" }; inline static const String TIME_UNIT_LONG{ "sec" }; inline static const String SPEED_UNIT{ "x" }; inline static const String VOLUME_UNIT{ "dB" }; inline static const String FREQUENCY_UNIT{ "Hz" }; //============================================================================== namespace Version { // For DAW compatibility, these must not change once released constexpr int V1 = 100; constexpr int V1_1 = 101; constexpr int V1_2 = 102; constexpr int V1_3 = 1030; constexpr int V1_3_2 = 1032; } /** Utility to add an integer parameter to the layout */ inline void addInt(juce::AudioProcessorValueTreeState::ParameterLayout& layout, const juce::String& identifier, int defaultValue, const juce::NormalisableRange& range, int versionNum, const std::function& formatFunc = nullptr) { layout.add(std::make_unique( juce::ParameterID{ identifier, versionNum }, identifier, range.start, range.end, defaultValue, juce::AudioParameterIntAttributes{}.withStringFromValueFunction(formatFunc) )); } /** Utility to add a float parameter to the layout */ inline void addFloat(juce::AudioProcessorValueTreeState::ParameterLayout& layout, const juce::String& identifier, float defaultValue, const juce::NormalisableRange& range, int versionNum, const std::function& formatFunc = nullptr) { layout.add(std::make_unique( juce::ParameterID{ identifier, versionNum }, identifier, range, defaultValue, juce::AudioParameterFloatAttributes{}.withStringFromValueFunction(formatFunc) )); } /** Utility to add a boolean parameter to the layout */ inline void addBool(juce::AudioProcessorValueTreeState::ParameterLayout& layout, const juce::String& identifier, bool defaultValue, int versionNum, const std::function& formatFunc = nullptr) { layout.add(std::make_unique( juce::ParameterID{ identifier, versionNum }, identifier, defaultValue, juce::AudioParameterBoolAttributes{}.withStringFromValueFunction(formatFunc) )); }; /** Utility to add a choice parameter to the layout */ inline void addChoice(juce::AudioProcessorValueTreeState::ParameterLayout& layout, const juce::String& identifier, int defaultIndex, const juce::StringArray& choicesToUse, int versionNum, const std::function& formatFunc = nullptr) { layout.add(std::make_unique( juce::ParameterID{ identifier, versionNum }, identifier, choicesToUse, defaultIndex, juce::AudioParameterChoiceAttributes{}.withStringFromValueFunction(formatFunc) )); } template juce::NormalisableRange addSkew(juce::NormalisableRange range, T skewCenter) { auto r = range.getRange(); range.skew = std::log(0.5f) / std::log((float(skewCenter) - r.getStart()) / r.getLength()); return range; } /** This inverts the proportions, such that "increasing" a slider value will decrease the parameter value */ template juce::NormalisableRange invertProportions(juce::NormalisableRange range) { auto convertFrom0To1Function = [](T rangeStart, T rangeEnd, T normalised) { return juce::jmap(normalised, rangeEnd, rangeStart); }; auto convertTo0From1Function = [](T rangeStart, T rangeEnd, T value) { return juce::jmap(value, rangeEnd, rangeStart, 0.0f, 1.0f); }; auto newRange = juce::NormalisableRange{ range.start, range.end, convertFrom0To1Function, convertTo0From1Function }; newRange.interval = range.interval; return newRange; } /** Returns a function that appends a suffix to an integer */ inline std::function suffixI(const juce::String& suffix) { return [suffix](int value, int) { return juce::String(value) + suffix; }; } /** Returns a function that appends a suffix to a float. */ inline std::function suffixF(const juce::String& suffix, float interval) { return [suffix, interval](float value, int maxLength) { int numDecimalPlaces = String{ int(1.f / interval) }.length() - 1; auto asText{ String{value, numDecimalPlaces} + suffix }; return maxLength > 0 ? asText.substring(0, maxLength) : asText; }; } const auto FORMAT_MIDI_NOTE = [](int value, int) -> String { return juce::MidiMessage::getMidiNoteName(value, true, true, 4); }; const auto FORMAT_PERM_VALUE = [](int value, int) -> String { std::array perm = paramToPerm(value); String result; for (auto fx : perm) { switch (fx) { case DISTORTION: result += "D"; break; case CHORUS: result += "C"; break; case REVERB: result += "R"; break; case EQ: result += "E"; break; } } return result; }; inline juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout() { juce::AudioProcessorValueTreeState::ParameterLayout layout; addInt(layout, SEMITONE_TUNING, 0, { -18, 18 }, Version::V1, suffixI(" " + SEMITONE_UNIT)); addInt(layout, CENT_TUNING, 0, { -100, 100 }, Version::V1, suffixI(CENT_UNIT)); addFloat(layout, PITCH_WHEEL_RANGE, 1.f, { 0.f, 12.f, 0.1f }, Version::V1_3, suffixF(" " + SEMITONE_UNIT, 0.1f)); addFloat(layout, WIDE_TUNING, 0.f, { -48.f, 48.f, 0.01f }, Version::V1_3, suffixF(" " + SEMITONE_UNIT, 0.01f)); addFloat(layout, A4_HZ, 440.f, { 400.f, 480.f, 0.1f }, Version::V1_3); addInt(layout, WAVEFORM_SEMITONE_TUNING, 0, { -24, 24 }, Version::V1, suffixI(" " + SEMITONE_UNIT)); addInt(layout, WAVEFORM_CENT_TUNING, 0, { -100, 100 }, Version::V1, suffixI(CENT_UNIT)); addBool(layout, SKIP_ANTIALIASING, false, Version::V1); addChoice(layout, PLAYBACK_MODE, 0, PLAYBACK_MODE_LABELS, Version::V1); addFloat(layout, SPEED_FACTOR, 1.f, addSkew({ 0.01f, 5.f, 0.01f }, 1.f), Version::V1, suffixF(SPEED_UNIT, 0.01f)); addFloat(layout, OCTAVE_SPEED_FACTOR, 0.f, { 0.f, 0.6f, 0.15f }, Version::V1, suffixF(SPEED_UNIT, 0.15f)); addBool(layout, DISABLE_WAVETABLE_MODE, false, Version::V1_3); addBool(layout, PLAY_UNTIL_END, false, Version::V1_3); addBool(layout, LOOPING_HAS_START, false, Version::V1); addBool(layout, IS_LOOPING, false, Version::V1); addBool(layout, LOOPING_HAS_END, false, Version::V1); addBool(layout, IS_PING_PONG, false, Version::V1_3_2); addFloat(layout, SAMPLE_GAIN, 0.f, addSkew({ -32.f, 16.f, 0.1f }, 0.f), Version::V1, suffixF(" " + VOLUME_UNIT, 0.1f)); addBool(layout, MONO_OUTPUT, false, Version::V1); addBool(layout, DISABLE_VELOCITY, false, Version::V1_3_2); addInt(layout, NUM_VOICES, 88, { 1, MAX_VOICES }, Version::V1_2, suffixI(" v")); addInt(layout, MIDI_START, 0, MIDI_NOTE_RANGE, Version::V1_1, FORMAT_MIDI_NOTE); addInt(layout, MIDI_END, 127, MIDI_NOTE_RANGE, Version::V1_1, FORMAT_MIDI_NOTE); addInt(layout, MIDI_ROOT, 69, MIDI_NOTE_RANGE, Version::V1_3, FORMAT_MIDI_NOTE); addBool(layout, FOLLOW_MIDI_PITCH, true, Version::V1_3); addInt(layout, FX_PERM, permToParam({ DISTORTION, CHORUS, REVERB, EQ }), { 0, 23 }, Version::V1, FORMAT_PERM_VALUE); addBool(layout, PRE_FX, false, Version::V1); addFloat(layout, ATTACK, 1, addSkew(ENVELOPE_TIME_RANGE, 1000.f), Version::V1, suffixF(" " + TIME_UNIT, ENVELOPE_TIME_RANGE.interval)); addFloat(layout, RELEASE, 1, addSkew(ENVELOPE_TIME_RANGE, 1000.f), Version::V1, suffixF(" " + TIME_UNIT, ENVELOPE_TIME_RANGE.interval)); addFloat(layout, ATTACK_SHAPE, 0.f, invertProportions(NormalisableRange{ -10.f, 10.f, 0.1f }), Version::V1); addFloat(layout, RELEASE_SHAPE, 2.f, { -10.f, 10.f, 0.1f }, Version::V1); addInt(layout, CROSSFADE_SAMPLES, 1000, { 0, 50000 }, Version::V1_2); addBool(layout, REVERB_ENABLED, false, Version::V1); addFloat(layout, REVERB_MIX, 0.5f, { 0.f, 1.f, 0.01f }, Version::V1); addFloat(layout, REVERB_SIZE, 0.5f, { REVERB_SIZE_RANGE, 1.f }, Version::V1); addFloat(layout, REVERB_DAMPING, 0.5f, { REVERB_DAMPING_RANGE, 1.f }, Version::V1); addFloat(layout, REVERB_LOWS, 0.5f, { REVERB_LOWS_RANGE, 0.01f }, Version::V1); addFloat(layout, REVERB_HIGHS, 0.5f, { REVERB_HIGHS_RANGE, 0.01f }, Version::V1); addFloat(layout, REVERB_PREDELAY, 0.5f, { 0.f, 500.f, 1.f, 0.5f }, Version::V1, suffixF(" " + TIME_UNIT, 0.5f)); addBool(layout, DISTORTION_ENABLED, false, Version::V1); addFloat(layout, DISTORTION_MIX, 1.f, { 0.f, 1.f, 0.01f }, Version::V1); addFloat(layout, DISTORTION_HIGHPASS, 0.f, { DISTORTION_HIGHPASS_RANGE, 0.01f }, Version::V1); addFloat(layout, DISTORTION_DENSITY, 0.f, addSkew({ DISTORTION_DENSITY_RANGE, 0.01f }, 0.f), Version::V1); addBool(layout, EQ_ENABLED, false, Version::V1); addFloat(layout, EQ_LOW_GAIN, 0.f, addSkew(EQ_GAIN_RANGE, 0.f), Version::V1, suffixF(" " + VOLUME_UNIT, EQ_GAIN_RANGE.interval)); addFloat(layout, EQ_MID_GAIN, 0.f, addSkew(EQ_GAIN_RANGE, 0.f), Version::V1, suffixF(" " + VOLUME_UNIT, EQ_GAIN_RANGE.interval)); addFloat(layout, EQ_HIGH_GAIN, 0.f, addSkew(EQ_GAIN_RANGE, 0.f), Version::V1, suffixF(" " + VOLUME_UNIT, EQ_GAIN_RANGE.interval)); addFloat(layout, EQ_LOW_FREQ, EQ_LOW_FREQ_DEFAULT, EQ_LOW_FREQ_RANGE, Version::V1, suffixF(" " + FREQUENCY_UNIT, 1.f)); addFloat(layout, EQ_HIGH_FREQ, EQ_HIGH_FREQ_DEFAULT, EQ_HIGH_FREQ_RANGE, Version::V1, suffixF(" " + FREQUENCY_UNIT, 1.f)); addBool(layout, CHORUS_ENABLED, false, Version::V1); addFloat(layout, CHORUS_RATE, 1.f, CHORUS_RATE_RANGE, Version::V1, suffixF(" " + FREQUENCY_UNIT, 1.f)); addFloat(layout, CHORUS_DEPTH, 0.25f, CHORUS_DEPTH_RANGE, Version::V1); addFloat(layout, CHORUS_FEEDBACK, 0.f, { CHORUS_FEEDBACK_RANGE, 0.01f }, Version::V1); addFloat(layout, CHORUS_CENTER_DELAY, 7.f, { CHORUS_CENTER_DELAY_RANGE, 1.f }, Version::V1, suffixF(" " + TIME_UNIT, 1.f)); addFloat(layout, CHORUS_MIX, 0.5f, { 0.f, 1.f, 0.01f }, Version::V1); // This is a dummy parameter to notify the host of state changes addBool(layout, State::UI_DUMMY_PARAM, true, Version::V1, [](bool, int) -> String { return "Dummy Param"; }); return layout; } } // namespace PluginParameters