AnalogTapeModel

Physical modelling signal processing for analog tape recording
Log | Files | Refs | Submodules | README | LICENSE

commit aa64e48871204df59878d79d76d2b1af535c3f9c
parent b19789057482088bd3f7db1ca76de4868fc73f0d
Author: jatinchowdhury18 <jatinchowdhury18@gmail.com>
Date:   Sat,  7 Nov 2020 09:51:23 -0800

Presets Improvements (#105)

* Add Sink presets

* Allow user-defined presets

* Update presets menu to use user preset folder with recursive structure

* Preset tweaks

* Add tooltip for presets menu

Co-authored-by: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Diffstat:
MPlugin/CHOWTapeModel.jucer | 43+++++++++++++++++++++++++++++++++----------
MPlugin/Source/GUI/Assets/gui.xml | 3+--
DPlugin/Source/GUI/Assets/preset_save_gui.xml | 99-------------------------------------------------------------------------------
MPlugin/Source/GUI/MyLNF.h | 42+++++++++++++++++++++++++++++++++++++++---
MPlugin/Source/PluginProcessor.cpp | 23++++++++++-------------
MPlugin/Source/PluginProcessor.h | 1+
MPlugin/Source/Presets/PresetComp.cpp | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
MPlugin/Source/Presets/PresetComp.h | 8++++++--
RPlugin/Source/Presets/PresetConfigs/Default.xml -> Plugin/Source/Presets/PresetConfigs/Default.chowpreset | 0
RPlugin/Source/Presets/PresetConfigs/LoFi.xml -> Plugin/Source/Presets/PresetConfigs/LoFi.chowpreset | 0
RPlugin/Source/Presets/PresetConfigs/OldTape.xml -> Plugin/Source/Presets/PresetConfigs/OldTape.chowpreset | 0
APlugin/Source/Presets/PresetConfigs/SNK_BassPusher.chowpreset | 33+++++++++++++++++++++++++++++++++
APlugin/Source/Presets/PresetConfigs/SNK_Chorus2.chowpreset | 33+++++++++++++++++++++++++++++++++
APlugin/Source/Presets/PresetConfigs/SNK_Chorus3.chowpreset | 33+++++++++++++++++++++++++++++++++
APlugin/Source/Presets/PresetConfigs/SNK_Chorus4.chowpreset | 33+++++++++++++++++++++++++++++++++
APlugin/Source/Presets/PresetConfigs/SNK_CleanFat.chowpreset | 32++++++++++++++++++++++++++++++++
APlugin/Source/Presets/PresetConfigs/SNK_Fat2.chowpreset | 32++++++++++++++++++++++++++++++++
APlugin/Source/Presets/PresetConfigs/SNK_Gritty.chowpreset | 32++++++++++++++++++++++++++++++++
APlugin/Source/Presets/PresetConfigs/SNK_Gritty2.chowpreset | 33+++++++++++++++++++++++++++++++++
APlugin/Source/Presets/PresetConfigs/SNK_SlightlyWobbly.chowpreset | 32++++++++++++++++++++++++++++++++
APlugin/Source/Presets/PresetConfigs/SNK_lofi.chowpreset | 32++++++++++++++++++++++++++++++++
RPlugin/Source/Presets/PresetConfigs/TC260.xml -> Plugin/Source/Presets/PresetConfigs/TC260.chowpreset | 0
RPlugin/Source/Presets/PresetConfigs/Underbiased.xml -> Plugin/Source/Presets/PresetConfigs/Underbiased.chowpreset | 0
RPlugin/Source/Presets/PresetConfigs/WoozyChorus.xml -> Plugin/Source/Presets/PresetConfigs/WoozyChorus.chowpreset | 0
MPlugin/Source/Presets/PresetManager.cpp | 151+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
MPlugin/Source/Presets/PresetManager.h | 14++++++++++++++
26 files changed, 681 insertions(+), 141 deletions(-)

diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer @@ -2,8 +2,8 @@ <JUCERPROJECT id="jDoXPz" name="CHOWTapeModel" projectType="audioplug" version="2.6.0" pluginFormats="buildAU,buildStandalone,buildVST,buildVST3" cppLanguageStandard="17" - companyName="chowdsp" companyEmail="chowdsp@gmail.com" defines="SAVE_PRESETS=0" - pluginManufacturerCode="Chow" reportAppUsage="0" jucerFormatVersion="1"> + companyName="chowdsp" companyEmail="chowdsp@gmail.com" pluginManufacturerCode="Chow" + reportAppUsage="0" jucerFormatVersion="1"> <MAINGROUP id="pXbPvR" name="CHOWTapeModel"> <GROUP id="{0178B10A-4A61-796A-5AB2-915D32AF6EEE}" name="Source"> <GROUP id="{8D673967-9B5D-8254-9062-4C4B14D4EAD9}" name="GUI"> @@ -12,8 +12,6 @@ <FILE id="DzcyPN" name="gui.xml" compile="0" resource="1" file="Source/GUI/Assets/gui.xml"/> <FILE id="xzpwn3" name="knob.svg" compile="0" resource="1" file="Source/GUI/Assets/knob.svg"/> <FILE id="VVaf9c" name="pointer.svg" compile="0" resource="1" file="Source/GUI/Assets/pointer.svg"/> - <FILE id="LbfXKZ" name="preset_save_gui.xml" compile="0" resource="1" - file="Source/GUI/Assets/preset_save_gui.xml"/> <FILE id="th5YSa" name="RobotoCondensed-Bold.ttf" compile="0" resource="1" file="Source/GUI/Assets/RobotoCondensed-Bold.ttf"/> <FILE id="bQP3yl" name="RobotoCondensed-Regular.ttf" compile="0" resource="1" @@ -49,12 +47,37 @@ </GROUP> <GROUP id="{71C1FCA8-E7B0-3B66-1340-F140C452FF6F}" name="Presets"> <GROUP id="{AB6F221D-98B5-9782-2241-321BA5DFB83C}" name="PresetConfigs"> - <FILE id="AymGjK" name="Default.xml" compile="0" resource="1" file="Source/Presets/PresetConfigs/Default.xml"/> - <FILE id="zlTcrf" name="LoFi.xml" compile="0" resource="1" file="Source/Presets/PresetConfigs/LoFi.xml"/> - <FILE id="drwXrv" name="OldTape.xml" compile="0" resource="1" file="Source/Presets/PresetConfigs/OldTape.xml"/> - <FILE id="O4vt7g" name="TC260.xml" compile="0" resource="1" file="Source/Presets/PresetConfigs/TC260.xml"/> - <FILE id="FkbdSt" name="Underbiased.xml" compile="0" resource="1" file="Source/Presets/PresetConfigs/Underbiased.xml"/> - <FILE id="wf7iTS" name="WoozyChorus.xml" compile="0" resource="1" file="Source/Presets/PresetConfigs/WoozyChorus.xml"/> + <FILE id="GvUTP4" name="Default.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/Default.chowpreset"/> + <FILE id="BPj68R" name="LoFi.chowpreset" compile="0" resource="1" file="Source/Presets/PresetConfigs/LoFi.chowpreset"/> + <FILE id="uAXu5z" name="OldTape.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/OldTape.chowpreset"/> + <FILE id="U14vbg" name="SNK_BassPusher.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/SNK_BassPusher.chowpreset"/> + <FILE id="k622ua" name="SNK_Chorus3.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/SNK_Chorus3.chowpreset"/> + <FILE id="MOk9cf" name="SNK_Chorus4.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/SNK_Chorus4.chowpreset"/> + <FILE id="LktNck" name="SNK_Chorus2.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/SNK_Chorus2.chowpreset"/> + <FILE id="Tft4Nf" name="SNK_CleanFat.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/SNK_CleanFat.chowpreset"/> + <FILE id="JIJgNs" name="SNK_Fat2.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/SNK_Fat2.chowpreset"/> + <FILE id="MRXbHr" name="SNK_Gritty.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/SNK_Gritty.chowpreset"/> + <FILE id="WbTlM4" name="SNK_Gritty2.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/SNK_Gritty2.chowpreset"/> + <FILE id="oYWApp" name="SNK_lofi.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/SNK_lofi.chowpreset"/> + <FILE id="dfvIsA" name="SNK_SlightlyWobbly.chowpreset" compile="0" + resource="1" file="Source/Presets/PresetConfigs/SNK_SlightlyWobbly.chowpreset"/> + <FILE id="RwaoWX" name="TC260.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/TC260.chowpreset"/> + <FILE id="cwOBOG" name="Underbiased.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/Underbiased.chowpreset"/> + <FILE id="uL6YwZ" name="WoozyChorus.chowpreset" compile="0" resource="1" + file="Source/Presets/PresetConfigs/WoozyChorus.chowpreset"/> </GROUP> <FILE id="ByxTdI" name="PresetComp.cpp" compile="1" resource="0" file="Source/Presets/PresetComp.cpp"/> <FILE id="jFQg5e" name="PresetComp.h" compile="0" resource="0" file="Source/Presets/PresetComp.h"/> diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml @@ -168,8 +168,7 @@ tooltip="Adds this plugin to a mix group. When the plugin is added to a group, the group parameters will be copied to this plugin, and their parameters will remain in sync."/> <MixGroupViz flex-grow="0.3" margin="5" padding="0" background-color="00000000"/> <presets margin="5" padding="0" background-color="00000000" border-color="595C6B" - radius="" border="" lookAndFeel="ComboBoxLNF" tooltip="Selects a preset for the plugin." - flex-grow="1.9" max-height="100"/> + radius="" border="" lookAndFeel="PresetsLNF" flex-grow="1.9" max-height="100"/> </View> </View> </magic> diff --git a/Plugin/Source/GUI/Assets/preset_save_gui.xml b/Plugin/Source/GUI/Assets/preset_save_gui.xml @@ -1,99 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<magic> - <Styles> - <Style name="default"> - <Nodes/> - <Classes> - <plot-view border="2" background-color="black" border-color="silver" display="contents"/> - <nomargin margin="0" padding="0" border="0"/> - <group margin="5" padding="5" border="2" flex-direction="column"/> - </Classes> - <Types> - <Slider border="0" slider-type="rotary-horizontal-vertical" slider-textbox="textbox-below"/> - <ToggleButton border="0" max-height="50" caption-size="0" text="Active"/> - <TextButton border="0" max-height="50" caption-size="0"/> - <ComboBox border="0" max-height="50" caption-size="0"/> - <Plot border="0" margin="0" padding="0" background-color="00000000" - radius="0"/> - <XYDragComponent border="0" margin="0" padding="0" background-color="00000000" - radius="0"/> - </Types> - </Style> - </Styles> - <View id="root" resizable="1" resize-corner="1" flex-direction="column" - background-color="FF7D2C2C" padding="0" width="600" height="650"> - <View max-height="100" padding="0" background-color="FF7D2C2C" margin="0"> - <Label text="CHOW Tape Model" font-size="20" padding="0" background-color="FF7D2C2C" - border="" max-width="180"/> - <Plot source="scope" plot-fill-color="FF000000" plot-color="FFCC7D12" - padding="0" background-color="FF000000" plot-decay="0.0"/> - </View> - <View background-color="FF7D2C2C" padding="0" margin=""> - <View flex-direction="column" margin="5" padding=""> - <Label max-height="5"/> - <Slider caption="Input Gain [dB]" parameter="ingain" slider-textbox="textbox-below" - slider-type="rotary-horizontal-vertical" max-height="160"/> - <ComboBox caption="Oversampling" parameter="os" max-height="70"/> - <Slider caption="Dry/Wet" parameter="drywet" slider-textbox="textbox-below" - slider-type="rotary-horizontal-vertical" max-height="170"/> - <Slider caption="Output Gain [dB]" parameter="outgain" slider-textbox="textbox-below" - slider-type="rotary-horizontal-vertical" max-height="170"/> - </View> - <View flex-direction="column" tab-color=""> - <Label max-height="40" text="Hysteresis" font-size="18" justification="centred"/> - <Slider caption="Bias" parameter="width" slider-type="rotary-horizontal-vertical" slider-textbox="textbox-below"/> - <Slider caption="Saturation" parameter="sat" slider-textbox="textbox-below" - slider-type="rotary-horizontal-vertical"/> - <Slider caption="Drive" parameter="drive" slider-type="rotary-horizontal-vertical" slider-textbox="textbox-below"/> - </View> - <View display="tabbed" padding="0"> - <View flex-direction="column" tab-caption="Loss" tab-color="" background-color="" - padding="0" margin="0"> - <Label max-height="40" text="Loss" justification="centred" font-size="18" - padding="0" margin="0"/> - <Slider caption="Gap [mm]" parameter="gap" slider-type="linear-horizontal" - slider-textbox="textbox-below" background-color="" padding="0"/> - <Slider caption="Thickness [mm]" parameter="thick" slider-textbox="textbox-below" - slider-type="linear-horizontal" padding="0"/> - <Slider caption="Spacing [mm]" parameter="spacing" slider-type="linear-horizontal" - slider-textbox="textbox-below" lookAndFeel="LookAndFeel_V4" padding="0"/> - <Slider caption="Speed [ips]" parameter="speed" slider-type="linear-horizontal" - slider-textbox="textbox-below" padding="0"/> - </View> - <View tab-caption="Degr." padding="0" flex-direction="column"> - <Label text="Degrade" justification="centred" font-size="18" max-height="40"/> - <Slider parameter="deg_depth" slider-type="rotary-horizontal-vertical" slider-textbox="textbox-below" - caption="Depth"/> - <Slider caption="Amount" parameter="deg_amt" slider-type="rotary-horizontal-vertical" slider-textbox="textbox-below"/> - <Slider parameter="deg_var" slider-type="rotary-horizontal-vertical" slider-textbox="textbox-below" - caption="Variance"/> - </View> - <View tab-caption="CHEW" padding="0" flex-direction="column"> - <Label text="CHEW" justification="centred" font-size="18" max-height="40"/> - <Slider parameter="chew_depth" slider-type="rotary-horizontal-vertical" slider-textbox="textbox-below" - caption="Depth" max-height="180"/> - <Slider caption="Frequency" parameter="chew_freq" slider-type="rotary-horizontal-vertical" - slider-textbox="textbox-below" max-height="200"/> - </View> - </View> - <View display="tabbed" padding="0"> - <View tab-caption="Flutter" flex-direction="column"> - <Label max-height="40" text="Flutter" justification="centred" font-size="18"/> - <Slider caption="Depth" parameter="depth" slider-type="rotary-horizontal-vertical" slider-textbox="textbox-below" - max-height="200"/> - <Slider caption="Rate" parameter="rate" slider-textbox="textbox-below" - slider-type="rotary-horizontal-vertical" max-height="200"/> - </View> - <View tab-caption="Wow" flex-direction="column"> - <Label max-height="40" text="Wow" justification="centred" font-size="18"/> - <Slider caption="Depth" parameter="wow_depth" slider-type="rotary-horizontal-vertical" slider-textbox="textbox-below" - max-height="200"/> - <Slider caption="Rate" parameter="wow_rate" slider-textbox="textbox-below" - slider-type="rotary-horizontal-vertical" max-height="200"/> - </View> - </View> - </View> - <presets max-height="30" margin="0" padding="0"/> - </View> -</magic> diff --git a/Plugin/Source/GUI/MyLNF.h b/Plugin/Source/GUI/MyLNF.h @@ -42,7 +42,12 @@ private: class ComboBoxLNF : public MyLNF { public: - ComboBoxLNF() {} + ComboBoxLNF() + { + setColour (PopupMenu::backgroundColourId, Colour (0xFF31323A)); + setColour (PopupMenu::highlightedBackgroundColourId, Colour (0x7FEAA92C)); + setColour (PopupMenu::highlightedTextColourId, Colours::white); + } void drawComboBox (Graphics& g, int width, int height, bool, int, int, int, int, ComboBox& box) override; void positionComboBoxText (ComboBox& box, Label& label) override; @@ -59,11 +64,43 @@ public: shortcutKeyText, icon, textColourToUse); } -private: + void drawPopupMenuBackground (Graphics& g, int width, int height) override + { + g.fillAll (findColour (PopupMenu::backgroundColourId)); + ignoreUnused (width, height); + } +private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxLNF) }; + +class PresetsLNF : public ComboBoxLNF +{ +public: + PresetsLNF() = default; + + void drawComboBox (Graphics& g, int width, int height, bool, int, int, int, int, ComboBox& box) override + { + auto cornerSize = 5.0f; + Rectangle<int> boxBounds (0, 0, width, height); + + g.setColour (box.findColour (ComboBox::backgroundColourId)); + g.fillRoundedRectangle (boxBounds.toFloat(), cornerSize); + } + + void positionComboBoxText (ComboBox& box, Label& label) override + { + auto b = box.getBounds(); + label.setBounds (b); + label.setFont (getComboBoxFont (box).boldened()); + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PresetsLNF) +}; + + class SpeedButtonLNF : public MyLNF { public: @@ -90,7 +127,6 @@ public: } private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SpeedButtonLNF) }; diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -14,6 +14,11 @@ #include "GUI/TooltipComp.h" #include "GUI/MixGroupViz.h" +namespace +{ + constexpr int maxNumPresets = 999; +} + //============================================================================== ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor() #ifndef JucePlugin_PreferredChannelConfigurations @@ -56,7 +61,7 @@ AudioProcessorValueTreeState::ParameterLayout ChowtapeModelAudioProcessor::creat params.push_back (std::make_unique<AudioParameterFloat> ("ingain", "Input Gain [dB]", -30.0f, 6.0f, 0.0f)); params.push_back (std::make_unique<AudioParameterFloat> ("outgain", "Output Gain [dB]", -30.0f, 30.0f, 0.0f)); params.push_back (std::make_unique<AudioParameterFloat> ("drywet", "Dry/Wet", 0.0f, 100.0f, 100.0f)); - params.push_back (std::make_unique<AudioParameterInt> ("preset", "Preset", 0, 10, 0)); + params.push_back (std::make_unique<AudioParameterInt> ("preset", "Preset", 0, maxNumPresets, 0)); ToneControl::createParameterLayout (params); HysteresisProcessor::createParameterLayout (params); @@ -119,6 +124,9 @@ int ChowtapeModelAudioProcessor::getCurrentProgram() void ChowtapeModelAudioProcessor::setCurrentProgram (int index) { + if (index > maxNumPresets) + return; + auto& presetParam = *vts.getRawParameterValue ("preset"); if ((int) presetParam == index) return; @@ -272,6 +280,7 @@ AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor() builder->registerJUCELookAndFeels(); builder->registerLookAndFeel ("MyLNF", std::make_unique<MyLNF>()); builder->registerLookAndFeel ("ComboBoxLNF", std::make_unique<ComboBoxLNF>()); + builder->registerLookAndFeel ("PresetsLNF", std::make_unique<PresetsLNF>()); builder->registerLookAndFeel ("SpeedButtonLNF", std::make_unique<SpeedButtonLNF>()); auto* speedHandle = dynamic_cast<AudioParameterFloat*> (vts.getParameter ("speed")); @@ -284,17 +293,6 @@ AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor() }); } -#if SAVE_PRESETS // Add button to save new presets - magicState.addTrigger ("savepreset", [=] - { - File xmlFile ("D:\\preset.xml"); - xmlFile.deleteFile(); - xmlFile.create(); - xmlFile.replaceWithText (vts.state.toXmlString()); - }); - - return new foleys::MagicPluginEditor (magicState, BinaryData::preset_save_gui_xml, BinaryData::preset_save_gui_xmlSize, std::move (builder)); -#else auto* editor = new foleys::MagicPluginEditor (magicState, BinaryData::gui_xml, BinaryData::gui_xmlSize, std::move (builder)); if (needsUpdate) @@ -306,7 +304,6 @@ AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor() } return editor; -#endif } //============================================================================== diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h @@ -70,6 +70,7 @@ public: void setStateInformation (const void* data, int sizeInBytes) override; PresetManager& getPresetManager() { return presetManager; } + const AudioProcessorValueTreeState& getVTS() { return vts; } private: AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); diff --git a/Plugin/Source/Presets/PresetComp.cpp b/Plugin/Source/Presets/PresetComp.cpp @@ -6,16 +6,35 @@ PresetComp::PresetComp (ChowtapeModelAudioProcessor& proc, PresetManager& manage { manager.addListener (this); + presetBox.setName ("Preset Manager"); + presetBox.setTooltip ("Use this menu to select presets, and to save and manage user presets"); + setColour (backgroundColourId, Colour (0xFF595C6B)); setColour (textColourId, Colours::white); addAndMakeVisible (presetBox); presetBox.setColour (ComboBox::ColourIds::backgroundColourId, Colours::transparentWhite); presetBox.setJustificationType (Justification::centred); - presetBox.addItemList (manager.getPresetChoices(), 1); + presetBox.setTextWhenNothingSelected ("No Preset selected..."); + loadPresetChoices(); + + addChildComponent (presetNameEditor); + presetNameEditor.setColour (TextEditor::backgroundColourId, Colour (0xFF595C6B)); + presetNameEditor.setColour (TextEditor::textColourId, Colours::white); + presetNameEditor.setColour (TextEditor::highlightColourId, Colour (0xFF8B3232)); + presetNameEditor.setColour (CaretComponent::caretColourId, Colour (0xFF8B3232)); + presetNameEditor.setFont (Font (16.0f).boldened()); + presetNameEditor.setMultiLine (false, false); + presetNameEditor.setJustification (Justification::centred); + + presetUpdated(); + presetBox.onChange = [=, &proc] { + const auto selectedId = presetBox.getSelectedId(); + if (selectedId >= 1000 || selectedId <= 0) + return; - presetBox.setSelectedItemIndex (proc.getCurrentProgram(), dontSendNotification); - presetBox.onChange = [=, &proc, &manager] { proc.setCurrentProgram (presetBox.getSelectedItemIndex()); }; + proc.setCurrentProgram (presetBox.getSelectedId() - 1); + }; } PresetComp::~PresetComp() @@ -23,9 +42,69 @@ PresetComp::~PresetComp() manager.removeListener (this); } +void PresetComp::loadPresetChoices() +{ + presetBox.getRootMenu()->clear(); + + const auto& presetChoices = manager.getPresetChoices(); + std::map<String, PopupMenu> presetChoicesMap; + for (int i = 0; i < presetChoices.size(); ++i) + { + const String& choice = presetChoices[i]; + String category = choice.upToFirstOccurrenceOf ("_", false, false); + if (category == "User") // user presets are treated specially + continue; + category = (category == choice) ? "CHOW" : category; + String presetName = choice.fromLastOccurrenceOf ("_", false, false); + + if (presetChoicesMap.find (category) == presetChoicesMap.end()) + presetChoicesMap[category] = PopupMenu(); + + presetChoicesMap[category].addItem (i+1, presetName); + } + + for (auto& presetGroup : presetChoicesMap) + presetBox.getRootMenu()->addSubMenu (presetGroup.first, presetGroup.second); + + // add user presets + auto& userPresetMenu = manager.getUserPresetMenu(); + if (userPresetMenu.containsAnyActiveItems()) + presetBox.getRootMenu()->addSubMenu ("User", userPresetMenu); + + addPresetOptions(); +} + +void PresetComp::addPresetOptions() +{ + auto menu = presetBox.getRootMenu(); + menu->addSeparator(); + + PopupMenu::Item saveItem { "Save" }; + saveItem.itemID = 1001; + saveItem.action = [=] { saveUserPreset(); }; + menu->addItem (saveItem); + + PopupMenu::Item goToFolderItem { "Go to Preset folder..." }; + goToFolderItem.itemID = 1002; + goToFolderItem.action = [=] { + presetUpdated(); + auto folder = manager.getUserPresetFolder(); + if (folder.isDirectory()) + folder.startAsProcess(); + else + manager.chooseUserPresetFolder(); + }; + menu->addItem (goToFolderItem); + + PopupMenu::Item chooseFolderItem { "Choose Preset folder..." }; + chooseFolderItem.itemID = 1003; + chooseFolderItem.action = [=] { presetUpdated(); manager.chooseUserPresetFolder(); }; + menu->addItem (chooseFolderItem); +} + void PresetComp::paint (Graphics& g) { - const auto cornerSize = 5.0f; + constexpr auto cornerSize = 5.0f; presetBox.setColour (PopupMenu::ColourIds::backgroundColourId, findColour (backgroundColourId)); g.setColour (findColour (backgroundColourId)); @@ -35,10 +114,34 @@ void PresetComp::paint (Graphics& g) void PresetComp::resized() { presetBox.setBounds (getLocalBounds()); + presetNameEditor.setBounds (getLocalBounds()); repaint(); } void PresetComp::presetUpdated() { - presetBox.setSelectedItemIndex (proc.getCurrentProgram(), dontSendNotification); + presetBox.setSelectedId (proc.getCurrentProgram() + 1, dontSendNotification); +} + +void PresetComp::saveUserPreset() +{ + presetNameEditor.setVisible (true); + presetNameEditor.toFront (true); + presetNameEditor.setText ("MyPreset"); + presetNameEditor.setHighlightedRegion ({ 0, 10 }); + + presetNameEditor.onReturnKey = [=] { + auto presetName = presetNameEditor.getText(); + presetNameEditor.setVisible (false); + + if (manager.saveUserPreset (presetName, proc.getVTS())) + { + loadPresetChoices(); + proc.setCurrentProgram (manager.getNumPresets() - 1); + } + else + { + presetUpdated(); + } + }; } diff --git a/Plugin/Source/Presets/PresetComp.h b/Plugin/Source/Presets/PresetComp.h @@ -5,7 +5,6 @@ #include "../PluginProcessor.h" class PresetComp : public Component, - public SettableTooltipClient, private PresetManager::Listener { public: @@ -23,9 +22,14 @@ public: void presetUpdated() override; private: + void loadPresetChoices(); + void addPresetOptions(); + void saveUserPreset(); + ChowtapeModelAudioProcessor& proc; PresetManager& manager; - ComboBox presetBox; + ComboBox presetBox; + TextEditor presetNameEditor; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PresetComp) }; diff --git a/Plugin/Source/Presets/PresetConfigs/Default.xml b/Plugin/Source/Presets/PresetConfigs/Default.chowpreset diff --git a/Plugin/Source/Presets/PresetConfigs/LoFi.xml b/Plugin/Source/Presets/PresetConfigs/LoFi.chowpreset diff --git a/Plugin/Source/Presets/PresetConfigs/OldTape.xml b/Plugin/Source/Presets/PresetConfigs/OldTape.chowpreset diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_BassPusher.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_BassPusher.chowpreset @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_Bass Pusher I"> + <Parameters> + <PARAM id="chew_depth" value="0.0"/> + <PARAM id="chew_freq" value="0.0"/> + <PARAM id="chew_var" value="0.0"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.0"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.0"/> + <PARAM id="drive" value="0.75"/> + <PARAM id="drywet" value="100.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="0.5"/> + <PARAM id="h_treble" value="-0.1500000357627869"/> + <PARAM id="ingain" value="2.0"/> + <PARAM id="mix_group" value="0.0"/> + <PARAM id="mode" value="0.0"/> + <PARAM id="os" value="1.0"/> + <PARAM id="outgain" value="-2.0"/> + <PARAM id="rate" value="0.2999999821186066"/> + <PARAM id="sat" value="0.75"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="3.750000476837158"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="0.5999999642372131"/> + <PARAM id="wow_depth" value="0.0"/> + <PARAM id="wow_rate" value="0.1499999910593033"/> + <PARAM id="preset" value="6"/> + <PARAM id="h_tfreq"/> + </Parameters> +</Preset> diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_Chorus2.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_Chorus2.chowpreset @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_Chorus II"> + <Parameters> + <PARAM id="chew_depth" value="0.25"/> + <PARAM id="chew_freq" value="0.5999999642372131"/> + <PARAM id="chew_var" value="0.2999999821186066"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.0"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.7999999523162842"/> + <PARAM id="drive" value="0.5"/> + <PARAM id="drywet" value="65.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="-0.2100000381469727"/> + <PARAM id="h_treble" value="-0.14000004529953"/> + <PARAM id="ingain" value="0.0"/> + <PARAM id="mode" value="0"/> + <PARAM id="os" value="1"/> + <PARAM id="outgain" value="0.0"/> + <PARAM id="rate" value="0.3199999928474426"/> + <PARAM id="sat" value="0.5"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="7.500000476837158"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="0.5"/> + <PARAM id="wow_depth" value="0.7999999523162842"/> + <PARAM id="wow_rate" value="0.2599999904632568"/> + <PARAM id="preset" value="7"/> + <PARAM id="h_tfreq"/> + <PARAM id="mix_group"/> + </Parameters> +</Preset> diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_Chorus3.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_Chorus3.chowpreset @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_Chorus III"> + <Parameters> + <PARAM id="chew_depth" value="0.25"/> + <PARAM id="chew_freq" value="0.5999999642372131"/> + <PARAM id="chew_var" value="0.2999999821186066"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.0"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.2800000011920929"/> + <PARAM id="drive" value="0.5"/> + <PARAM id="drywet" value="55.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="-0.06999999284744263"/> + <PARAM id="h_treble" value="0.2200000286102295"/> + <PARAM id="ingain" value="0.0"/> + <PARAM id="mode" value="0"/> + <PARAM id="os" value="1"/> + <PARAM id="outgain" value="0.0"/> + <PARAM id="rate" value="0.119999997317791"/> + <PARAM id="sat" value="0.5"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="7.500000476837158"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="0.5"/> + <PARAM id="wow_depth" value="0.7999999523162842"/> + <PARAM id="wow_rate" value="0.1799999922513962"/> + <PARAM id="preset" value="8"/> + <PARAM id="h_tfreq"/> + <PARAM id="mix_group" value="0.0"/> + </Parameters> +</Preset> diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_Chorus4.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_Chorus4.chowpreset @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_Chorus IV"> + <Parameters> + <PARAM id="chew_depth" value="0.25"/> + <PARAM id="chew_freq" value="0.5999999642372131"/> + <PARAM id="chew_var" value="0.2999999821186066"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.0"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.7999999523162842"/> + <PARAM id="drive" value="0.5"/> + <PARAM id="drywet" value="55.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="-0.06999999284744263"/> + <PARAM id="h_treble" value="0.2200000286102295"/> + <PARAM id="ingain" value="0.0"/> + <PARAM id="mode" value="0"/> + <PARAM id="os" value="1"/> + <PARAM id="outgain" value="0.0"/> + <PARAM id="rate" value="0.07000000029802322"/> + <PARAM id="sat" value="0.5"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="3.750000476837158"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="1.0"/> + <PARAM id="wow_depth" value="1.0"/> + <PARAM id="wow_rate" value="0.05999999865889549"/> + <PARAM id="preset" value="9"/> + <PARAM id="h_tfreq"/> + <PARAM id="mix_group" value="0.0"/> + </Parameters> +</Preset> diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_CleanFat.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_CleanFat.chowpreset @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_Clean Fat"> + <Parameters> + <PARAM id="chew_depth" value="0.0"/> + <PARAM id="chew_freq" value="0.0"/> + <PARAM id="chew_var" value="0.0"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.0"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.0"/> + <PARAM id="drive" value="0.6499999761581421"/> + <PARAM id="drywet" value="100.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="-0.5"/> + <PARAM id="h_treble" value="-0.5"/> + <PARAM id="ingain" value="0.0"/> + <PARAM id="mix_group" value="0.0"/> + <PARAM id="mode" value="3.0"/> + <PARAM id="os" value="1.0"/> + <PARAM id="outgain" value="0.0"/> + <PARAM id="rate" value="0.2999999821186066"/> + <PARAM id="sat" value="0.6499999761581421"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="29.99999618530273"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="0.75"/> + <PARAM id="wow_depth" value="0.0"/> + <PARAM id="wow_rate" value="0.25"/> + <PARAM id="preset" value="10"/> + </Parameters> +</Preset> diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_Fat2.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_Fat2.chowpreset @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_Fat II"> + <Parameters> + <PARAM id="chew_depth" value="0.0"/> + <PARAM id="chew_freq" value="0.0"/> + <PARAM id="chew_var" value="0.0"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.0"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.0"/> + <PARAM id="drive" value="0.75"/> + <PARAM id="drywet" value="100.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="-0.300000011920929"/> + <PARAM id="h_treble" value="-0.300000011920929"/> + <PARAM id="ingain" value="0.0"/> + <PARAM id="mix_group" value="0.0"/> + <PARAM id="mode" value="0.0"/> + <PARAM id="os" value="1.0"/> + <PARAM id="outgain" value="0.0"/> + <PARAM id="rate" value="0.2999999821186066"/> + <PARAM id="sat" value="0.6499999761581421"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="7.500000476837158"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="0.75"/> + <PARAM id="wow_depth" value="0.0"/> + <PARAM id="wow_rate" value="0.25"/> + <PARAM id="preset" value="11"/> + </Parameters> +</Preset> diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_Gritty.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_Gritty.chowpreset @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_Gritty"> + <Parameters> + <PARAM id="chew_depth" value="0.0"/> + <PARAM id="chew_freq" value="0.0"/> + <PARAM id="chew_var" value="0.0"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.0"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.0"/> + <PARAM id="drive" value="1.0"/> + <PARAM id="drywet" value="100.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="0.1499999761581421"/> + <PARAM id="h_treble" value="0.2999999523162842"/> + <PARAM id="ingain" value="2.0"/> + <PARAM id="mix_group" value="0.0"/> + <PARAM id="mode" value="0.0"/> + <PARAM id="os" value="1.0"/> + <PARAM id="outgain" value="-2.0"/> + <PARAM id="rate" value="0.2999999821186066"/> + <PARAM id="sat" value="0.5"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="3.499999761581421"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="0.6499999761581421"/> + <PARAM id="wow_depth" value="0.0"/> + <PARAM id="wow_rate" value="0.1499999910593033"/> + <PARAM id="preset" value="12"/> + </Parameters> +</Preset> diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_Gritty2.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_Gritty2.chowpreset @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_Gritty II"> + <Parameters> + <PARAM id="chew_depth" value="0.0"/> + <PARAM id="chew_freq" value="0.0"/> + <PARAM id="chew_var" value="0.0"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.0"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.0"/> + <PARAM id="drive" value="1.0"/> + <PARAM id="drywet" value="100.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="-0.25"/> + <PARAM id="h_treble" value="0.25"/> + <PARAM id="ingain" value="2.0"/> + <PARAM id="mix_group" value="0.0"/> + <PARAM id="mode" value="0.0"/> + <PARAM id="os" value="1.0"/> + <PARAM id="outgain" value="-2.0"/> + <PARAM id="rate" value="0.2999999821186066"/> + <PARAM id="sat" value="0.5999999642372131"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="3.499999761581421"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="0.3499999940395355"/> + <PARAM id="wow_depth" value="0.0"/> + <PARAM id="wow_rate" value="0.1499999910593033"/> + <PARAM id="preset" value="13"/> + </Parameters> +</Preset> + diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_SlightlyWobbly.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_SlightlyWobbly.chowpreset @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_Slightly Wobbly"> + <Parameters> + <PARAM id="chew_depth" value="0.04999999701976776"/> + <PARAM id="chew_freq" value="0.07000000029802322"/> + <PARAM id="chew_var" value="0.199999988079071"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.2099999934434891"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.6599999666213989"/> + <PARAM id="drive" value="0.5"/> + <PARAM id="drywet" value="100.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="-0.14000004529953"/> + <PARAM id="h_treble" value="0.2200000286102295"/> + <PARAM id="ingain" value="0.0"/> + <PARAM id="mix_group" value="0.0"/> + <PARAM id="mode" value="0.0"/> + <PARAM id="os" value="1.0"/> + <PARAM id="outgain" value="0.0"/> + <PARAM id="rate" value="0.2599999904632568"/> + <PARAM id="sat" value="0.5"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="15.00000095367432"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="0.5399999618530273"/> + <PARAM id="wow_depth" value="0.6100000143051147"/> + <PARAM id="wow_rate" value="0.2099999934434891"/> + <PARAM id="preset" value="15"/> + </Parameters> +</Preset> diff --git a/Plugin/Source/Presets/PresetConfigs/SNK_lofi.chowpreset b/Plugin/Source/Presets/PresetConfigs/SNK_lofi.chowpreset @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<Preset name="Sink_LoFi"> + <Parameters> + <PARAM id="chew_depth" value="0.0"/> + <PARAM id="chew_freq" value="0.3999999761581421"/> + <PARAM id="chew_var" value="0.0"/> + <PARAM id="deg_amt" value="0.0"/> + <PARAM id="deg_depth" value="0.1499999910593033"/> + <PARAM id="deg_var" value="0.0"/> + <PARAM id="depth" value="0.1499999910593033"/> + <PARAM id="drive" value="1.0"/> + <PARAM id="drywet" value="100.0"/> + <PARAM id="gap" value="9.999999974752427e-7"/> + <PARAM id="h_bass" value="0.25"/> + <PARAM id="h_treble" value="-0.1000000238418579"/> + <PARAM id="ingain" value="1.0"/> + <PARAM id="mix_group" value="0.0"/> + <PARAM id="mode" value="0.0"/> + <PARAM id="os" value="1.0"/> + <PARAM id="outgain" value="-1.5"/> + <PARAM id="rate" value="0.2999999821186066"/> + <PARAM id="sat" value="0.6499999761581421"/> + <PARAM id="spacing" value="9.999999974752427e-7"/> + <PARAM id="speed" value="1.75"/> + <PARAM id="thick" value="9.999999974752427e-7"/> + <PARAM id="width" value="0.449999988079071"/> + <PARAM id="wow_depth" value="0.25"/> + <PARAM id="wow_rate" value="0.1499999910593033"/> + <PARAM id="preset" value="14"/> + </Parameters> +</Preset> diff --git a/Plugin/Source/Presets/PresetConfigs/TC260.xml b/Plugin/Source/Presets/PresetConfigs/TC260.chowpreset diff --git a/Plugin/Source/Presets/PresetConfigs/Underbiased.xml b/Plugin/Source/Presets/PresetConfigs/Underbiased.chowpreset diff --git a/Plugin/Source/Presets/PresetConfigs/WoozyChorus.xml b/Plugin/Source/Presets/PresetConfigs/WoozyChorus.chowpreset diff --git a/Plugin/Source/Presets/PresetManager.cpp b/Plugin/Source/Presets/PresetManager.cpp @@ -2,6 +2,11 @@ #include "../PluginProcessor.h" #include "PresetComp.h" +namespace +{ + static String userPresetPath = "ChowdhuryDSP/ChowTape/UserPresets.txt"; +} + Preset::Preset (String presetFile) { // load xml text from BinaryData @@ -16,8 +21,18 @@ Preset::Preset (String presetFile) } jassert (xmlText.isNotEmpty()); // preset does not exist!! + initialise (ValueTree::fromXml (xmlText)); + +} + +Preset::Preset (const File& presetFile) +{ + String xmlText = presetFile.loadFileAsString(); + initialise (ValueTree::fromXml (xmlText)); +} - ValueTree parentTree = ValueTree::fromXml (xmlText); +void Preset::initialise (const ValueTree& parentTree) +{ name = parentTree.getProperty ("name").toString(); jassert (name.isNotEmpty()); // Preset name not found!! @@ -50,12 +65,24 @@ StringArray PresetManager::getPresetChoices() void PresetManager::loadPresets() { - presets.add (new Preset ("Default.xml")); - presets.add (new Preset ("TC260.xml")); - presets.add (new Preset ("LoFi.xml")); - presets.add (new Preset ("WoozyChorus.xml")); - presets.add (new Preset ("OldTape.xml")); - presets.add (new Preset ("Underbiased.xml")); + // load factory presets + presets.add (std::make_unique<Preset> ("Default.chowpreset")); + presets.add (std::make_unique<Preset> ("TC260.chowpreset")); + presets.add (std::make_unique<Preset> ("LoFi.chowpreset")); + presets.add (std::make_unique<Preset> ("WoozyChorus.chowpreset")); + presets.add (std::make_unique<Preset> ("OldTape.chowpreset")); + presets.add (std::make_unique<Preset> ("Underbiased.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_BassPusher.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_Chorus2.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_Chorus3.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_Chorus4.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_CleanFat.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_Fat2.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_Gritty.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_Gritty2.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_lofi.chowpreset")); + presets.add (std::make_unique<Preset> ("SNK_SlightlyWobbly.chowpreset")); + numFactoryPresets = presets.size(); for (auto* p : presets) { @@ -65,6 +92,8 @@ void PresetManager::loadPresets() } maxIdx++; + + updateUserPresets(); } String PresetManager::getPresetName (int idx) @@ -96,3 +125,111 @@ void PresetManager::registerPresetsComponent (foleys::MagicGUIBuilder& builder) static Identifier presetsID { "presets" }; builder.registerFactory (presetsID, &PresetComponentItem::factory); } + +bool PresetManager::saveUserPreset (const String& name, const AudioProcessorValueTreeState& vts) +{ + if (! userPresetFolder.isDirectory()) // if not set, choose preset folder + chooseUserPresetFolder(); + + if (! userPresetFolder.isDirectory()) // user doesn't want to choose preset folder, cancelling... + return false; + + // create file to save preset + File saveFile = userPresetFolder.getChildFile (name + ".chowpreset"); + saveFile.deleteFile(); + auto result = saveFile.create(); + + if (result.failed()) // unable to create file; + return false; + + auto stateXml = vts.state.createXml(); + if (stateXml == nullptr) // invalid xml + return false; + + // create preset XML + auto presetXml = std::make_unique<XmlElement> ("Preset"); + presetXml->setAttribute ("name", "User_" + name); + + auto xmlParameters = std::make_unique<XmlElement> ("Parameters"); + forEachXmlChildElementWithTagName (*stateXml, p, "PARAM") + { + if (p->getAttributeValue (0) == "preset") + p->setAttribute ("value", maxIdx); + + xmlParameters->addChildElement (new XmlElement (*p)); + } + presetXml->addChildElement (xmlParameters.release()); + + saveFile.replaceWithText (presetXml->toString()); + updateUserPresets(); + return true; +} + +File PresetManager::getUserPresetConfigFile() const +{ + File updatePresetFile = File::getSpecialLocation (File::userApplicationDataDirectory); + return updatePresetFile.getChildFile (userPresetPath); +} + +void PresetManager::chooseUserPresetFolder() +{ + FileChooser chooser ("Choose preset folder"); + if (chooser.browseForDirectory()) + { + auto result = chooser.getResult(); + auto config = getUserPresetConfigFile(); + config.deleteFile(); + config.create(); + config.replaceWithText (result.getFullPathName()); + updateUserPresets(); + } +} + +void PresetManager::loadPresetFolder (PopupMenu& menu, File& directory) +{ + Array<File> presetFiles; + for (auto& userPreset : directory.findChildFiles (File::findFilesAndDirectories, false)) + { + if (userPreset.isDirectory()) + { + auto relativePath = userPreset.getRelativePathFrom (userPresetFolder); + auto firstSubfolder = relativePath.fromLastOccurrenceOf (File::getSeparatorString(), false, false); + + PopupMenu subMenu; + loadPresetFolder (subMenu, userPreset); + menu.addSubMenu (firstSubfolder, subMenu); + } + + if (userPreset.hasFileExtension (".chowpreset")) + presetFiles.add (userPreset); + } + + for (auto& userPreset : presetFiles) + { + auto relativePath = userPreset.getRelativePathFrom (userPresetFolder); + auto newPreset = presets.add (std::make_unique<Preset> (userPreset)); + newPreset->index = maxIdx; + presetMap.set (newPreset->index, newPreset); + menu.addItem (newPreset->index + 1, newPreset->name.fromFirstOccurrenceOf ("User_", false, false)); + maxIdx++; + } +} + +void PresetManager::updateUserPresets() +{ + // set preset folder + auto config = getUserPresetConfigFile(); + if (config.existsAsFile()) + userPresetFolder = File (config.loadFileAsString()); + else + userPresetFolder = File(); + + // remove existing user presets + presets.removeRange (numFactoryPresets, maxIdx - numFactoryPresets); + for (; maxIdx > numFactoryPresets; maxIdx--) + presetMap.remove (maxIdx - 1); + userPresetMenu.clear(); + + if (userPresetFolder.isDirectory()) + loadPresetFolder (userPresetMenu, userPresetFolder); +} diff --git a/Plugin/Source/Presets/PresetManager.h b/Plugin/Source/Presets/PresetManager.h @@ -6,6 +6,8 @@ struct Preset { Preset (String presetFile); + Preset (const File& presetFile); + void initialise (const ValueTree& parentTree); String name; ValueTree state; @@ -28,6 +30,11 @@ public: void registerPresetsComponent (foleys::MagicGUIBuilder&); void presetUpdated() { listeners.call (&Listener::presetUpdated); } + File getUserPresetFolder() { return userPresetFolder; } + void chooseUserPresetFolder(); + bool saveUserPreset (const String& name, const AudioProcessorValueTreeState& vts); + const PopupMenu& getUserPresetMenu() const { return userPresetMenu; } + struct Listener { virtual ~Listener() {} @@ -38,6 +45,13 @@ public: void removeListener (Listener* l) { listeners.remove (l); } private: + File getUserPresetConfigFile() const; + void updateUserPresets(); + void loadPresetFolder (PopupMenu& menu, File& directory); + File userPresetFolder; + int numFactoryPresets = 0; + PopupMenu userPresetMenu; + HashMap<int, Preset*> presetMap; OwnedArray<Preset> presets; int maxIdx = 0;