From fcb670cee692daa96612ab71d29af4c0ed90885e Mon Sep 17 00:00:00 2001 From: SillyGesture <69798195+Johnbob7@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:20:39 -0500 Subject: [PATCH 1/8] Add profile save routine and breadcrumb feedback --- gui/src/plugin_manager.py | 54 ++++++++++++++++++++++++++++++++++++++- gui/src/qwidgets/core.py | 32 ++++++++++++++++++++--- 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/gui/src/plugin_manager.py b/gui/src/plugin_manager.py index 12d9816..c92de83 100644 --- a/gui/src/plugin_manager.py +++ b/gui/src/plugin_manager.py @@ -1,4 +1,7 @@ import json +import os + +from utils import config_dir class Parameter(): @@ -118,7 +121,9 @@ def initFromJSON(self, jsonFile: str): mode=param_data.get("mode", "dial"), min=param_data["min"], max=param_data["max"], - value=param_data.get("default", 1.0) + value=param_data.get("value", + param_data.get("default", + 1.0)) ) parameters.append(parameter) except KeyError as e: @@ -141,3 +146,50 @@ def initFromJSON(self, jsonFile: str): return -1 except ValueError as e: print(f"JSON Error: {e}") + + def serialize(self): + """Return the current plugin configuration as a dict.""" + plugin_data = [] + for plugin in self.plugins: + params = [] + for param in plugin.parameters: + params.append({ + "type": param.type, + "name": param.name, + "symbol": param.symbol, + "mode": param.mode, + "min": param.minimum, + "max": param.max, + "value": param.value, + "default": param.value, + }) + + plugin_data.append({ + "name": plugin.name, + "uri": plugin.uri, + "bypass": plugin.bypass, + "channels": plugin.channels, + "inputs": plugin.inputs, + "outputs": plugin.outputs, + "parameters": params, + }) + + return {"plugins": plugin_data} + + def save_to_profile(self, profile_name: str) -> str: + """Save the current plugin state to a profile file. + + Args: + profile_name: Name of the profile without the extension. + + Returns: + Path to the saved profile file. + """ + if not profile_name: + raise ValueError("Profile name is required to save the board") + + profile_path = os.path.join(config_dir, f"{profile_name}.json") + with open(profile_path, "w") as file: + json.dump(self.serialize(), file, indent=4) + + return profile_path diff --git a/gui/src/qwidgets/core.py b/gui/src/qwidgets/core.py index d62776c..c93ca28 100644 --- a/gui/src/qwidgets/core.py +++ b/gui/src/qwidgets/core.py @@ -3,7 +3,7 @@ import os from PyQt5.QtWidgets import QWidget, QStackedWidget, QVBoxLayout, QLabel from PyQt5.QtGui import QPainter, QPen -from PyQt5.QtCore import Qt, QRect, QLine +from PyQt5.QtCore import Qt, QRect, QLine, QTimer from plugin_manager import PluginManager, Plugin from modhostmanager import ( connectToModHost, setUpPlugins, setUpPatch, verifyParameters, @@ -100,6 +100,7 @@ def launch_board(self, selected_profile): board, mod_host_manager=modhost, restart_callback=self.show_start_screen, + profile_name=selected_profile, ) self.stack.addWidget(self.board_window) self.stack.setCurrentWidget(self.board_window) # Switch view @@ -135,11 +136,13 @@ def reset_modhost(self): class BoardWindow(QWidget): def __init__( - self, manager: PluginManager, mod_host_manager, restart_callback): + self, manager: PluginManager, mod_host_manager, restart_callback, + profile_name: str): super().__init__() self.plugins = manager self.mod_host_manager = mod_host_manager self.restart_callback = restart_callback + self.profile_name = profile_name self.backgroundColor = color_background self.param_page = 0 @@ -202,8 +205,11 @@ def keyPressEvent(self, event): if type(cur) is AddPluginBox: BreadcrumbsBar.navForward("SAVING...") BreadcrumbsBar.instance.repaint() - try_save() - BreadcrumbsBar.navBackward() + saved_locally = self.save_profile_to_disk() + saved_to_usb = False + if saved_locally: + saved_to_usb = try_save() + self.show_save_feedback(saved_locally and saved_to_usb) def swap_plugins(self, dist: int) -> bool: index = self.curIndex() @@ -458,6 +464,24 @@ def curItem(self) -> PluginBox | AddPluginBox: else: return self.pluginbox.add_plugin_box + def save_profile_to_disk(self) -> bool: + try: + self.plugins.save_to_profile(self.profile_name) + return True + except Exception as e: + print(f"Failed to save profile: {e}") + return False + + def show_save_feedback(self, success: bool): + message = "WRITE SUCCESSFUL" if success else "WRITE FAIL" + BreadcrumbsBar.crumbs[-1] = message + BreadcrumbsBar.instance.label.setText(BreadcrumbsBar.labelText()) + BreadcrumbsBar.instance.label.adjustSize() + QTimer.singleShot(1000, self.finish_save_feedback) + + def finish_save_feedback(self): + BreadcrumbsBar.navBackward() + class BoxOfPlugins(QWidget): pluginsPerPage: int = 3 From 768b586ccc1ae56a9b44f17d4cdeef677e1a10c4 Mon Sep 17 00:00:00 2001 From: John Putnam Date: Sat, 6 Dec 2025 15:39:46 -0500 Subject: [PATCH 2/8] Add config plugins/profiles directories --- .../ChorusPlugin/ChorusJUCE/ChorusPlugin.h | 169 +++++ .../ChorusPlugin/ChorusV1/ChorusPlugin.h | 251 +++++++ .../ChorusPlugin/ChorusV2/ChorusPlugin.h | 284 ++++++++ .../ChorusPlugin/ChorusV3/ChorusPlugin.h | 328 +++++++++ .../ChorusPlugin/ChorusV4/ChorusPlugin.h | 552 ++++++++++++++++ .../ChorusPlugin/ChorusV5/ChorusPlugin.h | 556 ++++++++++++++++ gui/config/plugins/ChorusPlugin/Main.cpp | 8 + .../CompressionPlugin/CompressionPlugin.h | 171 +++++ gui/config/plugins/CompressionPlugin/Main.cpp | 8 + .../plugins/DelayPlugin/DelayPlugin.jucer | 51 ++ .../DelayPluginV1/DelayPlugin.jucer | 51 ++ .../DelayPlugin/DelayPluginV1/DelayPluginV1.h | 184 ++++++ .../DelayPlugin/DelayPluginV1/Main.cpp | 11 + .../DelayPluginV2/DelayPlugin.jucer | 51 ++ .../DelayPlugin/DelayPluginV2/DelayPluginV2.h | 191 ++++++ .../DelayPlugin/DelayPluginV2/Main.cpp | 8 + .../DelayPlugin/DelayPluginV3/DelayPluginV3.h | 210 ++++++ .../DelayPlugin/DelayPluginV3/Main.cpp | 8 + .../DistortionPlugin/DistortionPlugin.h | 126 ++++ gui/config/plugins/DistortionPlugin/Main.cpp | 9 + .../plugins/DistortionPlugin/QuadClipping.txt | 18 + gui/config/plugins/EchoPlugin/EchoPlugin.h | 358 ++++++++++ gui/config/plugins/EchoPlugin/Main.cpp | 8 + .../plugins/EnvelopePlugin/EnvelopePlugin.h | 154 +++++ gui/config/plugins/EnvelopePlugin/Main.cpp | 8 + .../FlangerPlugin/FlangerV1/FlangerPlugin.h | 242 +++++++ .../FlangerPlugin/FlangerV2/FlangerPlugin.h | 321 +++++++++ .../FlangerPlugin/FlangerV3/FlangerPlugin.h | 556 ++++++++++++++++ gui/config/plugins/FlangerPlugin/Main.cpp | 8 + .../FunDistortionPlugin/FunDistortionPlugin.h | 220 +++++++ .../plugins/FunDistortionPlugin/Main.cpp | 8 + gui/config/plugins/FuzzPlugin/FuzzPlugin.h | 134 ++++ gui/config/plugins/FuzzPlugin/Main.cpp | 7 + gui/config/plugins/GUI.cpp | 622 ++++++++++++++++++ gui/config/plugins/GainPlugin/GainPlugin.h | 122 ++++ gui/config/plugins/GainPlugin/Main.cpp | 8 + gui/config/plugins/PhaserPlugin/Main.cpp | 1 + .../plugins/PhaserPlugin/PhaserPlugin.h | 148 +++++ gui/config/plugins/README.md | 1 + gui/config/plugins/ReverbPlugin/Main.cpp | 8 + .../plugins/ReverbPlugin/ReverbPlugin.h | 169 +++++ gui/config/plugins/SaturationPlugin/Main.cpp | 9 + .../SaturationPlugin/SaturationPlugin.h | 154 +++++ gui/config/plugins/Template/Main.cpp | 8 + .../plugins/Template/TemplateV1/TempPlugin.h | 135 ++++ .../plugins/Template/TemplateV2/TempPlugin.h | 161 +++++ .../TremoloPlugin/TremoloPluginV1/Main.cpp | 19 + .../TremoloPluginV1/TremoloPlugin.h | 198 ++++++ .../TremoloPluginV1/TremoloPlugin.jucer | 92 +++ .../TremoloPlugin/TremoloPluginV2/Main.cpp | 19 + .../TremoloPluginV2/TremoloPlugin.h | 234 +++++++ .../TremoloPluginV2/TremoloPlugin.jucer | 92 +++ .../TremoloPlugin/TremoloPluginV3/Main.cpp | 8 + .../TremoloPluginV3/TremoloPlugin.h | 155 +++++ .../TremoloPluginV3/TremoloPlugin.jucer | 49 ++ .../TremoloPlugin/TremoloPluginV4/Main.cpp | 12 + .../TremoloPluginV4/TremoloPlugin.jucer | 52 ++ .../TremoloPluginV4/TremoloPluginV4.h | 163 +++++ .../TremoloPlugin/TremoloPluginV5/Main.cpp | 8 + .../TremoloPluginV5/TremoloPluginV5.h | 188 ++++++ .../TremoloPluginV6/TremoloPluginV6.h | 250 +++++++ gui/config/{ => profiles}/all_plugins.json | 0 gui/config/{ => profiles}/checkoff.json | 0 gui/config/{ => profiles}/chorus.json | 0 gui/config/{ => profiles}/chorus_2.json | 0 gui/config/{ => profiles}/chorus_2_2.json | 0 gui/config/{ => profiles}/compressor.json | 0 gui/config/{ => profiles}/delay.json | 0 gui/config/{ => profiles}/distortion.json | 0 .../{ => profiles}/distortion_normal.json | 0 gui/config/{ => profiles}/flanger.json | 0 gui/config/{ => profiles}/fundistortion.json | 0 gui/config/{ => profiles}/fuzz.json | 0 gui/config/{ => profiles}/main.json | 0 gui/config/{ => profiles}/tremolo.json | 0 gui/src/plugin_manager.py | 6 +- gui/src/qwidgets/core.py | 8 +- gui/src/utils.py | 2 + 78 files changed, 8363 insertions(+), 7 deletions(-) create mode 100644 gui/config/plugins/ChorusPlugin/ChorusJUCE/ChorusPlugin.h create mode 100644 gui/config/plugins/ChorusPlugin/ChorusV1/ChorusPlugin.h create mode 100644 gui/config/plugins/ChorusPlugin/ChorusV2/ChorusPlugin.h create mode 100644 gui/config/plugins/ChorusPlugin/ChorusV3/ChorusPlugin.h create mode 100644 gui/config/plugins/ChorusPlugin/ChorusV4/ChorusPlugin.h create mode 100644 gui/config/plugins/ChorusPlugin/ChorusV5/ChorusPlugin.h create mode 100644 gui/config/plugins/ChorusPlugin/Main.cpp create mode 100644 gui/config/plugins/CompressionPlugin/CompressionPlugin.h create mode 100644 gui/config/plugins/CompressionPlugin/Main.cpp create mode 100644 gui/config/plugins/DelayPlugin/DelayPlugin.jucer create mode 100644 gui/config/plugins/DelayPlugin/DelayPluginV1/DelayPlugin.jucer create mode 100644 gui/config/plugins/DelayPlugin/DelayPluginV1/DelayPluginV1.h create mode 100644 gui/config/plugins/DelayPlugin/DelayPluginV1/Main.cpp create mode 100644 gui/config/plugins/DelayPlugin/DelayPluginV2/DelayPlugin.jucer create mode 100644 gui/config/plugins/DelayPlugin/DelayPluginV2/DelayPluginV2.h create mode 100644 gui/config/plugins/DelayPlugin/DelayPluginV2/Main.cpp create mode 100644 gui/config/plugins/DelayPlugin/DelayPluginV3/DelayPluginV3.h create mode 100644 gui/config/plugins/DelayPlugin/DelayPluginV3/Main.cpp create mode 100644 gui/config/plugins/DistortionPlugin/DistortionPlugin.h create mode 100644 gui/config/plugins/DistortionPlugin/Main.cpp create mode 100644 gui/config/plugins/DistortionPlugin/QuadClipping.txt create mode 100644 gui/config/plugins/EchoPlugin/EchoPlugin.h create mode 100644 gui/config/plugins/EchoPlugin/Main.cpp create mode 100644 gui/config/plugins/EnvelopePlugin/EnvelopePlugin.h create mode 100644 gui/config/plugins/EnvelopePlugin/Main.cpp create mode 100644 gui/config/plugins/FlangerPlugin/FlangerV1/FlangerPlugin.h create mode 100644 gui/config/plugins/FlangerPlugin/FlangerV2/FlangerPlugin.h create mode 100644 gui/config/plugins/FlangerPlugin/FlangerV3/FlangerPlugin.h create mode 100644 gui/config/plugins/FlangerPlugin/Main.cpp create mode 100644 gui/config/plugins/FunDistortionPlugin/FunDistortionPlugin.h create mode 100644 gui/config/plugins/FunDistortionPlugin/Main.cpp create mode 100644 gui/config/plugins/FuzzPlugin/FuzzPlugin.h create mode 100644 gui/config/plugins/FuzzPlugin/Main.cpp create mode 100644 gui/config/plugins/GUI.cpp create mode 100644 gui/config/plugins/GainPlugin/GainPlugin.h create mode 100644 gui/config/plugins/GainPlugin/Main.cpp create mode 100644 gui/config/plugins/PhaserPlugin/Main.cpp create mode 100644 gui/config/plugins/PhaserPlugin/PhaserPlugin.h create mode 100644 gui/config/plugins/README.md create mode 100644 gui/config/plugins/ReverbPlugin/Main.cpp create mode 100644 gui/config/plugins/ReverbPlugin/ReverbPlugin.h create mode 100644 gui/config/plugins/SaturationPlugin/Main.cpp create mode 100644 gui/config/plugins/SaturationPlugin/SaturationPlugin.h create mode 100644 gui/config/plugins/Template/Main.cpp create mode 100644 gui/config/plugins/Template/TemplateV1/TempPlugin.h create mode 100644 gui/config/plugins/Template/TemplateV2/TempPlugin.h create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV1/Main.cpp create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV1/TremoloPlugin.h create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV1/TremoloPlugin.jucer create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV2/Main.cpp create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV2/TremoloPlugin.h create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV2/TremoloPlugin.jucer create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV3/Main.cpp create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV3/TremoloPlugin.h create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV3/TremoloPlugin.jucer create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV4/Main.cpp create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV4/TremoloPlugin.jucer create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV4/TremoloPluginV4.h create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV5/Main.cpp create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV5/TremoloPluginV5.h create mode 100644 gui/config/plugins/TremoloPlugin/TremoloPluginV6/TremoloPluginV6.h rename gui/config/{ => profiles}/all_plugins.json (100%) rename gui/config/{ => profiles}/checkoff.json (100%) rename gui/config/{ => profiles}/chorus.json (100%) rename gui/config/{ => profiles}/chorus_2.json (100%) rename gui/config/{ => profiles}/chorus_2_2.json (100%) rename gui/config/{ => profiles}/compressor.json (100%) rename gui/config/{ => profiles}/delay.json (100%) rename gui/config/{ => profiles}/distortion.json (100%) rename gui/config/{ => profiles}/distortion_normal.json (100%) rename gui/config/{ => profiles}/flanger.json (100%) rename gui/config/{ => profiles}/fundistortion.json (100%) rename gui/config/{ => profiles}/fuzz.json (100%) rename gui/config/{ => profiles}/main.json (100%) rename gui/config/{ => profiles}/tremolo.json (100%) diff --git a/gui/config/plugins/ChorusPlugin/ChorusJUCE/ChorusPlugin.h b/gui/config/plugins/ChorusPlugin/ChorusJUCE/ChorusPlugin.h new file mode 100644 index 0000000..9e18b8e --- /dev/null +++ b/gui/config/plugins/ChorusPlugin/ChorusJUCE/ChorusPlugin.h @@ -0,0 +1,169 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: ChorusPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Chorus audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: ChorusProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class ChorusProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + ChorusProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gainParam = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 10.0f, 2.0f)); // rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.2f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 1.0f, 100.0f, 7.0f)); // delay is in milliseconds + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.2f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // ProcessSpec is same structure for all JUCE DSP algorithms + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + // Initializes the chorus and gain processors + chorus.prepare (spec); + gain.prepare (spec); + + // Sets values for chorus and gain parameters + gain.setGainLinear (gainParam->get()); + chorus.setRate (rate->get()); + chorus.setDepth (depth->get()); + chorus.setCentreDelay (delay->get()); + chorus.setFeedback (feedback->get()); + chorus.setMix (mix->get()); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + // Audio buffer converted to audio block for processing + juce::dsp::AudioBlock audioBlock {buffer}; + + // Processes samples in audio block + chorus.process (juce::dsp::ProcessContextReplacing (audioBlock)); + gain.process (juce::dsp::ProcessContextReplacing (audioBlock)); + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Chorus PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gainParam); + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gainParam->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::dsp::Gain gain; + juce::dsp::Chorus chorus; + + juce::AudioParameterFloat* gainParam; + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChorusProcessor) +}; \ No newline at end of file diff --git a/gui/config/plugins/ChorusPlugin/ChorusV1/ChorusPlugin.h b/gui/config/plugins/ChorusPlugin/ChorusV1/ChorusPlugin.h new file mode 100644 index 0000000..dbe35ce --- /dev/null +++ b/gui/config/plugins/ChorusPlugin/ChorusV1/ChorusPlugin.h @@ -0,0 +1,251 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: ChorusPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Chorus audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: ChorusProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class ChorusProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + ChorusProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 10.0f, 5.0f)); // rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.01f, 0.1f, 0.03f)); // delay is in seconds + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.2f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + + // Delay Lines + + // Initializes both delay lines + chnl1delay.prepare (spec); + chnl2delay.prepare (spec); + + // Since the delay parameter is limited to a maximum of 0.1s, and based on delayInSamples + // which can double the value of the delay parameter based on the LFO, the maximum possible number of samples is sampleRate in samples/s * 0.2s + chnl1delay.setMaximumDelayInSamples (sampleRate * 0.2f); + chnl2delay.setMaximumDelayInSamples (sampleRate * 0.2f); + + // Converts delay in seconds to delay in samples and updates delay of both delay lines + delayFloat = delay->get(); + chnl1delay.setDelay (delayFloat * sampleRate); + chnl2delay.setDelay (delayFloat * sampleRate); + + + // LFOs + + // Initializes both LFOs + chnl1LFO.prepare (spec); + chnl2LFO.prepare (spec); + + // Updates rate of both LFOs + rateFloat = rate->get(); + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + rateFloat = rate->get(); + depthFloat = depth->get(); + delayFloat = delay->get(); + feedbackFloat = feedback->get(); + mixFloat = mix->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1delay.setDelay (delayFloat * sampleRate); + chnl2delay.setDelay (delayFloat * sampleRate); + + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + lfoValue = chnl1LFO.processSample(0.0f); + delayInSamples = (lfoValue * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + wetSample = (wetSample * ((depthFloat/5.0f) * (0.5f * abs(lfoValue) + 0.5f) + (1.0f - (depthFloat/5.0f)))); // Amplitude Modulation + chnl1delay.pushSample(channel, channelData[sample]);//chnl1delay.pushSample(channel, drySample + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix Delay + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + lfoValue = chnl2LFO.processSample(0.0f); + delayInSamples = (lfoValue * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + wetSample = (wetSample * ((depthFloat/5.0f) * (0.5f * abs(lfoValue) + 0.5f) + (1.0f - (depthFloat/5.0f)))); + chnl2delay.pushSample(channel, channelData[sample]);//chnl2delay.pushSample(channel, drySample + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Chorus PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + + float gainFloat; + float rateFloat; + float depthFloat; + float delayFloat; + float feedbackFloat; + float mixFloat; + + float lfoValue; + float drySample; + float wetSample; + double sampleRate; + int totalNumInputChannels; + int delayInSamples; + + juce::dsp::DelayLine chnl1delay; + juce::dsp::DelayLine chnl2delay; + + // The last argument for the following lines is the number of points in the lookup table + juce::dsp::Oscillator chnl1LFO { [](float x) { return std::sin (x); }, 200 }; + juce::dsp::Oscillator chnl2LFO { [](float x) { return std::sin (x); }, 200 }; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChorusProcessor) +}; diff --git a/gui/config/plugins/ChorusPlugin/ChorusV2/ChorusPlugin.h b/gui/config/plugins/ChorusPlugin/ChorusV2/ChorusPlugin.h new file mode 100644 index 0000000..4eab88a --- /dev/null +++ b/gui/config/plugins/ChorusPlugin/ChorusV2/ChorusPlugin.h @@ -0,0 +1,284 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: ChorusPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Chorus audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: ChorusProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class ChorusProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + ChorusProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 10.0f, 5.0f)); // Rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.01f, 0.1f, 0.03f)); // Delay is in seconds + //addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.2f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + + // Delay Lines + + // Initializes both delay lines + chnl1delay.prepare (spec); + chnl2delay.prepare (spec); + + // Since the delay parameter is limited to a maximum of 0.1s, and based on delayInSamples3 + // which can double the value of the delay parameter based on the LFO, the maximum possible number of samples is sampleRate in samples/s * 0.2s + chnl1delay.setMaximumDelayInSamples (sampleRate * 0.2f); + chnl2delay.setMaximumDelayInSamples (sampleRate * 0.2f); + + // Converts delay in seconds to delay in samples and updates delay of both delay lines + delayFloat = delay->get(); + chnl1delay.setDelay (delayFloat * sampleRate); + chnl2delay.setDelay (delayFloat * sampleRate); + + + // LFOs + + // Initializes both LFOs + chnl1LFO.prepare (spec); + chnl2LFO.prepare (spec); + + // Updates rate of both LFOs + rateFloat = rate->get(); + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + rateFloat = rate->get(); + depthFloat = depth->get(); + delayFloat = delay->get(); + //feedbackFloat = feedback->get(); + mixFloat = mix->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1delay.setDelay (delayFloat * sampleRate); + chnl2delay.setDelay (delayFloat * sampleRate); + + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl1delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl1LFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/3.0f + delayFloat/3.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/1.5f + delayFloat/1.5f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat + delayFloat) * sampleRate; // Delay parameter controls the average delay of the most delayed sample + + drySample = channelData[sample]; + + wetSample1 = chnl1delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * (depthFloat * lfoValue + (1.0f - depthFloat))); // AM + + wetSample2 = chnl1delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); // AM + + wetSample3 = chnl1delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/3.0f) * lfoValue + (1.0f - (depthFloat/3.0f)))); // AM + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/2.0f) + (wetSample2 * mixFloat/3.0f) + (wetSample3 * mixFloat/6.0f); // Mix dry and wet samples + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/3.0f) + (wetSample2 * mixFloat/3.0f) + (wetSample3 * mixFloat/3.0f); + + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl2delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl2LFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/3.0f + delayFloat/3.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/1.5f + delayFloat/1.5f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + + wetSample1 = chnl2delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * (depthFloat * lfoValue + (1.0f - depthFloat))); + + wetSample2 = chnl2delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample3 = chnl2delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/3.0f) * lfoValue + (1.0f - (depthFloat/3.0f)))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/2.0f) + (wetSample2 * mixFloat/3.0f) + (wetSample3 * mixFloat/6.0f); + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/3.0f) + (wetSample2 * mixFloat/3.0f) + (wetSample3 * mixFloat/3.0f); + + channelData[sample] *= gainFloat; + } + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Chorus PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + //juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + //feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* delay; + //juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + + float gainFloat; + float rateFloat; + float depthFloat; + float delayFloat; + //float feedbackFloat; + float mixFloat; + + float lfoValue; + float drySample; + double sampleRate; + int totalNumInputChannels; + + float wetSample1; + float wetSample2; + float wetSample3; + int delayInSamples1; + int delayInSamples2; + int delayInSamples3; + + juce::dsp::DelayLine chnl1delay; + juce::dsp::DelayLine chnl2delay; + + // The last argument for the following lines is the number of points in the lookup table + juce::dsp::Oscillator chnl1LFO { [](float x) { return std::sin (x); }, 200 }; + juce::dsp::Oscillator chnl2LFO { [](float x) { return std::sin (x); }, 200 }; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChorusProcessor) +}; diff --git a/gui/config/plugins/ChorusPlugin/ChorusV3/ChorusPlugin.h b/gui/config/plugins/ChorusPlugin/ChorusV3/ChorusPlugin.h new file mode 100644 index 0000000..8ef4589 --- /dev/null +++ b/gui/config/plugins/ChorusPlugin/ChorusV3/ChorusPlugin.h @@ -0,0 +1,328 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: ChorusPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Chorus audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: ChorusProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class ChorusProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + ChorusProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 10.0f, 5.0f)); // Rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.01f, 0.1f, 0.03f)); // Delay is in seconds + //addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.2f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + + // Delay Lines + + // Initializes both delay lines + chnl1delay.prepare (spec); + chnl2delay.prepare (spec); + + // Since the delay parameter is limited to a maximum of 0.1s, and based on delayInSamples8 + // which can double the value of the delay parameter based on the LFO, the maximum possible number of samples is sampleRate in samples/s * 0.2s + chnl1delay.setMaximumDelayInSamples (sampleRate * 0.2f); + chnl2delay.setMaximumDelayInSamples (sampleRate * 0.2f); + + // Converts delay in seconds to delay in samples and updates delay of both delay lines + delayFloat = delay->get(); + chnl1delay.setDelay (delayFloat * sampleRate); + chnl2delay.setDelay (delayFloat * sampleRate); + + + // LFOs + + // Initializes both LFOs + chnl1LFO.prepare (spec); + chnl2LFO.prepare (spec); + + // Updates rate of both LFOs + rateFloat = rate->get(); + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + rateFloat = rate->get(); + depthFloat = depth->get(); + delayFloat = delay->get(); + //feedbackFloat = feedback->get(); + mixFloat = mix->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl1delay.pushSample(channel, channelData[sample]); + + lfoValue = abs(chnl1LFO.processSample(0.0f));//lfoValue = chnl1LFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/8.0f + delayFloat/8.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/4.0f + delayFloat/4.0f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(8.0f/3.0f) + delayFloat/(8.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/2.0f + delayFloat/2.0f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.6f + delayFloat/1.6f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(4.0f/3.0f) + delayFloat/(4.0f/3.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat/(8.0f/7.0f) + delayFloat/(8.0f/7.0f)) * sampleRate; + delayInSamples8 = (lfoValue * delayFloat + delayFloat) * sampleRate; // Delay parameter controls the lowest (or avg if not using abs for lfoValue) delay of the most delayed sample + + drySample = channelData[sample]; + + wetSample1 = chnl1delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); // AM + + wetSample2 = chnl1delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl1delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl1delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl1delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl1delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl1delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + wetSample8 = chnl1delay.popSample(channel, delayInSamples8, true); + wetSample8 = (wetSample8 * ((depthFloat) * lfoValue + (1.0f - (depthFloat)))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.5f) + (wetSample2 * mixFloat/(36.0f/7.0f)) + (wetSample3 * mixFloat/6.0f) + (wetSample4 * mixFloat/7.2f) + (wetSample5 * mixFloat/9.0f) + (wetSample6 * mixFloat/12.0f) + (wetSample7 * mixFloat/18.0f) + (wetSample8 * mixFloat/36.0f); // Mix dry and wet samples + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/8.0f) + (wetSample2 * mixFloat/8.0f) + (wetSample3 * mixFloat/8.0f) + (wetSample4 * mixFloat/8.0f) + (wetSample5 * mixFloat/8.0f) + (wetSample6 * mixFloat/8.0f) + (wetSample7 * mixFloat/8.0f) + (wetSample8 * mixFloat/8.0f); + + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl2delay.pushSample(channel, channelData[sample]); + + lfoValue = abs(chnl2LFO.processSample(0.0f));//lfoValue = chnl2LFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/7.0f + delayFloat/7.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/3.5f + delayFloat/3.5f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(7.0f/3.0f) + delayFloat/(7.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/1.75f + delayFloat/1.75f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.4f + delayFloat/1.4f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(7.0f/6.0f) + delayFloat/(7.0f/6.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + + wetSample1 = chnl2delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); + + wetSample2 = chnl2delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl2delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl2delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl2delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl2delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl2delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.0f) + (wetSample2 * mixFloat/(14.0f/3.0f)) + (wetSample3 * mixFloat/5.6f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/(28.0f/3.0f)) + (wetSample6 * mixFloat/14.0f) + (wetSample7 * mixFloat/28.0f); + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/7.0f) + (wetSample2 * mixFloat/7.0f) + (wetSample3 * mixFloat/7.0f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/7.0f) + (wetSample6 * mixFloat/7.0f) + (wetSample7 * mixFloat/7.0f); + + channelData[sample] *= gainFloat; + } + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Chorus PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + //juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + //feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* delay; + //juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + + float gainFloat; + float rateFloat; + float depthFloat; + float delayFloat; + //float feedbackFloat; + float mixFloat; + + float lfoValue; + float drySample; + double sampleRate; + int totalNumInputChannels; + + float wetSample1; + float wetSample2; + float wetSample3; + float wetSample4; + float wetSample5; + float wetSample6; + float wetSample7; + float wetSample8; + + int delayInSamples1; + int delayInSamples2; + int delayInSamples3; + int delayInSamples4; + int delayInSamples5; + int delayInSamples6; + int delayInSamples7; + int delayInSamples8; + + juce::dsp::DelayLine chnl1delay; + juce::dsp::DelayLine chnl2delay; + + // The last argument for the following lines is the number of points in the lookup table + juce::dsp::Oscillator chnl1LFO { [](float x) { return std::sin (x); }, 200 }; + juce::dsp::Oscillator chnl2LFO { [](float x) { return std::sin (x); }, 200 }; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChorusProcessor) +}; diff --git a/gui/config/plugins/ChorusPlugin/ChorusV4/ChorusPlugin.h b/gui/config/plugins/ChorusPlugin/ChorusV4/ChorusPlugin.h new file mode 100644 index 0000000..db9c2c7 --- /dev/null +++ b/gui/config/plugins/ChorusPlugin/ChorusV4/ChorusPlugin.h @@ -0,0 +1,552 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: ChorusPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Chorus audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: ChorusProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class ChorusProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + ChorusProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 10.0f, 5.0f)); // Rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.01f, 0.1f, 0.03f)); // Delay is in seconds + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + + // Waveform 0: Pass-Through, Waveform 1: Sinusoidal LFO, Waveform 2: Saw Wave LFO, Waveform 3: Square Wave LFO + addParameter (waveform = new juce::AudioParameterInt ({ "waveform", 1 }, "Waveform", 0, 3, 1)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + + // Delay Lines + + // Initializes both delay lines + chnl1delay.prepare (spec); + chnl2delay.prepare (spec); + + // Since the delay parameter is limited to a maximum of 0.1s, and based on delayInSamples8 + // which can double the value of the delay parameter based on the LFO, the maximum possible number of samples is sampleRate in samples/s * 0.2s + chnl1delay.setMaximumDelayInSamples (sampleRate * 0.2f); + chnl2delay.setMaximumDelayInSamples (sampleRate * 0.2f); + + // Converts delay in seconds to delay in samples and updates delay of both delay lines + delayFloat = delay->get(); + chnl1delay.setDelay (delayFloat * sampleRate); + chnl2delay.setDelay (delayFloat * sampleRate); + + + // LFOs + + // Initializes all LFOs + chnl1sineLFO.prepare (spec); + chnl2sineLFO.prepare (spec); + chnl1sawLFO.prepare (spec); + chnl2sawLFO.prepare (spec); + chnl1squareLFO.prepare (spec); + chnl2squareLFO.prepare (spec); + + // Updates rate of all LFOs + rateFloat = rate->get(); + chnl1sineLFO.setFrequency (rateFloat); + chnl2sineLFO.setFrequency (rateFloat); + chnl1sawLFO.setFrequency (rateFloat); + chnl2sawLFO.setFrequency (rateFloat); + chnl1squareLFO.setFrequency (rateFloat); + chnl2squareLFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + rateFloat = rate->get(); + depthFloat = depth->get(); + delayFloat = delay->get(); + mixFloat = mix->get(); + waveformInt = waveform->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1sineLFO.setFrequency (rateFloat); + chnl2sineLFO.setFrequency (rateFloat); + chnl1sawLFO.setFrequency (rateFloat); + chnl2sawLFO.setFrequency (rateFloat); + chnl1squareLFO.setFrequency (rateFloat); + chnl2squareLFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + switch(waveformInt) + { + case 1: // Sinusoidal LFO + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl1delay.pushSample(channel, channelData[sample]); + + lfoValue = abs(chnl1sineLFO.processSample(0.0f));//lfoValue = chnl1sineLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/8.0f + delayFloat/8.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/4.0f + delayFloat/4.0f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(8.0f/3.0f) + delayFloat/(8.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/2.0f + delayFloat/2.0f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.6f + delayFloat/1.6f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(4.0f/3.0f) + delayFloat/(4.0f/3.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat/(8.0f/7.0f) + delayFloat/(8.0f/7.0f)) * sampleRate; + delayInSamples8 = (lfoValue * delayFloat + delayFloat) * sampleRate; // Delay parameter controls the lowest (or avg if not using abs for lfoValue) delay of the most delayed sample + + drySample = channelData[sample]; + + wetSample1 = chnl1delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); // AM + + wetSample2 = chnl1delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl1delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl1delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl1delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl1delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl1delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + wetSample8 = chnl1delay.popSample(channel, delayInSamples8, true); + wetSample8 = (wetSample8 * ((depthFloat) * lfoValue + (1.0f - (depthFloat)))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.5f) + (wetSample2 * mixFloat/(36.0f/7.0f)) + (wetSample3 * mixFloat/6.0f) + (wetSample4 * mixFloat/7.2f) + (wetSample5 * mixFloat/9.0f) + (wetSample6 * mixFloat/12.0f) + (wetSample7 * mixFloat/18.0f) + (wetSample8 * mixFloat/36.0f); // Mix dry and wet samples + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/8.0f) + (wetSample2 * mixFloat/8.0f) + (wetSample3 * mixFloat/8.0f) + (wetSample4 * mixFloat/8.0f) + (wetSample5 * mixFloat/8.0f) + (wetSample6 * mixFloat/8.0f) + (wetSample7 * mixFloat/8.0f) + (wetSample8 * mixFloat/8.0f); + + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl2delay.pushSample(channel, channelData[sample]); + + lfoValue = abs(chnl2sineLFO.processSample(0.0f));//lfoValue = chnl2sineLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/7.0f + delayFloat/7.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/3.5f + delayFloat/3.5f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(7.0f/3.0f) + delayFloat/(7.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/1.75f + delayFloat/1.75f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.4f + delayFloat/1.4f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(7.0f/6.0f) + delayFloat/(7.0f/6.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat + delayFloat * sampleRate; + + drySample = channelData[sample]; + + wetSample1 = chnl2delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); + + wetSample2 = chnl2delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl2delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl2delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl2delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl2delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl2delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.0f) + (wetSample2 * mixFloat/(14.0f/3.0f)) + (wetSample3 * mixFloat/5.6f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/(28.0f/3.0f)) + (wetSample6 * mixFloat/14.0f) + (wetSample7 * mixFloat/28.0f); + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/7.0f) + (wetSample2 * mixFloat/7.0f) + (wetSample3 * mixFloat/7.0f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/7.0f) + (wetSample6 * mixFloat/7.0f) + (wetSample7 * mixFloat/7.0f); + + channelData[sample] *= gainFloat; + } + } + break; + + case 2: // Saw Wave LFO + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl1delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl1sawLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/8.0f + delayFloat/8.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/4.0f + delayFloat/4.0f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(8.0f/3.0f) + delayFloat/(8.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/2.0f + delayFloat/2.0f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.6f + delayFloat/1.6f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(4.0f/3.0f) + delayFloat/(4.0f/3.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat/(8.0f/7.0f) + delayFloat/(8.0f/7.0f)) * sampleRate; + delayInSamples8 = (lfoValue * delayFloat + delayFloat) * sampleRate; // Delay parameter controls the average delay of the most delayed sample + + drySample = channelData[sample]; + + wetSample1 = chnl1delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); // AM + + wetSample2 = chnl1delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl1delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl1delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl1delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl1delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl1delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + wetSample8 = chnl1delay.popSample(channel, delayInSamples8, true); + wetSample8 = (wetSample8 * ((depthFloat) * lfoValue + (1.0f - (depthFloat)))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.5f) + (wetSample2 * mixFloat/(36.0f/7.0f)) + (wetSample3 * mixFloat/6.0f) + (wetSample4 * mixFloat/7.2f) + (wetSample5 * mixFloat/9.0f) + (wetSample6 * mixFloat/12.0f) + (wetSample7 * mixFloat/18.0f) + (wetSample8 * mixFloat/36.0f); // Mix dry and wet samples + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/8.0f) + (wetSample2 * mixFloat/8.0f) + (wetSample3 * mixFloat/8.0f) + (wetSample4 * mixFloat/8.0f) + (wetSample5 * mixFloat/8.0f) + (wetSample6 * mixFloat/8.0f) + (wetSample7 * mixFloat/8.0f) + (wetSample8 * mixFloat/8.0f); + + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl2delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl2sawLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/7.0f + delayFloat/7.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/3.5f + delayFloat/3.5f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(7.0f/3.0f) + delayFloat/(7.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/1.75f + delayFloat/1.75f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.4f + delayFloat/1.4f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(7.0f/6.0f) + delayFloat/(7.0f/6.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat + delayFloat * sampleRate; + + drySample = channelData[sample]; + + wetSample1 = chnl2delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); + + wetSample2 = chnl2delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl2delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl2delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl2delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl2delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl2delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.0f) + (wetSample2 * mixFloat/(14.0f/3.0f)) + (wetSample3 * mixFloat/5.6f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/(28.0f/3.0f)) + (wetSample6 * mixFloat/14.0f) + (wetSample7 * mixFloat/28.0f); + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/7.0f) + (wetSample2 * mixFloat/7.0f) + (wetSample3 * mixFloat/7.0f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/7.0f) + (wetSample6 * mixFloat/7.0f) + (wetSample7 * mixFloat/7.0f); + + channelData[sample] *= gainFloat; + } + } + break; + + case 3: // Square Wave LFO + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl1delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl1squareLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/8.0f + delayFloat/8.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/4.0f + delayFloat/4.0f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(8.0f/3.0f) + delayFloat/(8.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/2.0f + delayFloat/2.0f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.6f + delayFloat/1.6f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(4.0f/3.0f) + delayFloat/(4.0f/3.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat/(8.0f/7.0f) + delayFloat/(8.0f/7.0f)) * sampleRate; + delayInSamples8 = (lfoValue * delayFloat + delayFloat) * sampleRate; // Delay parameter controls the average delay of the most delayed sample + + drySample = channelData[sample]; + + wetSample1 = chnl1delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); // AM + + wetSample2 = chnl1delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl1delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl1delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl1delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl1delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl1delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + wetSample8 = chnl1delay.popSample(channel, delayInSamples8, true); + wetSample8 = (wetSample8 * ((depthFloat) * lfoValue + (1.0f - (depthFloat)))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.5f) + (wetSample2 * mixFloat/(36.0f/7.0f)) + (wetSample3 * mixFloat/6.0f) + (wetSample4 * mixFloat/7.2f) + (wetSample5 * mixFloat/9.0f) + (wetSample6 * mixFloat/12.0f) + (wetSample7 * mixFloat/18.0f) + (wetSample8 * mixFloat/36.0f); // Mix dry and wet samples + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/8.0f) + (wetSample2 * mixFloat/8.0f) + (wetSample3 * mixFloat/8.0f) + (wetSample4 * mixFloat/8.0f) + (wetSample5 * mixFloat/8.0f) + (wetSample6 * mixFloat/8.0f) + (wetSample7 * mixFloat/8.0f) + (wetSample8 * mixFloat/8.0f); + + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl2delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl2squareLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/7.0f + delayFloat/7.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/3.5f + delayFloat/3.5f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(7.0f/3.0f) + delayFloat/(7.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/1.75f + delayFloat/1.75f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.4f + delayFloat/1.4f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(7.0f/6.0f) + delayFloat/(7.0f/6.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat + delayFloat * sampleRate; + + drySample = channelData[sample]; + + wetSample1 = chnl2delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); + + wetSample2 = chnl2delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl2delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl2delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl2delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl2delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl2delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.0f) + (wetSample2 * mixFloat/(14.0f/3.0f)) + (wetSample3 * mixFloat/5.6f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/(28.0f/3.0f)) + (wetSample6 * mixFloat/14.0f) + (wetSample7 * mixFloat/28.0f); + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/7.0f) + (wetSample2 * mixFloat/7.0f) + (wetSample3 * mixFloat/7.0f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/7.0f) + (wetSample6 * mixFloat/7.0f) + (wetSample7 * mixFloat/7.0f); + + channelData[sample] *= gainFloat; + } + } + break; + + default: // Pass-Through + break; + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Chorus PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + juce::MemoryOutputStream (destData, true).writeInt (*waveform); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + waveform->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* mix; + juce::AudioParameterInt* waveform; + + float gainFloat; + float rateFloat; + float depthFloat; + float delayFloat; + float mixFloat; + int waveformInt; + + float lfoValue; + float drySample; + double sampleRate; + int totalNumInputChannels; + + float wetSample1; + float wetSample2; + float wetSample3; + float wetSample4; + float wetSample5; + float wetSample6; + float wetSample7; + float wetSample8; + + int delayInSamples1; + int delayInSamples2; + int delayInSamples3; + int delayInSamples4; + int delayInSamples5; + int delayInSamples6; + int delayInSamples7; + int delayInSamples8; + + juce::dsp::DelayLine chnl1delay; + juce::dsp::DelayLine chnl2delay; + + // The last argument for the following lines is the number of points in the lookup table + juce::dsp::Oscillator chnl1sineLFO { [](float x) { return std::sin (x); }, 500 }; // Sine Wave + juce::dsp::Oscillator chnl1sawLFO { [](float x) { return x / juce::MathConstants::pi; }, 10000 }; // Saw Wave + juce::dsp::Oscillator chnl1squareLFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 10000 }; // Square Wave + juce::dsp::Oscillator chnl2sineLFO { [](float x) { return std::sin (x); }, 500 }; // Sine Wave + juce::dsp::Oscillator chnl2sawLFO { [](float x) { return x / juce::MathConstants::pi; }, 10000 }; // Saw Wave + juce::dsp::Oscillator chnl2squareLFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 10000 }; // Square Wave + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChorusProcessor) +}; diff --git a/gui/config/plugins/ChorusPlugin/ChorusV5/ChorusPlugin.h b/gui/config/plugins/ChorusPlugin/ChorusV5/ChorusPlugin.h new file mode 100644 index 0000000..4beea6d --- /dev/null +++ b/gui/config/plugins/ChorusPlugin/ChorusV5/ChorusPlugin.h @@ -0,0 +1,556 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: ChorusPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Chorus audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: ChorusProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class ChorusProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + ChorusProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 10.0f, 5.0f)); // Rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.005f, 0.05f, 0.025f)); // Delay is in seconds + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + + // Waveform 0: Pass-Through, Waveform 1: Sinusoidal LFO, Waveform 2: Saw Wave LFO, Waveform 3: Square Wave LFO + addParameter (waveform = new juce::AudioParameterInt ({ "waveform", 1 }, "Waveform", 0, 3, 1)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + + // Delay Lines + + // Initializes both delay lines + chnl1delay.prepare (spec); + chnl2delay.prepare (spec); + + // Since the delay parameter is limited to a maximum of 0.1s, and based on delayInSamples8 + // which can double the value of the delay parameter based on the LFO, the maximum possible number of samples is sampleRate in samples/s * 0.2s + chnl1delay.setMaximumDelayInSamples (sampleRate * 0.2f); + chnl2delay.setMaximumDelayInSamples (sampleRate * 0.2f); + + // Converts delay in seconds to delay in samples and updates delay of both delay lines + delayFloat = delay->get(); + chnl1delay.setDelay (delayFloat * sampleRate); + chnl2delay.setDelay (delayFloat * sampleRate); + + + // LFOs + + // Initializes all LFOs + chnl1sineLFO.prepare (spec); + chnl2sineLFO.prepare (spec); + chnl1sawLFO.prepare (spec); + chnl2sawLFO.prepare (spec); + chnl1squareLFO.prepare (spec); + chnl2squareLFO.prepare (spec); + + // Updates rate of all LFOs + rateFloat = rate->get(); + chnl1sineLFO.setFrequency (rateFloat); + chnl2sineLFO.setFrequency (rateFloat); + chnl1sawLFO.setFrequency (rateFloat); + chnl2sawLFO.setFrequency (rateFloat); + chnl1squareLFO.setFrequency (rateFloat); + chnl2squareLFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + rateFloat = rate->get(); + depthFloat = depth->get(); + delayFloat = delay->get(); + mixFloat = mix->get(); + waveformInt = waveform->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1sineLFO.setFrequency (rateFloat); + chnl2sineLFO.setFrequency (rateFloat); + chnl1sawLFO.setFrequency (rateFloat); + chnl2sawLFO.setFrequency (rateFloat); + chnl1squareLFO.setFrequency (rateFloat); + chnl2squareLFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + switch(waveformInt) + { + case 1: // Sinusoidal LFO + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl1delay.pushSample(channel, channelData[sample]); + + drySample = channelData[sample]; + + lfoValue = chnl1sineLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/8.0f + delayFloat/8.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/4.0f + delayFloat/4.0f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(8.0f/3.0f) + delayFloat/(8.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/2.0f + delayFloat/2.0f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.6f + delayFloat/1.6f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(4.0f/3.0f) + delayFloat/(4.0f/3.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat/(8.0f/7.0f) + delayFloat/(8.0f/7.0f)) * sampleRate; + delayInSamples8 = (lfoValue * delayFloat + delayFloat) * sampleRate; // Delay parameter controls the average delay of the most delayed sample + + lfoValue = 0.5f * abs(lfoValue) + 0.5f; + + wetSample1 = chnl1delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); // AM + + wetSample2 = chnl1delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl1delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl1delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl1delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl1delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl1delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + wetSample8 = chnl1delay.popSample(channel, delayInSamples8, true); + wetSample8 = (wetSample8 * ((depthFloat) * lfoValue + (1.0f - (depthFloat)))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.5f) + (wetSample2 * mixFloat/(36.0f/7.0f)) + (wetSample3 * mixFloat/6.0f) + (wetSample4 * mixFloat/7.2f) + (wetSample5 * mixFloat/9.0f) + (wetSample6 * mixFloat/12.0f) + (wetSample7 * mixFloat/18.0f) + (wetSample8 * mixFloat/36.0f); // Mix dry and wet samples + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/8.0f) + (wetSample2 * mixFloat/8.0f) + (wetSample3 * mixFloat/8.0f) + (wetSample4 * mixFloat/8.0f) + (wetSample5 * mixFloat/8.0f) + (wetSample6 * mixFloat/8.0f) + (wetSample7 * mixFloat/8.0f) + (wetSample8 * mixFloat/8.0f); + + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl2delay.pushSample(channel, channelData[sample]); + + drySample = channelData[sample]; + + lfoValue = chnl2sineLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/7.0f + delayFloat/7.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/3.5f + delayFloat/3.5f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(7.0f/3.0f) + delayFloat/(7.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/1.75f + delayFloat/1.75f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.4f + delayFloat/1.4f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(7.0f/6.0f) + delayFloat/(7.0f/6.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat + delayFloat) * sampleRate; + + lfoValue = 0.5f * abs(lfoValue) + 0.5f; + + wetSample1 = chnl2delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); + + wetSample2 = chnl2delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl2delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl2delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl2delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl2delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl2delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.0f) + (wetSample2 * mixFloat/(14.0f/3.0f)) + (wetSample3 * mixFloat/5.6f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/(28.0f/3.0f)) + (wetSample6 * mixFloat/14.0f) + (wetSample7 * mixFloat/28.0f); + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/7.0f) + (wetSample2 * mixFloat/7.0f) + (wetSample3 * mixFloat/7.0f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/7.0f) + (wetSample6 * mixFloat/7.0f) + (wetSample7 * mixFloat/7.0f); + + channelData[sample] *= gainFloat; + } + } + break; + + case 2: // Saw Wave LFO + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl1delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl1sawLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/8.0f + delayFloat/8.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/4.0f + delayFloat/4.0f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(8.0f/3.0f) + delayFloat/(8.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/2.0f + delayFloat/2.0f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.6f + delayFloat/1.6f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(4.0f/3.0f) + delayFloat/(4.0f/3.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat/(8.0f/7.0f) + delayFloat/(8.0f/7.0f)) * sampleRate; + delayInSamples8 = (lfoValue * delayFloat + delayFloat) * sampleRate; // Delay parameter controls the average delay of the most delayed sample + + drySample = channelData[sample]; + + wetSample1 = chnl1delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); // AM + + wetSample2 = chnl1delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl1delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl1delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl1delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl1delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl1delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + wetSample8 = chnl1delay.popSample(channel, delayInSamples8, true); + wetSample8 = (wetSample8 * ((depthFloat) * lfoValue + (1.0f - (depthFloat)))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.5f) + (wetSample2 * mixFloat/(36.0f/7.0f)) + (wetSample3 * mixFloat/6.0f) + (wetSample4 * mixFloat/7.2f) + (wetSample5 * mixFloat/9.0f) + (wetSample6 * mixFloat/12.0f) + (wetSample7 * mixFloat/18.0f) + (wetSample8 * mixFloat/36.0f); // Mix dry and wet samples + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/8.0f) + (wetSample2 * mixFloat/8.0f) + (wetSample3 * mixFloat/8.0f) + (wetSample4 * mixFloat/8.0f) + (wetSample5 * mixFloat/8.0f) + (wetSample6 * mixFloat/8.0f) + (wetSample7 * mixFloat/8.0f) + (wetSample8 * mixFloat/8.0f); + + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl2delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl2sawLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/7.0f + delayFloat/7.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/3.5f + delayFloat/3.5f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(7.0f/3.0f) + delayFloat/(7.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/1.75f + delayFloat/1.75f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.4f + delayFloat/1.4f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(7.0f/6.0f) + delayFloat/(7.0f/6.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + + wetSample1 = chnl2delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); + + wetSample2 = chnl2delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl2delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl2delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl2delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl2delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl2delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.0f) + (wetSample2 * mixFloat/(14.0f/3.0f)) + (wetSample3 * mixFloat/5.6f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/(28.0f/3.0f)) + (wetSample6 * mixFloat/14.0f) + (wetSample7 * mixFloat/28.0f); + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/7.0f) + (wetSample2 * mixFloat/7.0f) + (wetSample3 * mixFloat/7.0f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/7.0f) + (wetSample6 * mixFloat/7.0f) + (wetSample7 * mixFloat/7.0f); + + channelData[sample] *= gainFloat; + } + } + break; + + case 3: // Square Wave LFO + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl1delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl1squareLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/8.0f + delayFloat/8.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/4.0f + delayFloat/4.0f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(8.0f/3.0f) + delayFloat/(8.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/2.0f + delayFloat/2.0f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.6f + delayFloat/1.6f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(4.0f/3.0f) + delayFloat/(4.0f/3.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat/(8.0f/7.0f) + delayFloat/(8.0f/7.0f)) * sampleRate; + delayInSamples8 = (lfoValue * delayFloat + delayFloat) * sampleRate; // Delay parameter controls the average delay of the most delayed sample + + drySample = channelData[sample]; + + wetSample1 = chnl1delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); // AM + + wetSample2 = chnl1delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl1delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl1delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl1delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl1delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl1delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + wetSample8 = chnl1delay.popSample(channel, delayInSamples8, true); + wetSample8 = (wetSample8 * ((depthFloat) * lfoValue + (1.0f - (depthFloat)))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.5f) + (wetSample2 * mixFloat/(36.0f/7.0f)) + (wetSample3 * mixFloat/6.0f) + (wetSample4 * mixFloat/7.2f) + (wetSample5 * mixFloat/9.0f) + (wetSample6 * mixFloat/12.0f) + (wetSample7 * mixFloat/18.0f) + (wetSample8 * mixFloat/36.0f); // Mix dry and wet samples + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/8.0f) + (wetSample2 * mixFloat/8.0f) + (wetSample3 * mixFloat/8.0f) + (wetSample4 * mixFloat/8.0f) + (wetSample5 * mixFloat/8.0f) + (wetSample6 * mixFloat/8.0f) + (wetSample7 * mixFloat/8.0f) + (wetSample8 * mixFloat/8.0f); + + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + chnl2delay.pushSample(channel, channelData[sample]); + + lfoValue = chnl2squareLFO.processSample(0.0f); + + delayInSamples1 = (lfoValue * delayFloat/7.0f + delayFloat/7.0f) * sampleRate; + delayInSamples2 = (lfoValue * delayFloat/3.5f + delayFloat/3.5f) * sampleRate; + delayInSamples3 = (lfoValue * delayFloat/(7.0f/3.0f) + delayFloat/(7.0f/3.0f)) * sampleRate; + delayInSamples4 = (lfoValue * delayFloat/1.75f + delayFloat/1.75f) * sampleRate; + delayInSamples5 = (lfoValue * delayFloat/1.4f + delayFloat/1.4f) * sampleRate; + delayInSamples6 = (lfoValue * delayFloat/(7.0f/6.0f) + delayFloat/(7.0f/6.0f)) * sampleRate; + delayInSamples7 = (lfoValue * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + + wetSample1 = chnl2delay.popSample(channel, delayInSamples1, true); + wetSample1 = (wetSample1 * ((depthFloat/8.0f) * lfoValue + (1.0f - (depthFloat/8.0f)))); + + wetSample2 = chnl2delay.popSample(channel, delayInSamples2, true); + wetSample2 = (wetSample2 * ((depthFloat/4.0f) * lfoValue + (1.0f - (depthFloat/4.0f)))); + + wetSample3 = chnl2delay.popSample(channel, delayInSamples3, true); + wetSample3 = (wetSample3 * ((depthFloat/(8.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/3.0f))))); + + wetSample4 = chnl2delay.popSample(channel, delayInSamples4, true); + wetSample4 = (wetSample4 * ((depthFloat/2.0f) * lfoValue + (1.0f - (depthFloat/2.0f)))); + + wetSample5 = chnl2delay.popSample(channel, delayInSamples5, true); + wetSample5 = (wetSample5 * ((depthFloat/1.6f) * lfoValue + (1.0f - (depthFloat/1.6f)))); + + wetSample6 = chnl2delay.popSample(channel, delayInSamples6, true); + wetSample6 = (wetSample6 * ((depthFloat/(4.0f/3.0f)) * lfoValue + (1.0f - (depthFloat/(4.0f/3.0f))))); + + wetSample7 = chnl2delay.popSample(channel, delayInSamples7, true); + wetSample7 = (wetSample7 * ((depthFloat/(8.0f/7.0f)) * lfoValue + (1.0f - (depthFloat/(8.0f/7.0f))))); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/4.0f) + (wetSample2 * mixFloat/(14.0f/3.0f)) + (wetSample3 * mixFloat/5.6f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/(28.0f/3.0f)) + (wetSample6 * mixFloat/14.0f) + (wetSample7 * mixFloat/28.0f); + // Uncomment the line below and comment out the line above for a different mixing ratio + //channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat/7.0f) + (wetSample2 * mixFloat/7.0f) + (wetSample3 * mixFloat/7.0f) + (wetSample4 * mixFloat/7.0f) + (wetSample5 * mixFloat/7.0f) + (wetSample6 * mixFloat/7.0f) + (wetSample7 * mixFloat/7.0f); + + channelData[sample] *= gainFloat; + } + } + break; + + default: // Pass-Through + break; + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Chorus PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + juce::MemoryOutputStream (destData, true).writeInt (*waveform); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + waveform->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* mix; + juce::AudioParameterInt* waveform; + + float gainFloat; + float rateFloat; + float depthFloat; + float delayFloat; + float mixFloat; + int waveformInt; + + float lfoValue; + float drySample; + double sampleRate; + int totalNumInputChannels; + + float wetSample1; + float wetSample2; + float wetSample3; + float wetSample4; + float wetSample5; + float wetSample6; + float wetSample7; + float wetSample8; + + int delayInSamples1; + int delayInSamples2; + int delayInSamples3; + int delayInSamples4; + int delayInSamples5; + int delayInSamples6; + int delayInSamples7; + int delayInSamples8; + + juce::dsp::DelayLine chnl1delay; + juce::dsp::DelayLine chnl2delay; + + // The last argument for the following lines is the number of points in the lookup table + juce::dsp::Oscillator chnl1sineLFO { [](float x) { return std::sin (x); }, 500 }; // Sine Wave + juce::dsp::Oscillator chnl1sawLFO { [](float x) { return x / juce::MathConstants::pi; }, 10000 }; // Saw Wave + juce::dsp::Oscillator chnl1squareLFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 10000 }; // Square Wave + juce::dsp::Oscillator chnl2sineLFO { [](float x) { return std::sin (x); }, 500 }; // Sine Wave + juce::dsp::Oscillator chnl2sawLFO { [](float x) { return x / juce::MathConstants::pi; }, 10000 }; // Saw Wave + juce::dsp::Oscillator chnl2squareLFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 10000 }; // Square Wave + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChorusProcessor) +}; diff --git a/gui/config/plugins/ChorusPlugin/Main.cpp b/gui/config/plugins/ChorusPlugin/Main.cpp new file mode 100644 index 0000000..ae966ac --- /dev/null +++ b/gui/config/plugins/ChorusPlugin/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "ChorusPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new ChorusProcessor(); +} diff --git a/gui/config/plugins/CompressionPlugin/CompressionPlugin.h b/gui/config/plugins/CompressionPlugin/CompressionPlugin.h new file mode 100644 index 0000000..be901ff --- /dev/null +++ b/gui/config/plugins/CompressionPlugin/CompressionPlugin.h @@ -0,0 +1,171 @@ +/******************************************************************************* + + name: CompressorPlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: compressor audio plugin. + lastUpdated: March 17 2025 by Anna Andres + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, juce_dsp, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: CompressorProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class CompressorProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + CompressorProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + // adding parameters as well as their bounds + addParameter (attack = new juce::AudioParameterFloat ({ "attack", 1 }, "Attack", 0.0f, 200.0f, 10.0f)); + addParameter (release = new juce::AudioParameterFloat ({ "release", 1 }, "Release", 0.0f, 400.0f, 100.0f)); + addParameter (threshold = new juce::AudioParameterFloat ({ "threshold", 1 }, "Threshold", -50.0f, 10.0f, -20.0f)); + addParameter (ratio = new juce::AudioParameterFloat ({ "ratio", 1 }, "Ratio", 1.0f, 20.0f, 3.0f)); + addParameter (thresMod = new juce::AudioParameterInt ({ "thresMod", 1 }, "Threshold Modulation Boolean", 0, 1, 0)); + addParameter (thresModFreq = new juce::AudioParameterFloat ({ "thresModFreq", 1 }, "Threshold Modulation Freq", 1.0f, 20.0f, 2.0f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double samplerate, int samplesPerBlock) override + { + // initialize the processor and set initial parameter values + juce::dsp::ProcessSpec spec { samplerate, static_cast(samplesPerBlock), static_cast(getTotalNumOutputChannels()) }; + compressor.prepare(spec); + compressor.setAttack(10.0f); + compressor.setRelease(100.0f); + compressor.setThreshold(-20.0f); + compressor.setRatio(3.0f); + + lfo.initialise([](float x) { return std::sin(x); }, 256 ); + rate = thresModFreq->get(); + lfo.setFrequency(rate); + } + + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One block of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + juce::dsp::AudioBlock block (buffer); + juce::dsp::ProcessContextReplacing context (block); + + // read the values of the parameters in from the GUI + auto attackValue = attack->get(); + auto releaseValue = release->get(); + auto thresholdValue = threshold->get(); + auto ratioValue = ratio->get(); + auto thresModBool = thresMod->get(); + auto freq = thresModFreq->get(); + + lfo.setFrequency(freq); + float lfoDepth = 2.0f; + float lfoVal = (lfo.processSample(0.0f)+1.0f)*lfoDepth; + float modThres = juce::jmap(lfoVal, -1.0f, 1.0f, -50.0f, 5.0f)*lfoDepth; + + // update parameters and apply them to the audio block with process() + compressor.setAttack(attackValue); + compressor.setRelease(releaseValue); + if(thresModBool) { + compressor.setThreshold(modThres); + } + else { + compressor.setThreshold(thresholdValue); + } + compressor.setRatio(ratioValue); + compressor.process(context); + } + + //============================================================================== + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Compressor PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*attack); + juce::MemoryOutputStream (destData, true).writeFloat (*release); + juce::MemoryOutputStream (destData, true).writeFloat (*threshold); + juce::MemoryOutputStream (destData, true).writeFloat (*ratio); + juce::MemoryOutputStream (destData, true).writeInt (*thresMod); + juce::MemoryOutputStream (destData, true).writeFloat (*thresModFreq); + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + void setStateInformation (const void* data, int sizeInBytes) override + { + attack->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + release->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + threshold->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + ratio->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + thresMod->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + thresModFreq->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::dsp::Compressor compressor; + juce::dsp::Oscillator lfo; + + juce::AudioParameterFloat* attack; //the attack time in milliseconds of the compressor + juce::AudioParameterFloat* release; //the release time in milliseconds of the compressor + juce::AudioParameterFloat* threshold; //the threshold in dB of the compressor + juce::AudioParameterFloat* ratio; //the ratio of the compressor (must be higher or equal to 1) + juce::AudioParameterInt* thresMod; + juce::AudioParameterFloat* thresModFreq; + + juce::AudioParameterFloat* lfoRate; + float rate; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CompressorProcessor) +}; diff --git a/gui/config/plugins/CompressionPlugin/Main.cpp b/gui/config/plugins/CompressionPlugin/Main.cpp new file mode 100644 index 0000000..13e8697 --- /dev/null +++ b/gui/config/plugins/CompressionPlugin/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "CompressionPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new CompressionProcessor(); +} diff --git a/gui/config/plugins/DelayPlugin/DelayPlugin.jucer b/gui/config/plugins/DelayPlugin/DelayPlugin.jucer new file mode 100644 index 0000000..02cc2da --- /dev/null +++ b/gui/config/plugins/DelayPlugin/DelayPlugin.jucer @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/config/plugins/DelayPlugin/DelayPluginV1/DelayPlugin.jucer b/gui/config/plugins/DelayPlugin/DelayPluginV1/DelayPlugin.jucer new file mode 100644 index 0000000..7a9a41c --- /dev/null +++ b/gui/config/plugins/DelayPlugin/DelayPluginV1/DelayPlugin.jucer @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/config/plugins/DelayPlugin/DelayPluginV1/DelayPluginV1.h b/gui/config/plugins/DelayPlugin/DelayPluginV1/DelayPluginV1.h new file mode 100644 index 0000000..f10f5c0 --- /dev/null +++ b/gui/config/plugins/DelayPlugin/DelayPluginV1/DelayPluginV1.h @@ -0,0 +1,184 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: DelayPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Delay audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2022 + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: DelayProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class DelayProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + DelayProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.0f, 1.0f, 0.2f)); // delay is in seconds + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.2f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int) override + { + delayFloat = delay->get(); + auto delaySamples = (int) std::round (sampleRate * delayFloat); + delayBuffer.setSize (2, delaySamples); + delayBuffer.clear(); + delayBufferPos = 0; + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + feedbackFloat = feedback->get(); + mixFloat = mix->get(); + + totalNumInputChannels = getTotalNumInputChannels(); + delayBufferSize = delayBuffer.getNumSamples(); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + int delayPosition = delayBufferPos; + + for (int i = 0; i < buffer.getNumSamples(); ++i) + { + float drySample = channelData[i]; + float wetSample = delayBuffer.getSample (channel, delayPosition) * feedbackFloat; + delayBuffer.setSample (channel, delayPosition, drySample + wetSample); + delayPosition++; + + if (delayPosition == delayBufferSize) + { + delayPosition = 0; + } + + channelData[i] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Delay Wet/Dry Mix + channelData[i] *= gainFloat; // Gain + } + } + delayBufferPos += buffer.getNumSamples(); + + if (delayBufferPos >= delayBufferSize) + { + delayBufferPos -= delayBufferSize; + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Delay PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + + float gainFloat; + float delayFloat; + float feedbackFloat; + float mixFloat; + + int totalNumInputChannels; + int delayBufferSize; + int delayBufferPos; + juce::AudioBuffer delayBuffer; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayProcessor) +}; \ No newline at end of file diff --git a/gui/config/plugins/DelayPlugin/DelayPluginV1/Main.cpp b/gui/config/plugins/DelayPlugin/DelayPluginV1/Main.cpp new file mode 100644 index 0000000..e9556e4 --- /dev/null +++ b/gui/config/plugins/DelayPlugin/DelayPluginV1/Main.cpp @@ -0,0 +1,11 @@ +#include +//#include +#include "DelayPluginV1.h" //make sure to update this for each new version! + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new DelayProcessor(); +} + +//note: juce_dsp module added \ No newline at end of file diff --git a/gui/config/plugins/DelayPlugin/DelayPluginV2/DelayPlugin.jucer b/gui/config/plugins/DelayPlugin/DelayPluginV2/DelayPlugin.jucer new file mode 100644 index 0000000..b2716d8 --- /dev/null +++ b/gui/config/plugins/DelayPlugin/DelayPluginV2/DelayPlugin.jucer @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/config/plugins/DelayPlugin/DelayPluginV2/DelayPluginV2.h b/gui/config/plugins/DelayPlugin/DelayPluginV2/DelayPluginV2.h new file mode 100644 index 0000000..d0a03c9 --- /dev/null +++ b/gui/config/plugins/DelayPlugin/DelayPluginV2/DelayPluginV2.h @@ -0,0 +1,191 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: DelayPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Delay audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2022 + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: DelayProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class DelayProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + DelayProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.001f, 1.0f, 0.2f)); // Delay is in seconds + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.2f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + // Initializes delay processor + delayLine.prepare (spec); + + // Since the delay parameter is limited to a maximum of 1s, the maximum possible number of samples is sampleRate in samples/s * 1s + delayLine.setMaximumDelayInSamples (sampleRate); + + // Delay in seconds is converted to delay in samples + delayLine.setDelay (delay->get() * sampleRate); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + feedbackFloat = feedback->get(); + mixFloat = mix->get(); + totalNumInputChannels = getTotalNumInputChannels(); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayLine.pushSample(channel, channelData[sample]); + float drySample = channelData[sample]; + float wetSample = delayLine.popSample(channel, -1, true) * feedbackFloat; // -1 is used for the second argument of popSample to use value from setDelay in prepareToPlay block + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Delay Wet/Dry Mix + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayLine.pushSample(channel, channelData[sample]); + float drySample = channelData[sample]; + float wetSample = delayLine.popSample(channel, -1, true) * feedbackFloat; + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Delay PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + + float gainFloat; + float feedbackFloat; + float mixFloat; + + int totalNumInputChannels; + + juce::dsp::DelayLine delayLine; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayProcessor) +}; diff --git a/gui/config/plugins/DelayPlugin/DelayPluginV2/Main.cpp b/gui/config/plugins/DelayPlugin/DelayPluginV2/Main.cpp new file mode 100644 index 0000000..b3ae425 --- /dev/null +++ b/gui/config/plugins/DelayPlugin/DelayPluginV2/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "DelayPluginV2.h" // Make sure to update this line with current version + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new DelayProcessor(); +} \ No newline at end of file diff --git a/gui/config/plugins/DelayPlugin/DelayPluginV3/DelayPluginV3.h b/gui/config/plugins/DelayPlugin/DelayPluginV3/DelayPluginV3.h new file mode 100644 index 0000000..7f26d8e --- /dev/null +++ b/gui/config/plugins/DelayPlugin/DelayPluginV3/DelayPluginV3.h @@ -0,0 +1,210 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: DelayPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Delay audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2022 + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: DelayProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class DelayProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + DelayProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.001f, 1.0f, 0.1f)); // Delay is in seconds + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.2f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + // Initializes delay line processors + delayCHNL1.prepare (spec); + delayCHNL2.prepare (spec); + + // Since the delay parameter is limited to a maximum of 1s, the maximum possible delay in samples is sampleRate in samples/s * 1s + delayCHNL1.setMaximumDelayInSamples (sampleRate); + delayCHNL2.setMaximumDelayInSamples (sampleRate); + + // Delay in seconds is converted to delay in samples + delayFloat = delay->get(); + delayCHNL1.setDelay (delayFloat * sampleRate); + delayCHNL2.setDelay (delayFloat * sampleRate); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + delayFloat = delay->get(); + feedbackFloat = feedback->get(); + mixFloat = mix->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + delayCHNL1.setDelay (delayFloat * sampleRate); + delayCHNL2.setDelay (delayFloat * sampleRate); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + drySample = channelData[sample]; + wetSample = delayCHNL1.popSample(channel, -1, true); // Second argument of popSample is -1 to use value from setDelay function + + // No feedback currently for testing purposes + delayCHNL1.pushSample(channel, channelData[sample]);//delayCHNL1.pushSample(channel, drySample + wetSample * feedbackFloat);//delayCHNL1.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix dry sample with delayed sample + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + drySample = channelData[sample]; + wetSample = delayCHNL2.popSample(channel, -1, true); + + // No feedback currently for testing purposes + delayCHNL2.pushSample(channel, channelData[sample]);//delayCHNL2.pushSample(channel, drySample + wetSample * feedbackFloat);//delayCHNL2.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Delay PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + + float gainFloat; + float delayFloat; + float feedbackFloat; + float mixFloat; + + float drySample; + float wetSample; + double sampleRate; + int totalNumInputChannels; + + juce::dsp::DelayLine delayCHNL1; + juce::dsp::DelayLine delayCHNL2; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayProcessor) +}; diff --git a/gui/config/plugins/DelayPlugin/DelayPluginV3/Main.cpp b/gui/config/plugins/DelayPlugin/DelayPluginV3/Main.cpp new file mode 100644 index 0000000..392099c --- /dev/null +++ b/gui/config/plugins/DelayPlugin/DelayPluginV3/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "DelayPluginV3.h" // Make sure to update this line with current version + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new DelayProcessor(); +} \ No newline at end of file diff --git a/gui/config/plugins/DistortionPlugin/DistortionPlugin.h b/gui/config/plugins/DistortionPlugin/DistortionPlugin.h new file mode 100644 index 0000000..646d395 --- /dev/null +++ b/gui/config/plugins/DistortionPlugin/DistortionPlugin.h @@ -0,0 +1,126 @@ +/******************************************************************************* + + name: DistortionPlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: distortion audio plugin. + lastUpdated: April 4 2025 by Anna Andres + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, juce_dsp, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: DistortionProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class DistortionProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + DistortionProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 3.0f, 1.0f)); + addParameter (di = new juce::AudioParameterFloat({ "di", 1 }, "Distortion Intensity", 5.0f, 50.0f, 30.0f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double, int) override {} + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One buffer of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + + auto gainValue = gain->get(); + auto diValue = di->get(); + + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { + float processedSample = channelData[sample] * gainValue; // applying gain + processedSample = copysign((1-1/(abs(diValue*processedSample)+1)), processedSample); // apply reciprocal clipping function + channelData[sample] = processedSample; + } + } + } + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Distortion PlugIn"; } + // This function returns a boolean for whether or not the plugin accepts Midi input. We don't. so this will be false + bool acceptsMidi() const override { return false; } + // This function returns a boolean for whether or not the plugin has Midi output. We don't. so this will be false + bool producesMidi() const override { return false; } + // This specifies how much longer there is output when the input stops. This would be helpful for reverb/delay but not so much for distortion/gain + // A 0 tail length means that the output stops as soon as the input stops + double getTailLengthSeconds() const override { return 0; } //TODO: Change tail length if desired + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*di); + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + di->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + // DO NOT CHANGE THIS FUNCTION + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* di; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DistortionProcessor) +}; diff --git a/gui/config/plugins/DistortionPlugin/Main.cpp b/gui/config/plugins/DistortionPlugin/Main.cpp new file mode 100644 index 0000000..5886a75 --- /dev/null +++ b/gui/config/plugins/DistortionPlugin/Main.cpp @@ -0,0 +1,9 @@ +#include +#include +#include "DistortionPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new DistortionProcessor(); +} diff --git a/gui/config/plugins/DistortionPlugin/QuadClipping.txt b/gui/config/plugins/DistortionPlugin/QuadClipping.txt new file mode 100644 index 0000000..d9428a1 --- /dev/null +++ b/gui/config/plugins/DistortionPlugin/QuadClipping.txt @@ -0,0 +1,18 @@ +This is another method of distortion but it doesn't sound nearly as nice as the one being used in the distortion plugin + +// two-stage quadratic soft clipping + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { + float processedSample = channelData[sample] * gainValue; // applying gain + if (abs(processedSample) > 0.1) { + channelData[sample] = processedSample; + continue; + } else if ((0.05 <= abs(processedSample)) && (abs(processedSample) <= 0.1)) { + processedSample = copysign((a4Value-pow(2-abs(a4Value*processedSample),2))/a4Value, processedSample); + } else { + processedSample = 2*processedSample; // ramp up + } + channelData[sample] = processedSample; + } + } diff --git a/gui/config/plugins/EchoPlugin/EchoPlugin.h b/gui/config/plugins/EchoPlugin/EchoPlugin.h new file mode 100644 index 0000000..dc7a184 --- /dev/null +++ b/gui/config/plugins/EchoPlugin/EchoPlugin.h @@ -0,0 +1,358 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: EchoPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Echo audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2022 + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: EchoProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class EchoProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + EchoProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 2.0f, 1.0f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.001f, 1.0f, 0.1f)); // Delay is in seconds + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.2f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.3f)); + addParameter (echo = new juce::AudioParameterInt ({ "echo", 1 }, "Amount of Echoes", 0, 4, 2)); // Zero echoes is pass-through + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + // Initializes delay line processors + delayCHNL1.prepare (spec); + delayCHNL2.prepare (spec); + + // Since the delay parameter is limited to a maximum of 1s, the maximum possible delay in samples is sampleRate in samples/s * 1s + delayCHNL1.setMaximumDelayInSamples (sampleRate); + delayCHNL2.setMaximumDelayInSamples (sampleRate); + + // Delay in seconds is converted to delay in samples + delayFloat = delay->get(); + delayCHNL1.setDelay (delayFloat * sampleRate); + delayCHNL2.setDelay (delayFloat * sampleRate); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + delayFloat = delay->get(); + feedbackFloat = feedback->get(); + mixFloat = mix->get(); + echoInt = echo->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + delayCHNL1.setDelay (delayFloat * sampleRate); + delayCHNL2.setDelay (delayFloat * sampleRate); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + switch(echoInt) + { + case 1: // 1 Echo + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples1 = sampleRate * delayFloat; + + wetSample1 = delayCHNL1.popSample(channel, delayInSamples1, true); + + drySample = channelData[sample]; + + delayCHNL1.pushSample(channel, (drySample * (1.0f - feedbackFloat)) + (wetSample1 * feedbackFloat)); // Feedback + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat); // Mix dry sample with wet sample + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples1 = sampleRate * delayFloat; + + wetSample1 = delayCHNL2.popSample(channel, delayInSamples1, true); + + drySample = channelData[sample]; + + delayCHNL2.pushSample(channel, (drySample * (1.0f - feedbackFloat)) + (wetSample1 * feedbackFloat)); + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + case 2: // 2 Echoes + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples1 = sampleRate * delayFloat * 0.5f; + delayInSamples2 = sampleRate * delayFloat; + + wetSample1 = delayCHNL1.popSample(channel, delayInSamples1, true); + wetSample2 = delayCHNL1.popSample(channel, delayInSamples2, true); + + drySample = channelData[sample]; + + delayCHNL1.pushSample(channel, (drySample * (1.0f - feedbackFloat)) + (wetSample1 * feedbackFloat / 1.5f) + (wetSample2 * feedbackFloat / 3.0f)); // Feedback + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat / 1.5f) + (wetSample2 * mixFloat / 3.0f); // Mix + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples1 = sampleRate * delayFloat * 0.5f; + delayInSamples2 = sampleRate * delayFloat; + + wetSample1 = delayCHNL2.popSample(channel, delayInSamples1, true); + wetSample2 = delayCHNL2.popSample(channel, delayInSamples2, true); + + drySample = channelData[sample]; + + delayCHNL2.pushSample(channel, (drySample * (1.0f - feedbackFloat)) + (wetSample1 * feedbackFloat / 1.5f) + (wetSample2 * feedbackFloat / 3.0f)); + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat / 1.5f) + (wetSample2 * mixFloat / 3.0f); + channelData[sample] *= gainFloat; + } + } + break; + + case 3: // 3 Echoes + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples1 = sampleRate * delayFloat * 1.0f / 3.0f; + delayInSamples2 = sampleRate * delayFloat * 2.0f / 3.0f; + delayInSamples3 = sampleRate * delayFloat; + + wetSample1 = delayCHNL1.popSample(channel, delayInSamples1, true); + wetSample2 = delayCHNL1.popSample(channel, delayInSamples2, true); + wetSample3 = delayCHNL1.popSample(channel, delayInSamples3, true); + + drySample = channelData[sample]; + + delayCHNL1.pushSample(channel, (drySample * (1.0f - feedbackFloat)) + (wetSample1 * feedbackFloat / 2.0f) + (wetSample2 * feedbackFloat / 3.0f) + (wetSample3 * feedbackFloat / 6.0f)); // Feedback + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat / 2.0f) + (wetSample2 * mixFloat / 3.0f) + (wetSample3 * mixFloat / 6.0f); // Mix + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples1 = sampleRate * delayFloat * 1.0f / 3.0f; + delayInSamples2 = sampleRate * delayFloat * 2.0f / 3.0f; + delayInSamples3 = sampleRate * delayFloat; + + wetSample1 = delayCHNL2.popSample(channel, delayInSamples1, true); + wetSample2 = delayCHNL2.popSample(channel, delayInSamples2, true); + wetSample3 = delayCHNL2.popSample(channel, delayInSamples3, true); + + drySample = channelData[sample]; + + delayCHNL2.pushSample(channel, (drySample * (1.0f - feedbackFloat)) + (wetSample1 * feedbackFloat / 2.0f) + (wetSample2 * feedbackFloat / 3.0f) + (wetSample3 * feedbackFloat / 6.0f)); + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat / 2.0f) + (wetSample2 * mixFloat / 3.0f) + (wetSample3 * mixFloat / 6.0f); + channelData[sample] *= gainFloat; + } + } + break; + + case 4: // 4 Echoes + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples1 = sampleRate * delayFloat * 0.25f; + delayInSamples2 = sampleRate * delayFloat * 0.5f; + delayInSamples3 = sampleRate * delayFloat * 0.75f; + delayInSamples4 = sampleRate * delayFloat; + + wetSample1 = delayCHNL1.popSample(channel, delayInSamples1, true); + wetSample2 = delayCHNL1.popSample(channel, delayInSamples2, true); + wetSample3 = delayCHNL1.popSample(channel, delayInSamples3, true); + wetSample4 = delayCHNL1.popSample(channel, delayInSamples4, true); + + drySample = channelData[sample]; + + delayCHNL1.pushSample(channel, (drySample * (1.0f - feedbackFloat)) + (wetSample1 * feedbackFloat * 0.4f) + (wetSample2 * feedbackFloat * 0.3f) + (wetSample3 * feedbackFloat * 0.2f) + (wetSample4 * feedbackFloat * 0.1f)); // Feedback + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat * 0.4f) + (wetSample2 * mixFloat * 0.3f) + (wetSample3 * mixFloat * 0.2f) + (wetSample4 * mixFloat * 0.1f); // Mix + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples1 = sampleRate * delayFloat * 0.25f; + delayInSamples2 = sampleRate * delayFloat * 0.5f; + delayInSamples3 = sampleRate * delayFloat * 0.75f; + delayInSamples4 = sampleRate * delayFloat; + + wetSample1 = delayCHNL2.popSample(channel, delayInSamples1, true); + wetSample2 = delayCHNL2.popSample(channel, delayInSamples2, true); + wetSample3 = delayCHNL2.popSample(channel, delayInSamples3, true); + wetSample4 = delayCHNL2.popSample(channel, delayInSamples4, true); + + drySample = channelData[sample]; + + delayCHNL2.pushSample(channel, (drySample * (1.0f - feedbackFloat)) + (wetSample1 * feedbackFloat * 0.4f) + (wetSample2 * feedbackFloat * 0.3f) + (wetSample3 * feedbackFloat * 0.2f) + (wetSample4 * feedbackFloat * 0.1f)); + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample1 * mixFloat * 0.4f) + (wetSample2 * mixFloat * 0.3f) + (wetSample3 * mixFloat * 0.2f) + (wetSample4 * mixFloat * 0.1f); + channelData[sample] *= gainFloat; + } + } + break; + + default: // Pass-Through + break; + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Echo PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + juce::MemoryOutputStream (destData, true).writeInt (*echo); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + echo->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + juce::AudioParameterInt* echo; + + float gainFloat; + float delayFloat; + float feedbackFloat; + float mixFloat; + int echoInt; + + double sampleRate; + int totalNumInputChannels; + float drySample; + + float wetSample1; + float wetSample2; + float wetSample3; + float wetSample4; + + int delayInSamples1; + int delayInSamples2; + int delayInSamples3; + int delayInSamples4; + + juce::dsp::DelayLine delayCHNL1; + juce::dsp::DelayLine delayCHNL2; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EchoProcessor) +}; diff --git a/gui/config/plugins/EchoPlugin/Main.cpp b/gui/config/plugins/EchoPlugin/Main.cpp new file mode 100644 index 0000000..a2c0259 --- /dev/null +++ b/gui/config/plugins/EchoPlugin/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "EchoPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new EchoProcessor(); +} \ No newline at end of file diff --git a/gui/config/plugins/EnvelopePlugin/EnvelopePlugin.h b/gui/config/plugins/EnvelopePlugin/EnvelopePlugin.h new file mode 100644 index 0000000..729cb14 --- /dev/null +++ b/gui/config/plugins/EnvelopePlugin/EnvelopePlugin.h @@ -0,0 +1,154 @@ +/******************************************************************************* + + name: EnvelopePlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: envelope follower audio plugin. + lastUpdated: Feb 7 2025 by Anna Andres + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: EnvelopeProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class EnvelopeProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + EnvelopeProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + // TODO: Add your parameters here. This allows you to assign min, max, and default parameters (respectively) for each parameter + // Example: + // addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 2.0f, 0.5f)); + addParameter (attack = new juce::AudioParameterFloat ({ "attack", 1 }, "Attack", 0.0f, 100.0f, 50.0f)); + addParameter (release = new juce::AudioParameterFloat ({ "release", 1 }, "Release", 0.0f, 100.0f, 50.0f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double, int) override {} + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One block of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + // TODO: Read the value for your parameters in from the GUI using get() + // Example: + // auto gainValue = gain->get(); + auto attackValue = attack->get(); + auto releaseValue = release->get(); + + float attackStrength = pow( 0.01, 1.0 / ( attackValue * getSampleRate() * 0.001 ) ); + float releaseStrength = pow( 0.01, 1.0 / ( releaseValue * getSampleRate() * 0.001 ) ); + float envelope = 0; + + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) + { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + // TODO: process the audio sample-by-sample here + //float processedSample = channelData[sample] * gain; + float inputEnvelope = fabsf(channelData[sample]); + if (inputEnvelope > envelope) + envelope = attackStrength * envelope + (1 - attackStrength) * inputEnvelope; + else + envelope = releaseStrength * envelope + (1 - releaseStrength) * inputEnvelope; + + // write processed sample back to buffer + channelData[sample] = envelope; + } + } + } + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + // TODO: Change the return string to be what you want the plugin name to be + const juce::String getName() const override { return "Envelope PlugIn"; } + // This function returns a boolean for whether or not the plugin accepts Midi input. We don't. so this will be false + bool acceptsMidi() const override { return false; } + // This function returns a boolean for whether or not the plugin has Midi output. We don't. so this will be false + bool producesMidi() const override { return false; } + // This specifies how much longer there is output when the input stops. This would be helpful for reverb/delay but not so much for distortion/gain + // A 0 tail length means that the output stops as soon as the input stops + double getTailLengthSeconds() const override { return 0; } //TODO: Change tail length if desired + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + // TODO: Save the value of your parameter to memory. Make sure you do this for every one of your parameters. + void getStateInformation (juce::MemoryBlock& destData) override + { + // Example: + // juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*attack); + juce::MemoryOutputStream (destData, true).writeFloat (*release); + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + // TODO: Read the value into your parameter from memory. Make sure you do this for every one of your parameters. + void setStateInformation (const void* data, int sizeInBytes) override + { + // Example: + // gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + attack->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + release->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + // DO NOT CHANGE THIS FUNCTION + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + // TODO: This is where you define your audio parameters from the GUI that your code relies on in the process block. You can also define other variables here. + // Example: + // juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* attack; + juce::AudioParameterFloat* release; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnvelopeProcessor) +}; diff --git a/gui/config/plugins/EnvelopePlugin/Main.cpp b/gui/config/plugins/EnvelopePlugin/Main.cpp new file mode 100644 index 0000000..cc386f2 --- /dev/null +++ b/gui/config/plugins/EnvelopePlugin/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "EnvelopePlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new EnvelopeProcessor(); +} diff --git a/gui/config/plugins/FlangerPlugin/FlangerV1/FlangerPlugin.h b/gui/config/plugins/FlangerPlugin/FlangerV1/FlangerPlugin.h new file mode 100644 index 0000000..1c46270 --- /dev/null +++ b/gui/config/plugins/FlangerPlugin/FlangerV1/FlangerPlugin.h @@ -0,0 +1,242 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: FlangerPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Flanger audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: FlangerProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class FlangerProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + FlangerProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f)); + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 10.0f, 5.0f)); // rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.001f, 0.01f, 0.003f)); // delay is in seconds + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.2f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + + // Delay Lines + + // Initializes both delay lines + chnl1delay.prepare (spec); + chnl2delay.prepare (spec); + + // Since the delay parameter is limited to a maximum of 0.01s, and based on delayInSamples + // which can double the value of the delay parameter based on the LFO, the maximum possible number of samples is sampleRate in samples/s * 0.02s + chnl1delay.setMaximumDelayInSamples (sampleRate * 0.02f); + chnl2delay.setMaximumDelayInSamples (sampleRate * 0.02f); + + // Converts delay in seconds to delay in samples and updates delay of both delay lines + chnl1delay.setDelay (delay->get() * sampleRate); + chnl2delay.setDelay (delay->get() * sampleRate); + + + // LFOs + + // Initializes both LFOs + chnl1LFO.prepare (spec); + chnl2LFO.prepare (spec); + + // Updates rate of both LFOs + rateFloat = rate->get(); + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + rateFloat = rate->get(); + depthFloat = depth->get(); + delayFloat = delay->get(); + feedbackFloat = feedback->get(); + mixFloat = mix->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (chnl1LFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, channelData[sample]);//chnl1delay.pushSample(channel, drySample + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix Delay + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (chnl2LFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, channelData[sample]);//chnl2delay.pushSample(channel, drySample + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Flanger PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + + float gainFloat; + float rateFloat; + float depthFloat; + float delayFloat; + float feedbackFloat; + float mixFloat; + + float drySample; + float wetSample; + double sampleRate; + int totalNumInputChannels; + int delayInSamples; + + juce::dsp::DelayLine chnl1delay; + juce::dsp::DelayLine chnl2delay; + + // The last argument for the following lines is the number of points in the lookup table + juce::dsp::Oscillator chnl1LFO { [](float x) { return std::sin (x); }, 200 }; + juce::dsp::Oscillator chnl2LFO { [](float x) { return std::sin (x); }, 200 }; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlangerProcessor) +}; diff --git a/gui/config/plugins/FlangerPlugin/FlangerV2/FlangerPlugin.h b/gui/config/plugins/FlangerPlugin/FlangerV2/FlangerPlugin.h new file mode 100644 index 0000000..58bf665 --- /dev/null +++ b/gui/config/plugins/FlangerPlugin/FlangerV2/FlangerPlugin.h @@ -0,0 +1,321 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: FlangerPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Flanger audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: FlangerProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class FlangerProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + FlangerProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 2.0f, 1.0f)); + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1 }, "Rate", 0.0f, 10.0f, 5.0f)); // rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1 }, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.001f, 0.01f, 0.003f)); // delay is in seconds + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.1f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + + // Mode 0: Pass-Through, Mode 1: Additive Flanging, Mode 2: Subtractive Flanging, Mode 3: Through-Zero Flanging + addParameter (flangMode = new juce::AudioParameterInt ({ "flangMode", 1 }, "Flanging Mode", 0, 3, 1)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + + // Delay Lines + + // Initializes both delay lines + chnl1delay.prepare (spec); + chnl2delay.prepare (spec); + + // Since the delay parameter is limited to a maximum of 0.01s, and based on delayInSamples + // which can double the value of the delay parameter based on the LFO, the maximum possible number of samples is sampleRate in samples/s * 0.02s + chnl1delay.setMaximumDelayInSamples (sampleRate * 0.02f); + chnl2delay.setMaximumDelayInSamples (sampleRate * 0.02f); + + // Converts delay in seconds to delay in samples and updates delay of both delay lines + delayFloat = delay->get(); + chnl1delay.setDelay (delay->get() * sampleRate); + chnl2delay.setDelay (delay->get() * sampleRate); + + + // LFOs + + // Initializes both LFOs + chnl1LFO.prepare (spec); + chnl2LFO.prepare (spec); + + // Updates rate of both LFOs + rateFloat = rate->get(); + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + rateFloat = rate->get(); + depthFloat = depth->get(); + delayFloat = delay->get(); + feedbackFloat = feedback->get(); + mixFloat = mix->get(); + mode = flangMode->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + switch(mode) + { + case 1: // Additive Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1LFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix Delay (wet added to dry) + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2LFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + case 2: // Subtractive Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1LFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); // Mix Delay (wet subtracted from dry) + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2LFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + case 3: // Through-Zero Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1LFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = chnl1delay.popSample(channel, delayFloat * sampleRate, true); // Note: not actually dry since it's delayed but reusing the variable name for simplicity + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, channelData[sample] + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix Delay (modulated wet added to delayed wet) + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2LFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = chnl2delay.popSample(channel, delayFloat * sampleRate, true); + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, channelData[sample] + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + default: // Pass-Through + break; + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Flanger PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + juce::MemoryOutputStream (destData, true).writeInt (*flangMode); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + flangMode->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + juce::AudioParameterInt* flangMode; + + float gainFloat; + float rateFloat; + float depthFloat; + float delayFloat; + float feedbackFloat; + float mixFloat; + int mode; + + float drySample; + float wetSample; + double sampleRate; + int totalNumInputChannels; + int delayInSamples; + + juce::dsp::DelayLine chnl1delay; + juce::dsp::DelayLine chnl2delay; + + // The last argument for the following lines is the number of points in the lookup table + juce::dsp::Oscillator chnl1LFO { [](float x) { return std::sin (x); }, 200 }; + juce::dsp::Oscillator chnl2LFO { [](float x) { return std::sin (x); }, 200 }; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlangerProcessor) +}; diff --git a/gui/config/plugins/FlangerPlugin/FlangerV3/FlangerPlugin.h b/gui/config/plugins/FlangerPlugin/FlangerV3/FlangerPlugin.h new file mode 100644 index 0000000..adb6563 --- /dev/null +++ b/gui/config/plugins/FlangerPlugin/FlangerV3/FlangerPlugin.h @@ -0,0 +1,556 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: FlangerPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Flanger audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: FlangerProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class FlangerProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + FlangerProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 2.0f, 1.0f)); + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1 }, "Rate", 0.0f, 10.0f, 0.2f)); // rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1 }, "Depth", 0.0f, 1.0f, 0.9f)); + addParameter (delay = new juce::AudioParameterFloat ({ "delay", 1 }, "Delay", 0.001f, 0.01f, 0.003f)); // delay is in seconds + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", 0.0f, 1.0f, 0.1f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.3f)); + + // Waveform 0: Pass-Through, Waveform 1: Sinusoidal LFO, Waveform 2: Saw Wave LFO, Waveform 3: Square Wave LFO + addParameter (waveform = new juce::AudioParameterInt ({ "waveform", 1 }, "Waveform", 0, 3, 1)); + + // Mode 0: Pass-Through, Mode 1: Additive Flanging, Mode 2: Subtractive Flanging, Mode 3: Through-Zero Flanging + addParameter (flangMode = new juce::AudioParameterInt ({ "flangMode", 1 }, "Flanging Mode", 0, 3, 1)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + + // Delay Lines + + // Initializes both delay lines + chnl1delay.prepare (spec); + chnl2delay.prepare (spec); + + // Since the delay parameter is limited to a maximum of 0.01s, and based on delayInSamples + // which can double the value of the delay parameter based on the LFO, the maximum possible number of samples is sampleRate in samples/s * 0.02s + chnl1delay.setMaximumDelayInSamples (sampleRate * 0.02f); + chnl2delay.setMaximumDelayInSamples (sampleRate * 0.02f); + + // Converts delay in seconds to delay in samples and updates delay of both delay lines + delayFloat = delay->get(); + chnl1delay.setDelay (delay->get() * sampleRate); + chnl2delay.setDelay (delay->get() * sampleRate); + + + // LFOs + + // Initializes all LFOs + chnl1sineLFO.prepare (spec); + chnl2sineLFO.prepare (spec); + chnl1sawLFO.prepare (spec); + chnl2sawLFO.prepare (spec); + chnl1squareLFO.prepare (spec); + chnl2squareLFO.prepare (spec); + + // Updates rate of all LFOs + rateFloat = rate->get(); + chnl1sineLFO.setFrequency (rateFloat); + chnl2sineLFO.setFrequency (rateFloat); + chnl1sawLFO.setFrequency (rateFloat); + chnl2sawLFO.setFrequency (rateFloat); + chnl1squareLFO.setFrequency (rateFloat); + chnl2squareLFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + gainFloat = gain->get(); + rateFloat = rate->get(); + depthFloat = depth->get(); + delayFloat = delay->get(); + feedbackFloat = feedback->get(); + mixFloat = mix->get(); + waveformInt = waveform->get(); + mode = flangMode->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1sineLFO.setFrequency (rateFloat); + chnl2sineLFO.setFrequency (rateFloat); + chnl1sawLFO.setFrequency (rateFloat); + chnl2sawLFO.setFrequency (rateFloat); + chnl1squareLFO.setFrequency (rateFloat); + chnl2squareLFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + switch(waveformInt) + { + case 1: // Sinusoidal LFO + switch(mode) + { + case 1: // Additive Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1sineLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix Delay (wet added to dry) + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2sineLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + case 2: // Subtractive Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1sineLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); // Mix Delay (wet subtracted from dry) + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2sineLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + case 3: // Through-Zero Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1sineLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = chnl1delay.popSample(channel, delayFloat * sampleRate, true); // Note: not actually dry since it's delayed but reusing the variable name for simplicity + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); // Mix Delay (modulated wet added to delayed wet) + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2sineLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = chnl2delay.popSample(channel, delayFloat * sampleRate, true); + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + default: // Pass-Through + break; + } + break; + + case 2: // Saw Wave LFO + switch(mode) + { + case 1: // Additive Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1sawLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix Delay (wet added to dry) + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2sawLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + case 2: // Subtractive Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1sawLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); // Mix Delay (wet subtracted from dry) + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2sawLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + case 3: // Through-Zero Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1sawLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = chnl1delay.popSample(channel, delayFloat * sampleRate, true); // Note: not actually dry since it's delayed but reusing the variable name for simplicity + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix Delay (modulated wet added to delayed wet) + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2sawLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = chnl2delay.popSample(channel, delayFloat * sampleRate, true); + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + default: // Pass-Through + break; + } + break; + + case 3: // Square Wave LFO + switch(mode) + { + case 1: // Additive Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1squareLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix Delay (wet added to dry) + channelData[sample] *= gainFloat; // Gain + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2squareLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + case 2: // Subtractive Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1squareLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); // Mix Delay (wet subtracted from dry) + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2squareLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = channelData[sample]; + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) - (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + case 3: // Through-Zero Flanging + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl1squareLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = chnl1delay.popSample(channel, delayFloat * sampleRate, true); // Note: not actually dry since it's delayed but reusing the variable name for simplicity + wetSample = chnl1delay.popSample(channel, delayInSamples, true); + chnl1delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); // Feedback + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); // Mix Delay (modulated wet added to delayed wet) + channelData[sample] *= gainFloat; // Gain + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + delayInSamples = (depthFloat * chnl2squareLFO.processSample(0.0f) * delayFloat + delayFloat) * sampleRate; + + drySample = chnl2delay.popSample(channel, delayFloat * sampleRate, true); + wetSample = chnl2delay.popSample(channel, delayInSamples, true); + chnl2delay.pushSample(channel, drySample * (1.0f - feedbackFloat) + wetSample * feedbackFloat); + + channelData[sample] = (drySample * (1.0f - mixFloat)) + (wetSample * mixFloat); + channelData[sample] *= gainFloat; + } + } + break; + + default: // Pass-Through + break; + } + break; + + default: // Pass-Through + break; + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Flanger PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*delay); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + juce::MemoryOutputStream (destData, true).writeInt (*waveform); + juce::MemoryOutputStream (destData, true).writeInt (*flangMode); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + delay->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + waveform->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + flangMode->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* delay; + juce::AudioParameterFloat* feedback; + juce::AudioParameterFloat* mix; + juce::AudioParameterInt* waveform; + juce::AudioParameterInt* flangMode; + + float gainFloat; + float rateFloat; + float depthFloat; + float delayFloat; + float feedbackFloat; + float mixFloat; + int waveformInt; + int mode; + + float drySample; + float wetSample; + double sampleRate; + int totalNumInputChannels; + int delayInSamples; + + juce::dsp::DelayLine chnl1delay; + juce::dsp::DelayLine chnl2delay; + + // The last argument for the following lines is the number of points in the lookup table + juce::dsp::Oscillator chnl1sineLFO { [](float x) { return std::sin (x); }, 500 }; // Sine Wave + juce::dsp::Oscillator chnl1sawLFO { [](float x) { return x / juce::MathConstants::pi; }, 10000 }; // Saw Wave + juce::dsp::Oscillator chnl1squareLFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 10000 }; // Square Wave + juce::dsp::Oscillator chnl2sineLFO { [](float x) { return std::sin (x); }, 500 }; // Sine Wave + juce::dsp::Oscillator chnl2sawLFO { [](float x) { return x / juce::MathConstants::pi; }, 10000 }; // Saw Wave + juce::dsp::Oscillator chnl2squareLFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 10000 }; // Square Wave + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlangerProcessor) +}; diff --git a/gui/config/plugins/FlangerPlugin/Main.cpp b/gui/config/plugins/FlangerPlugin/Main.cpp new file mode 100644 index 0000000..8428740 --- /dev/null +++ b/gui/config/plugins/FlangerPlugin/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "FlangerPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new FlangerProcessor(); +} diff --git a/gui/config/plugins/FunDistortionPlugin/FunDistortionPlugin.h b/gui/config/plugins/FunDistortionPlugin/FunDistortionPlugin.h new file mode 100644 index 0000000..e955cf7 --- /dev/null +++ b/gui/config/plugins/FunDistortionPlugin/FunDistortionPlugin.h @@ -0,0 +1,220 @@ +/******************************************************************************* + + name: FunDistortionPlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: fun distortion audio plugin. these won't sound like typical/practical distortion effects + lastUpdated: April 4 2025 by Anna Andres + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, juce_dsp, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: FunDistortionProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class FunDistortionProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + FunDistortionProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 3.0f, 1.0f)); + addParameter (mode = new juce::AudioParameterInt({ "mode", 1 }, "Mode", 0, 4, 0)); + addParameter (lowthres = new juce::AudioParameterFloat({ "lowthres", 1 }, "Lower Threshold (Mode 1)", 0.5f, 9.0f, 0.5f)); + addParameter (highthres = new juce::AudioParameterFloat({ "highthres", 1 }, "(Higher) Threshold (Mode 1 & 4)", 0.5f, 9.0f, 0.5f)); + addParameter (nBits = new juce::AudioParameterFloat({ "nBits", 1 }, "Number of Bits (Mode 2)", 1.0f, 128.0f, 4.0f)); + addParameter (percentDrop = new juce::AudioParameterFloat({ "percentDrop", 1 }, "Sample Drop Percent (Mode 3)", 0.0f, 10.0f, 0.5f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double, int) override {} + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One buffer of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + + auto gainValue = gain->get(); + + //int modeValue = juce::roundToInt(mode->get()); + int modeValue = mode->get(); + + // this seems counter-intuitive but the higher the parameter value, the closer the second calculations will be to 0 + auto hthres = lowthres->get(); + auto lthres = highthres->get(); + float lowThreshold = 0.05f / lthres; + float highThreshold = 0.05f / hthres; + + // wavefold threshold will just use the upper threshold parameter instead of having its own (due to having a max of six params) + float wavefoldThreshold = 0.05f / hthres; + + auto nbits = nBits->get(); // can be int or float for plugins + auto ampValues = pow(2, nbits-1); + + auto pDrop = percentDrop->get(); + + + switch(modeValue) { + case 1: // pause distortion - self made + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { + float processedSample = channelData[sample] * gainValue; // applying gain + if(processedSample <= (-1 * highThreshold)) { + processedSample = processedSample + (highThreshold - lowThreshold); + } + else if((-1 * highThreshold) < processedSample <= (-1 * lowThreshold)) { + processedSample = (-1 * lowThreshold); + } + else if(lowThreshold < processedSample <= highThreshold) { + processedSample = lowThreshold; + } + else if(processedSample > highThreshold) { + processedSample = processedSample - (highThreshold - lowThreshold); + } + channelData[sample] = processedSample; + } + } + break; + case 2: // bit crushing + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { + float processedSample = ceil(ampValues*channelData[sample])*(1/ampValues); // apply bit crushing + channelData[sample] = gainValue*(processedSample); + } + } + break; + case 3: // sample dropout + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { + int randomNum = fmod(rand(),100); + if(randomNum < pDrop) { + channelData[sample] = 0; + continue; + } + channelData[sample] = channelData[sample] * gainValue; + } + } + break; + case 4: // wave folding + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { + float processedSample = channelData[sample] * gainValue; // applying gain + float dif = 0; + if(processedSample > wavefoldThreshold) { + dif = processedSample - wavefoldThreshold; + channelData[sample] = wavefoldThreshold - dif; + } + else if(processedSample < (-1 * wavefoldThreshold)) { + dif = wavefoldThreshold - processedSample; + channelData[sample] = wavefoldThreshold + dif; + } + else { + channelData[sample] = processedSample; + } + } + } + break; + default: + // do nothing + break; + } + } + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Fun Distortion PlugIn"; } + // This function returns a boolean for whether or not the plugin accepts Midi input. We don't. so this will be false + bool acceptsMidi() const override { return false; } + // This function returns a boolean for whether or not the plugin has Midi output. We don't. so this will be false + bool producesMidi() const override { return false; } + // This specifies how much longer there is output when the input stops. This would be helpful for reverb/delay but not so much for distortion/gain + // A 0 tail length means that the output stops as soon as the input stops + double getTailLengthSeconds() const override { return 0; } //TODO: Change tail length if desired + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeInt (*mode); + juce::MemoryOutputStream (destData, true).writeFloat (*lowthres); + juce::MemoryOutputStream (destData, true).writeFloat (*highthres); + juce::MemoryOutputStream (destData, true).writeFloat (*nBits); + juce::MemoryOutputStream (destData, true).writeFloat (*percentDrop); + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mode->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + lowthres->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + highthres->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + nBits->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + percentDrop->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + // DO NOT CHANGE THIS FUNCTION + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterInt* mode; + juce::AudioParameterFloat* lowthres; + juce::AudioParameterFloat* highthres; + juce::AudioParameterFloat* nBits; + juce::AudioParameterFloat* percentDrop; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FunDistortionProcessor) +}; + diff --git a/gui/config/plugins/FunDistortionPlugin/Main.cpp b/gui/config/plugins/FunDistortionPlugin/Main.cpp new file mode 100644 index 0000000..f863902 --- /dev/null +++ b/gui/config/plugins/FunDistortionPlugin/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "FunDistortionPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new FunDistortionProcessor(); +} diff --git a/gui/config/plugins/FuzzPlugin/FuzzPlugin.h b/gui/config/plugins/FuzzPlugin/FuzzPlugin.h new file mode 100644 index 0000000..7413e8b --- /dev/null +++ b/gui/config/plugins/FuzzPlugin/FuzzPlugin.h @@ -0,0 +1,134 @@ +/******************************************************************************* + + name: FuzzPlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: fuzz audio plugin. + lastUpdated: Jan 20 2025 by Anna Andres + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, juce_dsp, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: FuzzProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class FuzzProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + FuzzProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 3.0f, 0.5f)); + addParameter (clip = new juce::AudioParameterFloat ({ "clip", 1 }, "Clip", 0.0f, 9.0f, 5.0f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double, int) override {} + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One buffer of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + + auto gainValue = gain->get(); + auto clipValue = clip->get(); + + float clipThreshold = 0.05f / clipValue; + + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) + { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + float processedSample = channelData[sample] * gainValue; // applying gain + + processedSample = (processedSample > clipThreshold) ? clipThreshold : ((processedSample < -clipThreshold) ? -clipThreshold : processedSample); + + // write processed sample back to buffer + channelData[sample] = processedSample; + } + } + } + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Fuzz PlugIn"; } + // This function returns a boolean for whether or not the plugin accepts Midi input. We don't. so this will be false + bool acceptsMidi() const override { return false; } + // This function returns a boolean for whether or not the plugin has Midi output. We don't. so this will be false + bool producesMidi() const override { return false; } + // This specifies how much longer there is output when the input stops. This would be helpful for reverb/delay but not so much for distortion/gain + // A 0 tail length means that the output stops as soon as the input stops + double getTailLengthSeconds() const override { return 0; } //TODO: Change tail length if desired + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeFloat (*clip);; + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + clip->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + // DO NOT CHANGE THIS FUNCTION + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterFloat* clip; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FuzzProcessor) +}; + diff --git a/gui/config/plugins/FuzzPlugin/Main.cpp b/gui/config/plugins/FuzzPlugin/Main.cpp new file mode 100644 index 0000000..40340d5 --- /dev/null +++ b/gui/config/plugins/FuzzPlugin/Main.cpp @@ -0,0 +1,7 @@ +#include + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new FuzzProcessor(); +} diff --git a/gui/config/plugins/GUI.cpp b/gui/config/plugins/GUI.cpp new file mode 100644 index 0000000..878181e --- /dev/null +++ b/gui/config/plugins/GUI.cpp @@ -0,0 +1,622 @@ +#include +#include +#include +#include +#include +#include + +struct Parameter{ + std::string name; + double value; + uint8_t type; +}; + +struct Plugins{ + std::string name; + std::vector parameters; +}; + +/*list of plugins and parameters +*When adding new plugins name the vector the plugin name in the .json file +*ex. std::vector "pluginname" = { +* param1, +* param2, +* param3 +*} +* Types: +* 0 button: +* 1 fader: +* 2 bypass: +*/ + +std::vector reverb = { + {"bypass", 1, 2}, + {"freeze", 0, 0}, + {"dry", 0.5, 1}, + {"wet", 0.5, 1}, + {"room_size", 0.5, 1}, + {"width", 0.5, 1}, + {"damp", 1, 1} +}; + +std::vector plugins = { + {"reverb", reverb} +}; + +void initscreen(){ + initscr(); // Initialize ncurses screen + cbreak(); // Disable line buffering + noecho(); // Disable echoing of characters + keypad(stdscr, TRUE); // Enable keypad for arrow keys + curs_set(FALSE); // Dont show cursor + + // Check if the terminal supports color + if (!(has_colors())) { + endwin(); + printf("Your terminal does not support color\n"); + } + // Initialize color pairs + start_color(); + init_pair(1, COLOR_GREEN, COLOR_BLACK); // Color for filled levels + init_pair(2, COLOR_WHITE, COLOR_BLACK); // Color for empty levels + init_pair(3, COLOR_BLACK, COLOR_WHITE); // Inverted for selection + +} + +uint8_t draw_letter(int16_t y, int16_t x, char letter){ + switch (letter) + { + case 'a': + case 'A': + mvprintw(y, x, " _ "); + mvprintw(y+1, x, " / \\ "); + mvprintw(y+2, x, " / _ \\ "); + mvprintw(y+3, x, " / ___ \\ "); + mvprintw(y+4, x, "/_/ \\_\\"); + return 9; + case 'b': + case 'B': + mvprintw(y, x, " ____ "); + mvprintw(y+1, x, "| __ ) "); + mvprintw(y+2, x, "| _ \\ "); + mvprintw(y+3, x, "| |_) |"); + mvprintw(y+4, x, "|____/ "); + return 7; + case 'c': + case 'C': + mvprintw(y, x, " ____ "); + mvprintw(y+1, x, " / ___|"); + mvprintw(y+2, x, "| | "); + mvprintw(y+3, x, "| |___ "); + mvprintw(y+4, x, " \\____|"); + return 7; + case 'd': + case 'D': + mvprintw(y, x, " ____ "); + mvprintw(y+1, x, "| _ \\ "); + mvprintw(y+2, x, "| | | |"); + mvprintw(y+3, x, "| |_| |"); + mvprintw(y+4, x, "|____/ "); + return 7; + case 'e': + case 'E': + mvprintw(y, x, " _____ "); + mvprintw(y+1, x, "| ____|"); + mvprintw(y+2, x, "| _| "); + mvprintw(y+3, x, "| |___ "); + mvprintw(y+4, x, "|_____|"); + return 7; + case 'f': + case 'F': + mvprintw(y, x, " _____ "); + mvprintw(y+1, x, "| ___|"); + mvprintw(y+2, x, "| |_ "); + mvprintw(y+3, x, "| _| "); + mvprintw(y+4, x, "|_| "); + return 7; + case 'g': + case 'G': + mvprintw(y, x, " ____ "); + mvprintw(y+1, x, " / ___|"); + mvprintw(y+2, x, "| | _ "); + mvprintw(y+3, x, "| |_| |"); + mvprintw(y+4, x, " \\____|"); + return 7; + case 'h': + case 'H': + mvprintw(y, x, " _ _ "); + mvprintw(y+1, x, "| | | |"); + mvprintw(y+2, x, "| |_| |"); + mvprintw(y+3, x, "| _ |"); + mvprintw(y+4, x, "|_| |_|"); + return 7; + case 'i': + case 'I': + mvprintw(y, x, " ___ "); + mvprintw(y+1, x, "|_ _|"); + mvprintw(y+2, x, " | | "); + mvprintw(y+3, x, " | | "); + mvprintw(y+4, x, "|___|"); + return 5; + case 'j': + case 'J': + mvprintw(y, x, " _ "); + mvprintw(y+1, x, " | |"); + mvprintw(y+2, x, " _ | |"); + mvprintw(y+3, x, "| |_| |"); + mvprintw(y+4, x, " \\___/ "); + return 7; + case 'k': + case 'K': + mvprintw(y, x, " _ __"); + mvprintw(y+1, x, "| |/ /"); + mvprintw(y+2, x, "| ' / "); + mvprintw(y+3, x, "| . \\ "); + mvprintw(y+4, x, "|_|\\_\\"); + return 7; + case 'l': + case 'L': + mvprintw(y, x, " _ "); + mvprintw(y+1, x, "| | "); + mvprintw(y+2, x, "| | "); + mvprintw(y+3, x, "| |___ "); + mvprintw(y+4, x, "|_____|"); + return 7; + case 'm': + case 'M': + mvprintw(y, x, " __ __ "); + mvprintw(y+1, x, "| \\/ |"); + mvprintw(y+2, x, "| |\\/| |"); + mvprintw(y+3, x, "| | | |"); + mvprintw(y+4, x, "|_| |_|"); + return 8; + case 'n': + case 'N': + mvprintw(y, x, " _ _ "); + mvprintw(y+1, x, "| \\ | |"); + mvprintw(y+2, x, "| \\| |"); + mvprintw(y+3, x, "| |\\ |"); + mvprintw(y+4, x, "|_| \\_|"); + return 7; + case 'o': + case 'O': + mvprintw(y, x, " ___ "); + mvprintw(y+1, x, " / _ \\ "); + mvprintw(y+2, x, "| | | |"); + mvprintw(y+3, x, "| |_| |"); + mvprintw(y+4, x, " \\___/ "); + return 7; + case 'p': + case 'P': + mvprintw(y, x, " ____ "); + mvprintw(y+1, x, "| _ \\ "); + mvprintw(y+2, x, "| |_) |"); + mvprintw(y+3, x, "| __/ "); + mvprintw(y+4, x, "|_| "); + return 7; + case 'q': + case 'Q': + mvprintw(y, x, " ___ "); + mvprintw(y+1, x, " / _ \\ "); + mvprintw(y+2, x, "| | | |"); + mvprintw(y+3, x, "| |_| |"); + mvprintw(y+4, x, " \\__\\_\\"); + return 7; + case 'r': + case 'R': + mvprintw(y, x, " ____ "); + mvprintw(y+1, x, "| _ \\"); + mvprintw(y+2, x, "| |_) |"); + mvprintw(y+3, x, "| _ < "); + mvprintw(y+4, x, "|_| \\_\\"); + return 7; + case 's': + case 'S': + mvprintw(y, x, " ____ "); + mvprintw(y+1, x, "/ ___| "); + mvprintw(y+2, x, "\\___ \\ "); + mvprintw(y+3, x, " ___) |"); + mvprintw(y+4, x, "|____/ "); + return 7; + case 't': + case 'T': + mvprintw(y, x, " _____ "); + mvprintw(y+1, x, "|_ _|"); + mvprintw(y+2, x, " | | "); + mvprintw(y+3, x, " | | "); + mvprintw(y+4, x, " |_| "); + return 7; + case 'u': + case 'U': + mvprintw(y, x, " _ _ "); + mvprintw(y+1, x, "| | | |"); + mvprintw(y+2, x, "| | | |"); + mvprintw(y+3, x, "| |_| |"); + mvprintw(y+4, x, " \\___/ "); + return 7; + case 'v': + case 'V': + mvprintw(y, x, "__ __"); + mvprintw(y+1, x, "\\ \\ / /"); + mvprintw(y+2, x, " \\ \\ / / "); + mvprintw(y+3, x, " \\ V / "); + mvprintw(y+4, x, " \\_/ "); + return 9; + case 'w': + case 'W': + mvprintw(y, x, "__ __"); + mvprintw(y+1, x, "\\ \\ / /"); + mvprintw(y+2, x, " \\ \\ /\\ / / "); + mvprintw(y+3, x, " \\ V V / "); + mvprintw(y+4, x, " \\_/\\_/ "); + return 12; + case 'x': + case 'X': + mvprintw(y, x, "__ __"); + mvprintw(y+1, x, "\\ \\/ /"); + mvprintw(y+2, x, " \\ / "); + mvprintw(y+3, x, " / \\ "); + mvprintw(y+4, x, "/_/\\_\\"); + return 6; + case 'y': + case 'Y': + mvprintw(y, x, "__ __"); + mvprintw(y+1, x, "\\ \\ / /"); + mvprintw(y+2, x, " \\ V / "); + mvprintw(y+3, x, " | | "); + mvprintw(y+4, x, " |_| "); + return 7; + case 'z': + case 'Z': + mvprintw(y, x, " _____"); + mvprintw(y+1, x, "|__ /"); + mvprintw(y+2, x, " / / "); + mvprintw(y+3, x, " / /_ "); + mvprintw(y+4, x, "/____|"); + return 6; + case '1': + mvprintw(y, x, " _ "); + mvprintw(y+1, x, "/ |"); + mvprintw(y+2, x, "| |"); + mvprintw(y+3, x, "| |"); + mvprintw(y+4, x, "|_|"); + return 3; + case '2': + mvprintw(y, x, " ____ "); + mvprintw(y+1, x, "|___ \\ "); + mvprintw(y+2, x, " __) |"); + mvprintw(y+3, x, " / __/ "); + mvprintw(y+4, x, "|_____|"); + return 7; + case '3': + mvprintw(y, x, " _____ "); + mvprintw(y+1, x, "|___ / "); + mvprintw(y+2, x, " |_ \\ "); + mvprintw(y+3, x, " ___) |"); + mvprintw(y+4, x, "|____/ "); + return 7; + case '4': + mvprintw(y, x, " _ _ "); + mvprintw(y+1, x, "| || | "); + mvprintw(y+2, x, "| || |_ "); + mvprintw(y+3, x, "|__ _|"); + mvprintw(y+4, x, " |_| "); + return 8; + case '5': + mvprintw(y, x, " ____ "); + mvprintw(y+1, x, "| ___| "); + mvprintw(y+2, x, "|___ \\ "); + mvprintw(y+3, x, " ___) |"); + mvprintw(y+4, x, "|____/ "); + return 7; + case '6': + mvprintw(y, x, " __ "); + mvprintw(y+1, x, " / /_ "); + mvprintw(y+2, x, "| '_ \\ "); + mvprintw(y+3, x, "| (_) |"); + mvprintw(y+4, x, " \\___/ "); + return 7; + case '7': + mvprintw(y, x, " _____ "); + mvprintw(y+1, x, "|___ |"); + mvprintw(y+2, x, " / / "); + mvprintw(y+3, x, " / / "); + mvprintw(y+4, x, " /_/ "); + return 7; + case '8': + mvprintw(y, x, " ___ "); + mvprintw(y+1, x, " ( _ ) "); + mvprintw(y+2, x, " / _ \\ "); + mvprintw(y+3, x, "| (_) |"); + mvprintw(y+4, x, " \\___/ "); + return 7; + case '9': + mvprintw(y, x, " ___ "); + mvprintw(y+1, x, " / _ \\ "); + mvprintw(y+2, x, "| (_) |"); + mvprintw(y+3, x, " \\__, |"); + mvprintw(y+4, x, " /_/ "); + return 7; + + case '0': + mvprintw(y, x, " ___ "); + mvprintw(y+1, x, " / _ \\ "); + mvprintw(y+2, x, "| |/| |"); + mvprintw(y+3, x, "| |/| |"); + mvprintw(y+4, x, " \\___/ "); + return 7; + + case '.': + mvprintw(y, x, " "); + mvprintw(y+1, x, " "); + mvprintw(y+2, x, " "); + mvprintw(y+3, x, " _ "); + mvprintw(y+4, x, "(_)"); + return 3; + + case '^': + mvprintw(y, x, " __ "); + mvprintw(y+1, x, " / \\ "); + mvprintw(y+2, x, " / /\\ \\ "); + mvprintw(y+3, x, " / / \\ \\ "); + mvprintw(y+4, x, "/_/ \\ \\"); + return 10; + + + default: + mvprintw(y, x, " "); + mvprintw(y+1, x, " "); + mvprintw(y+2, x, " "); + mvprintw(y+3, x, " "); + mvprintw(y+4, x, " "); + return 1; + } + return -1; +} + +std::string formatFloatToDecimals(double value) { + std::ostringstream stream; + stream << std::fixed << std::setprecision(2) << value; + std::string result = stream.str(); + + if (result[0] == '0' && result[1] == '.') { + result.erase(0, 1); // Remove the first character '0' + } + if(result[0] == '1'){ + result = " 1"; + } + + return result; +} + + +void draw_vertical_fader(int16_t y, int16_t x, Parameter parameter) { + // Draw each level of the fader with appropriate color + int i; + for(i = 0; i < 20-(parameter.value * 100)/5; i++){ + mvprintw(y + i, x, "| |"); + } + for(; i < 20; i++){ + mvprintw(y + i, x, "| |"); + attron(COLOR_PAIR(1)); // Filled level color + mvprintw(y + i, x+1, "#######"); + attroff(COLOR_PAIR(1)); + } + attroff(COLOR_PAIR(3)); +} + +void draw_button(int16_t y, int16_t x, double parameter, bool selected){ + if(selected){ + attron(COLOR_PAIR(3)); + } + else{ + attron(COLOR_PAIR(2)); + } + mvprintw(y , x, " ________ "); + mvprintw(y+1 , x, "| |"); + mvprintw(y+2 , x, "| |"); + mvprintw(y+3 , x, "| |"); + mvprintw(y+4 , x, "|________|"); + if(parameter > 0){ + mvprintw(y+1 , x+1, "########"); + mvprintw(y+2 , x+1, "########"); + mvprintw(y+3 , x+1, "########"); + mvprintw(y+4 , x+1, "########"); + } + attroff(COLOR_PAIR(3)); +} + +uint8_t draw_text(int16_t y, int16_t x, std::string text, bool selected){ + uint8_t color; + uint16_t oldx = x; + if(selected){ + color = 3; + } + else{ + color = 2; + } + attron(COLOR_PAIR(color)); + for(int i = 0; i < text.length(); i++){ + x = x + draw_letter(y, x, text[i]); + } + attroff(COLOR_PAIR(color)); + return x-oldx; +} + +void send_osc_parameter(Plugins plugin, Parameter param){ + std::string command = "oscsend localhost 24024 /parameter/" + plugin.name + '/' + param.name + " f " + std::to_string(param.value); + system(command.c_str()); +} + +void send_osc_bypass(Plugins plugin, Parameter param){ + std::string command = "oscsend localhost 24024 /bypass/" + plugin.name + " i " + std::to_string(param.value); +} + +void draw_parameter_bypass(uint16_t y, uint16_t x, Parameter param, bool Selected){ + if(param.value == 0){ + draw_text(y,x,"on",Selected); + } + else{ + draw_text(y,x,"off",Selected); + } + draw_button(y,x+50, param.value, Selected); +} + +void draw_line(uint16_t y, uint16_t x, uint16_t length){ + for(int i = 0; i < length; i++){ + mvprintw(y , x+i, "-"); + } +} + +void draw_parameter_fader(uint16_t y, uint16_t x, Parameter param, bool Selected){ + draw_text(y, x, param.name.substr(0,4), Selected); + draw_text(y,x+41, formatFloatToDecimals(param.value), Selected); +} + +void draw_parameter_button(uint16_t y, uint16_t x, Parameter param, bool Selected){ + draw_text(y, x, param.name.substr(0,4), Selected); + draw_button(y,x+50, param.value, Selected); +} + +void draw_plugin(Plugins plugin, uint8_t cursor){ + draw_text(0,0,plugin.name, false); + draw_line(5,0,60); + for(int i = 0; i < plugin.parameters.size(); i++){ + if(reverb[i].type == 1){ + draw_parameter_fader(((6*i)+6), 0, plugin.parameters[i], cursor == i); + } + if(reverb[i].type == 0) { + draw_parameter_button(((6*i)+6), 0, plugin.parameters[i], cursor == i); + } + if(reverb[i].type == 2){ + draw_parameter_bypass((6*i)+6, 0, plugin.parameters[i], cursor == i); + } + draw_line(((6*i) + 11),0,60); + } +} + +void draw_faderscreen(Parameter parameter){ + clear(); + uint16_t textsize = draw_text(0,1,parameter.name, false); + draw_vertical_fader(6,(textsize/2)-4,parameter); + draw_text(27, (textsize/2) - 9, formatFloatToDecimals(parameter.value), false); +} + +void draw_screen_pluginmenu(Plugins plugin){ + +} +int main() { + //Control variables + uint8_t cursor = 0; + uint8_t oldcursor; + bool selected = false; + + initscreen(); + //Draw screen elements + + draw_plugin(plugins[0], cursor); + + while (true) { + char q = getch(); + switch (q) + { + case ',': + if(selected){ + if(plugins[0].parameters[cursor].type == 1){ + if(plugins[0].parameters[cursor].value > 0){ + plugins[0].parameters[cursor].value = plugins[0].parameters[cursor].value - .01; + draw_faderscreen(plugins[0].parameters[cursor]); + send_osc_parameter(plugins[0], plugins[0].parameters[cursor]); + } + } + if(plugins[0].parameters[cursor].type == 0){ + if(plugins[0].parameters[cursor].value == 1){ + plugins[0].parameters[cursor].value = 0; + draw_plugin(plugins[0], cursor); + send_osc_parameter(plugins[0], plugins[0].parameters[cursor]); + } + } + if(plugins[0].parameters[cursor].type == 2){ + if(plugins[0].parameters[cursor].value == 1){ + plugins[0].parameters[cursor].value = 0; + clear(); + draw_plugin(plugins[0], cursor); + send_osc_bypass(plugins[0], plugins[0].parameters[cursor]); + } + } + } + else{ + oldcursor = cursor; + if(cursor != 0){ + cursor = cursor - 1; + } + if(oldcursor != cursor){ + draw_plugin(plugins[0], cursor); + } + } + break; + + case '.': + if(selected == true){ + if(plugins[0].parameters[cursor].type == 1){ + if(plugins[0].parameters[cursor].value < 1){ + plugins[0].parameters[cursor].value = plugins[0].parameters[cursor].value + .01; + draw_faderscreen(plugins[0].parameters[cursor]); + send_osc_parameter(plugins[0], plugins[0].parameters[cursor]); + } + break; + } + if(plugins[0].parameters[cursor].type == 0){ + if(plugins[0].parameters[cursor].value == 0){ + plugins[0].parameters[cursor].value = 1; + draw_plugin(plugins[0], cursor); + send_osc_parameter(plugins[0], plugins[0].parameters[cursor]); + } + break; + } + if(plugins[0].parameters[cursor].type == 2){ + if(plugins[0].parameters[cursor].value == 0){ + plugins[0].parameters[cursor].value = 1; + clear(); + draw_plugin(plugins[0], cursor); + send_osc_bypass(plugins[0], plugins[0].parameters[cursor]); + } + } + } + else{ + oldcursor = cursor; + if(cursor != 6){ + cursor = cursor + 1; + } + if(oldcursor != cursor){ + draw_plugin(plugins[0], cursor); + } + } + break; + + case ' ': + selected = !(selected); + if(selected){ + if(plugins[0].parameters[cursor].type == 1){ + draw_faderscreen(plugins[0].parameters[cursor]); + } + } + if(selected == false && plugins[0].parameters[cursor].type == 1){ + clear(); + draw_plugin(plugins[0], cursor); + } + break; + + + case 'q': + endwin(); // End ncurses mode + return 0; + + default: + break; + } + } + + endwin(); // End ncurses mode + return 0; +} diff --git a/gui/config/plugins/GainPlugin/GainPlugin.h b/gui/config/plugins/GainPlugin/GainPlugin.h new file mode 100644 index 0000000..07fbffb --- /dev/null +++ b/gui/config/plugins/GainPlugin/GainPlugin.h @@ -0,0 +1,122 @@ +/******************************************************************************* + + name: GainPlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: gain audio plugin. + lastUpdated: April 1 2025 by Anna Andres + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, juce_dsp, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: GainProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class GainProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + GainProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 3.0f, 0.5f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double, int) override {} + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One buffer of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + + auto gainValue = gain->get(); + + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) + { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + channelData[sample] = channelData[sample] * gainValue; // apply gain + } + } + } + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Gain PlugIn"; } + // This function returns a boolean for whether or not the plugin accepts Midi input. We don't. so this will be false + bool acceptsMidi() const override { return false; } + // This function returns a boolean for whether or not the plugin has Midi output. We don't. so this will be false + bool producesMidi() const override { return false; } + // This specifies how much longer there is output when the input stops. This would be helpful for reverb/delay but not so much for distortion/gain + // A 0 tail length means that the output stops as soon as the input stops + double getTailLengthSeconds() const override { return 0; } //TODO: Change tail length if desired + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + // DO NOT CHANGE THIS FUNCTION + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainProcessor) +}; + diff --git a/gui/config/plugins/GainPlugin/Main.cpp b/gui/config/plugins/GainPlugin/Main.cpp new file mode 100644 index 0000000..5c21a46 --- /dev/null +++ b/gui/config/plugins/GainPlugin/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "GainPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new GainProcessor(); +} diff --git a/gui/config/plugins/PhaserPlugin/Main.cpp b/gui/config/plugins/PhaserPlugin/Main.cpp new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gui/config/plugins/PhaserPlugin/Main.cpp @@ -0,0 +1 @@ + diff --git a/gui/config/plugins/PhaserPlugin/PhaserPlugin.h b/gui/config/plugins/PhaserPlugin/PhaserPlugin.h new file mode 100644 index 0000000..1a68bef --- /dev/null +++ b/gui/config/plugins/PhaserPlugin/PhaserPlugin.h @@ -0,0 +1,148 @@ +/******************************************************************************* + + name: PhaserPlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: phaser audio plugin. + lastUpdated: March 17 2025 by Anna Andres + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, juce_dsp, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: PhaserProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class PhaserProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + PhaserProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + // adding parameters as well as their bounds + addParameter (rate = new juce::AudioParameterFloat ({ "rate", 1 }, "Rate", 0.0f, 25.0f, 0.5f)); + addParameter (depth = new juce::AudioParameterFloat ({ "depth", 1 }, "Depth", 0.01f, 0.99f, 0.5f)); + addParameter (centreFreq = new juce::AudioParameterFloat ({ "centreFreq", 1 }, "Centre Frequency", 0.0f, 600.0f, 100.0f)); + addParameter (feedback = new juce::AudioParameterFloat ({ "feedback", 1 }, "Feedback", -0.99f, 0.99f, 0.0f)); + addParameter (mix = new juce::AudioParameterFloat ({ "mix", 1 }, "Mix", 0.0f, 1.0f, 0.5f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double samplerate, int samplesPerBlock) override + { + // initialize the processor and set initial parameter values + juce::dsp::ProcessSpec spec { samplerate, static_cast(samplesPerBlock), static_cast(getTotalNumOutputChannels()) }; + phaser.prepare(spec); + phaser.setRate(0.5f); + phaser.setDepth(0.5f); + phaser.setCentreFrequency(100.0f); + phaser.setFeedback(0.0f); + phaser.setMix(0.5f); + } + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One block of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + juce::dsp::AudioBlock block (buffer); + juce::dsp::ProcessContextReplacing context (block); + + // read the values of the parameters in from the GUI + auto rateValue = rate->get(); + auto depthValue = depth->get(); + auto centreFreqValue = centreFreq->get(); + auto feedbackValue = feedback->get(); + auto mixValue = mix->get(); + + // update parameters and apply them to the audio block with process() + phaser.setRate(rateValue); + phaser.setDepth(depthValue); + phaser.setCentreFrequency(centreFreqValue); + phaser.setFeedback(feedbackValue); + phaser.setMix(mixValue); + phaser.process(context); + } + + //============================================================================== + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Phaser PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*centreFreq); + juce::MemoryOutputStream (destData, true).writeFloat (*feedback); + juce::MemoryOutputStream (destData, true).writeFloat (*mix); + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + void setStateInformation (const void* data, int sizeInBytes) override + { + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + centreFreq->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + feedback->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mix->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::dsp::Phaser phaser; + juce::AudioParameterFloat* rate; //the rate (in Hz) of the LFO modulating the phaser all-pass filters. (Must be <100Hz) + juce::AudioParameterFloat* depth; //the volume (between 0 and 1) of the LFO modulating the phaser all-pass filters + juce::AudioParameterFloat* centreFreq; //the centre frequency (in Hz) of the phaser all-pass filters modulation + juce::AudioParameterFloat* feedback; //the feedback volume (between -1 and 1) of the phaser. (Negative can be used to get specific phaser sounds) + juce::AudioParameterFloat* mix; //the amount of dry and wet signal in the output of the phaser (between 0 for full dry and 1 for full wet) + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PhaserProcessor) +}; diff --git a/gui/config/plugins/README.md b/gui/config/plugins/README.md new file mode 100644 index 0000000..75104c6 --- /dev/null +++ b/gui/config/plugins/README.md @@ -0,0 +1 @@ +# PedalboardPlugins \ No newline at end of file diff --git a/gui/config/plugins/ReverbPlugin/Main.cpp b/gui/config/plugins/ReverbPlugin/Main.cpp new file mode 100644 index 0000000..8483440 --- /dev/null +++ b/gui/config/plugins/ReverbPlugin/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "ReverbPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new ReverbProcessor(); +} diff --git a/gui/config/plugins/ReverbPlugin/ReverbPlugin.h b/gui/config/plugins/ReverbPlugin/ReverbPlugin.h new file mode 100644 index 0000000..8b32b99 --- /dev/null +++ b/gui/config/plugins/ReverbPlugin/ReverbPlugin.h @@ -0,0 +1,169 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: ReverbPlugin + version: 1.0.0 + vendor: JUCE + website: oshe.io + description: Reverb audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: ReverbProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class ReverbProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + ReverbProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (roomSize = new juce::AudioParameterFloat ({ "roomSize", 1 }, "Room Size", 0.0f, 1.0f, 0.5f)); // 0 is small, 1 is large + addParameter (damping = new juce::AudioParameterFloat ({"damping", 1}, "Damping", 0.0f, 1.0f, 0.5f)); // 0 is undamped, 1 is fully damped + addParameter (wetLevel = new juce::AudioParameterFloat ({"wetLevel", 1}, "Wet Level", 0.0f, 1.0f, 0.5f)); + addParameter (dryLevel = new juce::AudioParameterFloat ({ "dryLevel", 1 }, "Dry Level", 0.0f, 1.0f, 0.5f)); + addParameter (width = new juce::AudioParameterFloat ({ "width", 1 }, "Width", 0.0f, 1.0f, 0.5f)); // 1 is very wide + addParameter (freezeMode = new juce::AudioParameterFloat ({ "freezeMode", 1 }, "Freeze Mode", 0.0f, 1.0f, 0.0f)); // Enters freeze mode above 0.5 + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Sets reverb parameters + reverbParams.roomSize = roomSize->get(); + reverbParams.damping = damping->get(); + reverbParams.wetLevel = wetLevel->get(); + reverbParams.dryLevel = dryLevel->get(); + reverbParams.width = width->get(); + reverbParams.freezeMode = freezeMode->get(); + + reverb.setParameters (reverbParams); + reverb.setSampleRate (sampleRate); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + // Determines number of input channels for either mono or stereo processing + totalNumInputChannels = getTotalNumInputChannels(); + + if (totalNumInputChannels == 1) + { + reverb.processMono (buffer.getWritePointer(0), buffer.getNumSamples()); + } + else + { + reverb.processStereo (buffer.getWritePointer(0), buffer.getWritePointer(1), buffer.getNumSamples()); + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Reverb PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*roomSize); + juce::MemoryOutputStream (destData, true).writeFloat (*damping); + juce::MemoryOutputStream (destData, true).writeFloat (*wetLevel); + juce::MemoryOutputStream (destData, true).writeFloat (*dryLevel); + juce::MemoryOutputStream (destData, true).writeFloat (*width); + juce::MemoryOutputStream (destData, true).writeFloat (*freezeMode); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + roomSize->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + damping->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + wetLevel->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + dryLevel->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + width->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + freezeMode->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::Reverb reverb; + juce::Reverb::Parameters reverbParams; + + juce::AudioParameterFloat* roomSize; + juce::AudioParameterFloat* damping; + juce::AudioParameterFloat* wetLevel; + juce::AudioParameterFloat* dryLevel; + juce::AudioParameterFloat* width; + juce::AudioParameterFloat* freezeMode; + + int totalNumInputChannels; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReverbProcessor) +}; \ No newline at end of file diff --git a/gui/config/plugins/SaturationPlugin/Main.cpp b/gui/config/plugins/SaturationPlugin/Main.cpp new file mode 100644 index 0000000..23eb981 --- /dev/null +++ b/gui/config/plugins/SaturationPlugin/Main.cpp @@ -0,0 +1,9 @@ +#include +#include +#include "SaturationPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new SaturationProcessor(); +} diff --git a/gui/config/plugins/SaturationPlugin/SaturationPlugin.h b/gui/config/plugins/SaturationPlugin/SaturationPlugin.h new file mode 100644 index 0000000..8ee3b5d --- /dev/null +++ b/gui/config/plugins/SaturationPlugin/SaturationPlugin.h @@ -0,0 +1,154 @@ +/******************************************************************************* + + name: SaturationPlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: Saturation audio plugin. + lastUpdated: Aoril 4 2025 by Anna Andres + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, juce_dsp, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: SaturationProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class SaturationProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + SaturationProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 3.0f, 1.0f)); + addParameter (mode = new juce::AudioParameterInt({ "mode", 1 }, "Mode", 0, 2, 0)); + addParameter (sc1 = new juce::AudioParameterFloat({ "sc1", 1 }, "Soft Clipping Factor (Mode 1)", 1.0f, 10.0f, 1.0f)); + addParameter (sc2 = new juce::AudioParameterFloat({ "sc2", 1 }, "Soft Clipping Factor (Mode 2)", 0.0f, 0.4f, 0.333f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double, int) override {} + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One buffer of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + + auto gainValue = gain->get(); + int modeValue = mode->get(); + auto a1Value = sc1->get(); + auto a2Value = sc2->get(); + + switch(modeValue) { + case 1: // soft clipping + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { + float processedSample = channelData[sample] * gainValue; // applying gain + processedSample = 2/(juce::MathConstants::pi)*atan(a1Value*processedSample); // apply soft clipping + channelData[sample] = processedSample; + } + } + break; + case 2: // cubic soft clipping + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { + float processedSample = channelData[sample] * gainValue; // applying gain + processedSample = processedSample-a2Value*pow(processedSample,3); // apply soft clipping + channelData[sample] = processedSample; + } + } + break; + default: + // do nothing + break; + } + } + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Saturation PlugIn"; } + // This function returns a boolean for whether or not the plugin accepts Midi input. We don't. so this will be false + bool acceptsMidi() const override { return false; } + // This function returns a boolean for whether or not the plugin has Midi output. We don't. so this will be false + bool producesMidi() const override { return false; } + // This specifies how much longer there is output when the input stops. This would be helpful for reverb/delay but not so much for distortion/gain + // A 0 tail length means that the output stops as soon as the input stops + double getTailLengthSeconds() const override { return 0; } //TODO: Change tail length if desired + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeInt (*mode); + juce::MemoryOutputStream (destData, true).writeFloat (*sc1); + juce::MemoryOutputStream (destData, true).writeFloat (*sc2); + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + void setStateInformation (const void* data, int sizeInBytes) override + { + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + mode->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + sc1->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + sc2->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + // DO NOT CHANGE THIS FUNCTION + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* gain; + juce::AudioParameterInt* mode; + juce::AudioParameterFloat* sc1; + juce::AudioParameterFloat* sc2; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SaturationProcessor) +}; + diff --git a/gui/config/plugins/Template/Main.cpp b/gui/config/plugins/Template/Main.cpp new file mode 100644 index 0000000..61e51b7 --- /dev/null +++ b/gui/config/plugins/Template/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "TemplatePlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new TempProcessor(); +} diff --git a/gui/config/plugins/Template/TemplateV1/TempPlugin.h b/gui/config/plugins/Template/TemplateV1/TempPlugin.h new file mode 100644 index 0000000..0000778 --- /dev/null +++ b/gui/config/plugins/Template/TemplateV1/TempPlugin.h @@ -0,0 +1,135 @@ +/******************************************************************************* + + name: TemplatePlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: TEMPLATE audio plugin. + lastUpdated: Jan 20 2025 by Anna Andres + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: TempProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class TempProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + TempProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + // TODO: Add your parameters here. This allows you to assign min, max, and default parameters (respectively) for each parameter + // Example: + // addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 2.0f, 0.5f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double, int) override {} + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One block of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + // TODO: Read the value for your parameters in from the GUI using get() + // Example: + // auto gainValue = gain->get(); + + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) + { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + // TODO: process the audio sample-by-sample here + float processedSample = channelData[sample] * gainValue; + + // write processed sample back to buffer + channelData[sample] = processedSample; + } + } + } + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + // TODO: Change the return string to be what you want the plugin name to be + const juce::String getName() const override { return "TEMPLATE PlugIn"; } + // This function returns a boolean for whether or not the plugin accepts Midi input. We don't. so this will be false + bool acceptsMidi() const override { return false; } + // This function returns a boolean for whether or not the plugin has Midi output. We don't. so this will be false + bool producesMidi() const override { return false; } + // This specifies how much longer there is output when the input stops. This would be helpful for reverb/delay but not so much for distortion/gain + // A 0 tail length means that the output stops as soon as the input stops + double getTailLengthSeconds() const override { return 0; } //TODO: Change tail length if desired + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + // TODO: Save the value of your parameter to memory. Make sure you do this for every one of your parameters. + void getStateInformation (juce::MemoryBlock& destData) override + { + // Example: + // juce::MemoryOutputStream (destData, true).writeFloat (*gain); + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + // TODO: Read the value into your parameter from memory. Make sure you do this for every one of your parameters. + void setStateInformation (const void* data, int sizeInBytes) override + { + // Example: + // gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + // DO NOT CHANGE THIS FUNCTION + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + // TODO: This is where you define your audio parameters from the GUI that your code relies on in the process block. You can also define other variables here. + // Example: + // juce::AudioParameterFloat* gain; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TempProcessor) +}; diff --git a/gui/config/plugins/Template/TemplateV2/TempPlugin.h b/gui/config/plugins/Template/TemplateV2/TempPlugin.h new file mode 100644 index 0000000..8de9a9e --- /dev/null +++ b/gui/config/plugins/Template/TemplateV2/TempPlugin.h @@ -0,0 +1,161 @@ +/******************************************************************************* + + name: TemplatePlugin + version: 2.0.0 + vendor: JUCE + website: https://oshe.io + description: TEMPLATE audio plugin. + lastUpdated: 31 March 2025 by Georgia Heintz + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: TempProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class TempProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + // Constructor that lets you define input/output channels as well as parameters and their bounds + TempProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + // TODO: Add your parameters here. This allows you to assign min, max, and default parameters (respectively) for each parameter + // Example: + // addParameter (gain = new juce::AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 2.0f, 0.5f)); + } + + //============================================================================== + // This function is used before audio processing. It lets you initialize variables and set up any other resources prior to running the plugin + void prepareToPlay (double sampleRate, int samplesPerBlock) override +{ + /* Example for setting up a JUCE DSP processor (not specific to the oscillator class, it's simply being used as an example since many effects utilize LFOs) + + // Sets specs for a JUCE DSP processor + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + // Initializes the LFO (same function as for other DSP processors) + LFO.prepare (spec); + + // Sets the rate of the LFO to 5 Hz (this function is specific to the oscillator class, check JUCE documentation for specific functions needed to set up a different type of processor) + LFO.setFrequency (5.0f); + */ +} + // This function is usually called after the plugin stops taking in audio. It can deallocate any memory used and clean out buffers + void releaseResources() override {} + + // This is where all the audio processing happens. One block of audio input is handled at a time. + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + // TODO: Read the value for your parameters in from the GUI using get() + // Example: + // auto gainValue = gain->get(); + + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) + { + auto* channelData = buffer.getWritePointer(channel); + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + /* Example of how to get a value from the LFO for sample-by-sample processing (value of the argument in processSample doesn't matter, just the data type) + LFO.processSample(0.0f); + */ + + // TODO: process the audio sample-by-sample here + //float processedSample = channelData[sample] * gainValue; + + // write processed sample back to buffer + //channelData[sample] = processedSample; + } + } + } + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This creates the GUI editor for the plugin + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + // We have a GUI editor for the plugin so we return true + bool hasEditor() const override { return true; } + + //============================================================================== + // TODO: Change the return string to be what you want the plugin name to be + const juce::String getName() const override { return "TEMPLATE PlugIn"; } + // This function returns a boolean for whether or not the plugin accepts Midi input. We don't. so this will be false + bool acceptsMidi() const override { return false; } + // This function returns a boolean for whether or not the plugin has Midi output. We don't. so this will be false + bool producesMidi() const override { return false; } + // This specifies how much longer there is output when the input stops. This would be helpful for reverb/delay but not so much for distortion/gain + // A 0 tail length means that the output stops as soon as the input stops + double getTailLengthSeconds() const override { return 0; } //TODO: Change tail length if desired + + //============================================================================== + // DO NOT CHANGE ANY OF THESE + // This returns the number of presets/configurations for the plugin. We only have a default configuration so we return 1 + int getNumPrograms() override { return 1; } + // This returns the index of the currently selected program. This will always be 0 for this plugin + int getCurrentProgram() override { return 0; } + // This allows the user to switch to a different program if you have multiple + void setCurrentProgram (int) override {} + // This gives you the name of the program for a given index + const juce::String getProgramName (int) override { return "None"; } + // This allows you to change the name of a program at the given index + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + // This function saves the current state of each parameter to memory so that we can load the state of each parameter + // in the next session of running the pedal + // TODO: Save the value of your parameter to memory. Make sure you do this for every one of your parameters. + void getStateInformation (juce::MemoryBlock& destData) override + { + // Example: + // juce::MemoryOutputStream (destData, true).writeFloat (*gain); + } + + // This function recalls the state of the parameters from the last session ran and restores it into the parameter + // TODO: Read the value into your parameter from memory. Make sure you do this for every one of your parameters. + void setStateInformation (const void* data, int sizeInBytes) override + { + // Example: + // gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + // This function checks to see if the requested input/output configuration is compatible with the coded plugin + // DO NOT CHANGE THIS FUNCTION + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + // TODO: This is where you define your audio parameters from the GUI that your code relies on in the process block. You can also define other variables here. + // Example: + // juce::AudioParameterFloat* gain; + + /* Example declaring a JUCE DSP oscillator + // The last argument for the following three lines is the number of points in the lookup table + juce::dsp::Oscillator LFO { [](float x) { return std::sin (x); }, 200 }; // Sine Wave + */ + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TempProcessor) +}; + diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV1/Main.cpp b/gui/config/plugins/TremoloPlugin/TremoloPluginV1/Main.cpp new file mode 100644 index 0000000..96a8dbd --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV1/Main.cpp @@ -0,0 +1,19 @@ +/* + ============================================================================== + + This file was auto-generated and contains the startup code for a PIP. + + ============================================================================== +*/ + +// This plugin was made by modifying the GainPluginDemo from File>Open Example>Plugins>GainPluginDemo +// https://github.com/juce-framework/JUCE/blob/master/examples/Plugins/GainPluginDemo.h + +#include +#include "TremoloPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new TremoloProcessor(); +} diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV1/TremoloPlugin.h b/gui/config/plugins/TremoloPlugin/TremoloPluginV1/TremoloPlugin.h new file mode 100644 index 0000000..604bbcd --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV1/TremoloPlugin.h @@ -0,0 +1,198 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: TremoloPlugin + version: 1.0.0 + vendor: JUCE + website: http://juce.com + description: Tremolo audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2022 + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: TremoloProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class TremoloProcessor final : public AudioProcessor +{ +public: + + //============================================================================== + TremoloProcessor() + : AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo()) + .withOutput ("Output", AudioChannelSet::stereo())) + { + addParameter (rate = new AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 20.0f, 10.0f)); // rate is in Hz + addParameter (depth = new AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (gain = new AudioParameterFloat ({"gain", 1}, "Gain", 0.0f, 2.0f, 1.0f)); + } + + //============================================================================== + void prepareToPlay (double, int) override + { + position = 0; // Initial value for position within LFO signal + } + + void releaseResources() override {} + + void processBlock (AudioBuffer& buffer, MidiBuffer&) override + { + rateFloat = rate->get(); + depthFloat = depth->get(); + gainFloat = gain->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + w = 6.28318530718 * rateFloat; + LFO = sin(position * w); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + for (int i = 0; i < buffer.getNumSamples(); ++i) + { + channelData[i] = channelData[i] * (depthFloat * LFO + (1.0f - depthFloat)); // Tremolo + channelData[i] *= gainFloat; // Gain + + position += (1 / sampleRate); // Increment position by sampling interval + if (position >= (1 / rateFloat)) // Check if position is beyond one LFO period + { + position = 0; + } + } + } + } + + void processBlock (AudioBuffer& buffer, MidiBuffer&) override + { + rateFloat = rate->get(); + depthFloat = depth->get(); + gainFloat = gain->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + + w = 6.28318530718 * rateFloat; + LFO = sin(position * w); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + for (int i = 0; i < buffer.getNumSamples(); ++i) + { + channelData[i] = channelData[i] * (depthFloat * LFO + (1.0f - depthFloat)); // Tremolo + channelData[i] *= gainFloat; // Gain + + position += (1 / sampleRate); // Increment position by sampling interval + if (position >= (1 / rateFloat)) // Check if position is beyond one LFO period + { + position = 0; + } + } + } + } + + //============================================================================== + AudioProcessorEditor* createEditor() override { return new GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const String getName() const override { return "Tremolo Plugin"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const String getProgramName (int) override { return "None"; } + void changeProgramName (int, const String&) override {} + + //============================================================================== + void getStateInformation (MemoryBlock& destData) override + { + MemoryOutputStream (destData, true).writeFloat (*rate); + MemoryOutputStream (destData, true).writeFloat (*depth); + MemoryOutputStream (destData, true).writeFloat (*gain); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + rate->setValueNotifyingHost (MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + gain->setValueNotifyingHost (MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + AudioParameterFloat* rate; + AudioParameterFloat* depth; + AudioParameterFloat* gain; + + float rateFloat; + float depthFloat; + float gainFloat; + + double sampleRate; + int totalNumInputChannels; + float position; // Current position within LFO signal + float w; // w is in radians per second + float LFO; // Low-frequency oscillator + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TremoloProcessor) +}; diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV1/TremoloPlugin.jucer b/gui/config/plugins/TremoloPlugin/TremoloPluginV1/TremoloPlugin.jucer new file mode 100644 index 0000000..d29a16e --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV1/TremoloPlugin.jucer @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV2/Main.cpp b/gui/config/plugins/TremoloPlugin/TremoloPluginV2/Main.cpp new file mode 100644 index 0000000..96a8dbd --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV2/Main.cpp @@ -0,0 +1,19 @@ +/* + ============================================================================== + + This file was auto-generated and contains the startup code for a PIP. + + ============================================================================== +*/ + +// This plugin was made by modifying the GainPluginDemo from File>Open Example>Plugins>GainPluginDemo +// https://github.com/juce-framework/JUCE/blob/master/examples/Plugins/GainPluginDemo.h + +#include +#include "TremoloPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new TremoloProcessor(); +} diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV2/TremoloPlugin.h b/gui/config/plugins/TremoloPlugin/TremoloPluginV2/TremoloPlugin.h new file mode 100644 index 0000000..3dedd15 --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV2/TremoloPlugin.h @@ -0,0 +1,234 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: TremoloPlugin + version: 1.0.0 + vendor: JUCE + website: http://juce.com + description: Tremolo audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2022 + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: TremoloProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class TremoloProcessor final : public AudioProcessor +{ +public: + + //============================================================================== + TremoloProcessor() + : AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo()) + .withOutput ("Output", AudioChannelSet::stereo())) + { + addParameter (rate = new AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 20.0f, 10.0f)); // rate is in Hz + addParameter (depth = new AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (gain = new AudioParameterFloat ({"gain", 1}, "Gain", 0.0f, 2.0f, 1.0f)); + } + + //============================================================================== + void prepareToPlay (double, int) override + { + position1 = 0; // Initial value for position within LFO signal for channel 1 + position2 = 0; // Initial value for position within LFO signal for channel 2 + } + + void releaseResources() override {} + + void processBlock (AudioBuffer& buffer, MidiBuffer&) override + { + rateFloat = rate->get(); + depthFloat = depth->get(); + gainFloat = gain->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + w = 6.28318530718 * rateFloat; + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + if (channel == 0) + { + LFO = sin(position1 * w); + + for (int i = 0; i < buffer.getNumSamples(); ++i) + { + channelData[i] = channelData[i] * (depthFloat * LFO + (1.0f - depthFloat)) * gainFloat; // Tremolo and gain effects + + position1 += (1 / sampleRate); // Increment position for channel 1 by sampling interval + if (position1 >= (1 / rateFloat)) // Check if position for channel 1 is beyond one LFO period + { + position1 = 0; // Resets position for channel 1 after one period + } + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + LFO = sin(position2 * w); + + for (int i = 0; i < buffer.getNumSamples(); ++i) + { + channelData[i] = channelData[i] * (depthFloat * LFO + (1.0f - depthFloat)) * gainFloat; // Tremolo and gain effects + + position2 += (1 / sampleRate); // Increment position for channel 2 by sampling interval + if (position2 >= (1 / rateFloat)) // Check if position for channel 2 is beyond one LFO period + { + position2 = 0; // Resets position for channel 2 after one period + } + } + } + } + } + + void processBlock (AudioBuffer& buffer, MidiBuffer&) override + { + rateFloat = rate->get(); + depthFloat = depth->get(); + gainFloat = gain->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + w = 6.28318530718 * rateFloat; + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + if (channel == 0) + { + LFO = sin(position1 * w); + + for (int i = 0; i < buffer.getNumSamples(); ++i) + { + channelData[i] = channelData[i] * (depthFloat * LFO + (1.0f - depthFloat)) * gainFloat; // Tremolo and gain effects + + position1 += (1 / sampleRate); // Increment position for channel 1 by sampling interval + if (position1 >= (1 / rateFloat)) // Check if position for channel 1 is beyond one LFO period + { + position1 = 0; // Resets position for channel 1 after one period + } + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + LFO = sin(position2 * w); + + for (int i = 0; i < buffer.getNumSamples(); ++i) + { + channelData[i] = channelData[i] * (depthFloat * LFO + (1.0f - depthFloat)) * gainFloat; // Tremolo and gain effects + + position2 += (1 / sampleRate); // Increment position for channel 2 by sampling interval + if (position2 >= (1 / rateFloat)) // Check if position for channel 2 is beyond one LFO period + { + position2 = 0; // Resets position for channel 2 after one period + } + } + } + } + } + + //============================================================================== + AudioProcessorEditor* createEditor() override { return new GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const String getName() const override { return "Tremolo Plugin"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const String getProgramName (int) override { return "None"; } + void changeProgramName (int, const String&) override {} + + //============================================================================== + void getStateInformation (MemoryBlock& destData) override + { + MemoryOutputStream (destData, true).writeFloat (*rate); + MemoryOutputStream (destData, true).writeFloat (*depth); + MemoryOutputStream (destData, true).writeFloat (*gain); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + rate->setValueNotifyingHost (MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + gain->setValueNotifyingHost (MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + AudioParameterFloat* rate; + AudioParameterFloat* depth; + AudioParameterFloat* gain; + + float rateFloat; + float depthFloat; + float gainFloat; + + double sampleRate; + int totalNumInputChannels; + float position1; // Current position within LFO signal for channel 1 + float position2; // Current position within LFO signal for channel 2 + float w; // w is in radians per second + float LFO; // Low-frequency oscillator + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TremoloProcessor) +}; diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV2/TremoloPlugin.jucer b/gui/config/plugins/TremoloPlugin/TremoloPluginV2/TremoloPlugin.jucer new file mode 100644 index 0000000..d29a16e --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV2/TremoloPlugin.jucer @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV3/Main.cpp b/gui/config/plugins/TremoloPlugin/TremoloPluginV3/Main.cpp new file mode 100644 index 0000000..4eed2de --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV3/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "TremoloPlugin.h" + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new TremoloProcessor(); +} diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV3/TremoloPlugin.h b/gui/config/plugins/TremoloPlugin/TremoloPluginV3/TremoloPlugin.h new file mode 100644 index 0000000..8c84730 --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV3/TremoloPlugin.h @@ -0,0 +1,155 @@ +/******************************************************************************* + + name: TremoloPlugin + version: 1.0.0 + vendor: JUCE + website: https://oshe.io + description: TEMPLATE audio plugin. + lastUpdated: Jan __ 2025 by ____ + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: linux makefile + + type: AudioProcessor + mainClass: TremoloProcessor + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class TremoloProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + TremoloProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 20.0f, 10.0f)); // rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (gain = new juce::AudioParameterFloat ({"gain", 1}, "Gain", 0.0f, 2.0f, 1.0f)); + } + + //============================================================================== + void prepareToPlay (double, int) override + { + position1 = 0; // Initial value for position within LFO signal for channel 1 + position2 = 0; // Initial value for position within LFO signal for channel 2 + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + rateFloat = rate->get(); + depthFloat = depth->get(); + gainFloat = gain->get(); + + sampleRate = this->getSampleRate(); + totalNumInputChannels = getTotalNumInputChannels(); + w = 6.28318530718 * rateFloat; + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + if (channel == 0) + { + LFO = sin(position1 * w); + + for (int i = 0; i < buffer.getNumSamples(); ++i) + { + channelData[i] = channelData[i] * (depthFloat * LFO + (1.0f - depthFloat)) * gainFloat; // Tremolo and gain effects + + position1 += (1 / sampleRate); // Increment position for channel 1 by sampling interval + if (position1 >= (1 / rateFloat)) // Check if position for channel 1 is beyond one LFO period + { + position1 = 0; // Resets position for channel 1 after one period + } + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + LFO = sin(position2 * w); + + for (int i = 0; i < buffer.getNumSamples(); ++i) + { + channelData[i] = channelData[i] * (depthFloat * LFO + (1.0f - depthFloat)) * gainFloat; // Tremolo and gain effects + + position2 += (1 / sampleRate); // Increment position for channel 2 by sampling interval + if (position2 >= (1 / rateFloat)) // Check if position for channel 2 is beyond one LFO period + { + position2 = 0; // Resets position for channel 2 after one period + } + } + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Tremolo Plugin"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* gain; + + float rateFloat; + float depthFloat; + float gainFloat; + + double sampleRate; + int totalNumInputChannels; + float position1; // Current position within LFO signal for channel 1 + float position2; // Current position within LFO signal for channel 2 + float w; // w is in radians per second + float LFO; // Low-frequency oscillator + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TremoloProcessor) +}; diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV3/TremoloPlugin.jucer b/gui/config/plugins/TremoloPlugin/TremoloPluginV3/TremoloPlugin.jucer new file mode 100644 index 0000000..116d2ea --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV3/TremoloPlugin.jucer @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV4/Main.cpp b/gui/config/plugins/TremoloPlugin/TremoloPluginV4/Main.cpp new file mode 100644 index 0000000..10014ba --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV4/Main.cpp @@ -0,0 +1,12 @@ +#include +//#include +#include "TremoloPluginV4.h" // Make sure to update this with current version!! + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new TremoloProcessor(); +} + + +// Note: juce_dsp module added \ No newline at end of file diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV4/TremoloPlugin.jucer b/gui/config/plugins/TremoloPlugin/TremoloPluginV4/TremoloPlugin.jucer new file mode 100644 index 0000000..ac2782f --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV4/TremoloPlugin.jucer @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV4/TremoloPluginV4.h b/gui/config/plugins/TremoloPlugin/TremoloPluginV4/TremoloPluginV4.h new file mode 100644 index 0000000..efe41db --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV4/TremoloPluginV4.h @@ -0,0 +1,163 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: TremoloPlugin + version: 1.0.0 + website: oshe.io + description: Tremolo audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: TremoloProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class TremoloProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + TremoloProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 10.0f, 5.0f)); // rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.5f)); + addParameter (gain = new juce::AudioParameterFloat ({"gain", 1}, "Gain", 0.0f, 2.0f, 1.0f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + LFO.prepare (spec); + rateFloat = rate->get(); + LFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + depthFloat = depth->get(); + gainFloat = gain->get(); + totalNumInputChannels = getTotalNumInputChannels(); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + // The value of the argument in processSample doesn't matter, just the data type + channelData[sample] = (channelData[sample] * (depthFloat * LFO.processSample(0.0f) + (1.0f - depthFloat))) * gainFloat; + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Tremolo PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* gain; + + float rateFloat; + float depthFloat; + float gainFloat; + + // The last argument for the following three lines is the number of points in the lookup table + juce::dsp::Oscillator LFO { [](float x) { return std::sin (x); }, 200 }; // Sine Wave + //juce::dsp::Oscillator LFO { [](float x) { return x / juce::MathConstants::pi; }, 200 }; // Saw Wave + //juce::dsp::Oscillator LFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 200 }; // Square Wave + + int totalNumInputChannels; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TremoloProcessor) +}; \ No newline at end of file diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV5/Main.cpp b/gui/config/plugins/TremoloPlugin/TremoloPluginV5/Main.cpp new file mode 100644 index 0000000..2aad868 --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV5/Main.cpp @@ -0,0 +1,8 @@ +#include +#include "TremoloPluginV5.h" // Make sure to update this with current version!! + +//============================================================================== +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new TremoloProcessor(); +} \ No newline at end of file diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV5/TremoloPluginV5.h b/gui/config/plugins/TremoloPlugin/TremoloPluginV5/TremoloPluginV5.h new file mode 100644 index 0000000..811d13c --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV5/TremoloPluginV5.h @@ -0,0 +1,188 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: TremoloPlugin + version: 1.0.0 + website: oshe.io + description: Tremolo audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: TremoloProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class TremoloProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + TremoloProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 10.0f, 2.0f)); // rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.2f)); + addParameter (gain = new juce::AudioParameterFloat ({"gain", 1}, "Gain", 0.0f, 2.0f, 1.0f)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Same ProcessSpec used for both LFOs + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + // Initializes both LFOs + chnl1LFO.prepare (spec); + chnl2LFO.prepare (spec); + + // Updates rate of both LFOs + rateFloat = rate->get(); + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + rateFloat = rate->get(); + depthFloat = depth->get(); + gainFloat = gain->get(); + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1LFO.setFrequency (rateFloat); + chnl2LFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + // Both channels have their own LFO for stereo input + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + // Tremolo and gain applied to audio sample + channelData[sample] = (channelData[sample] * (depthFloat * chnl1LFO.processSample(0.0f) + (1.0f - depthFloat))) * gainFloat; + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + channelData[sample] = (channelData[sample] * (depthFloat * chnl2LFO.processSample(0.0f) + (1.0f - depthFloat))) * gainFloat; + } + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Tremolo PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* gain; + + float rateFloat; + float depthFloat; + float gainFloat; + + int totalNumInputChannels; + + // The last argument for the following lines is the number of points in the lookup table + // To change the waveform of the LFOs, comment out the current waveform and uncomment the desired waveform + juce::dsp::Oscillator chnl1LFO { [](float x) { return std::sin (x); }, 200 }; // Sine Wave + //juce::dsp::Oscillator chnl1LFO { [](float x) { return x / juce::MathConstants::pi; }, 200 }; // Saw Wave + //juce::dsp::Oscillator chnl1LFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 200 }; // Square Wave + juce::dsp::Oscillator chnl2LFO { [](float x) { return std::sin (x); }, 200 }; // Sine Wave + //juce::dsp::Oscillator chnl2LFO { [](float x) { return x / juce::MathConstants::pi; }, 200 }; // Saw Wave + //juce::dsp::Oscillator chnl2LFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 200 }; // Square Wave + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TremoloProcessor) +}; diff --git a/gui/config/plugins/TremoloPlugin/TremoloPluginV6/TremoloPluginV6.h b/gui/config/plugins/TremoloPlugin/TremoloPluginV6/TremoloPluginV6.h new file mode 100644 index 0000000..4313193 --- /dev/null +++ b/gui/config/plugins/TremoloPlugin/TremoloPluginV6/TremoloPluginV6.h @@ -0,0 +1,250 @@ +/* + ============================================================================== + + This file is part of the JUCE framework examples. + Copyright (c) Raw Material Software Limited + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: TremoloPlugin + version: 1.0.0 + website: oshe.io + description: Tremolo audio plugin. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, juce_dsp, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporter: Linux Makefile + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: TremoloProcessor + + useLocalCopy: 1 + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + + +//============================================================================== +class TremoloProcessor final : public juce::AudioProcessor +{ +public: + + //============================================================================== + TremoloProcessor() + : juce::AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) + .withOutput ("Output", juce::AudioChannelSet::stereo())) + { + addParameter (rate = new juce::AudioParameterFloat ({"rate", 1}, "Rate", 0.0f, 20.0f, 2.0f)); // rate is in Hz + addParameter (depth = new juce::AudioParameterFloat ({"depth", 1}, "Depth", 0.0f, 1.0f, 0.2f)); + addParameter (gain = new juce::AudioParameterFloat ({"gain", 1}, "Gain", 0.0f, 2.0f, 1.0f)); + + // Waveform 0: Pass-Through, Waveform 1: Sinusoidal LFO, Waveform 2: Saw Wave LFO, Waveform 3: Square Wave LFO + addParameter (waveform = new juce::AudioParameterInt ({ "waveform", 1 }, "Waveform", 0, 3, 1)); + } + + //============================================================================== + void prepareToPlay (double sampleRate, int samplesPerBlock) override + { + // Same ProcessSpec used for all LFOs + juce::dsp::ProcessSpec spec; + spec.maximumBlockSize = samplesPerBlock; + spec.sampleRate = sampleRate; + spec.numChannels = getTotalNumOutputChannels(); + + // Initializes all LFOs + chnl1sineLFO.prepare (spec); + chnl2sineLFO.prepare (spec); + chnl1sawLFO.prepare (spec); + chnl2sawLFO.prepare (spec); + chnl1squareLFO.prepare (spec); + chnl2squareLFO.prepare (spec); + + // Updates rate of all LFOs + rateFloat = rate->get(); + chnl1sineLFO.setFrequency (rateFloat); + chnl2sineLFO.setFrequency (rateFloat); + chnl1sawLFO.setFrequency (rateFloat); + chnl2sawLFO.setFrequency (rateFloat); + chnl1squareLFO.setFrequency (rateFloat); + chnl2squareLFO.setFrequency (rateFloat); + } + + void releaseResources() override {} + + void processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer&) override + { + rateFloat = rate->get(); + depthFloat = depth->get(); + gainFloat = gain->get(); + waveformInt = waveform->get(); + + totalNumInputChannels = getTotalNumInputChannels(); + + chnl1sineLFO.setFrequency (rateFloat); + chnl2sineLFO.setFrequency (rateFloat); + chnl1sawLFO.setFrequency (rateFloat); + chnl2sawLFO.setFrequency (rateFloat); + chnl1squareLFO.setFrequency (rateFloat); + chnl2squareLFO.setFrequency (rateFloat); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + + switch(waveformInt) + { + case 1: // Sinusoidal LFO + // Both channels have their own LFO for stereo input + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + // Tremolo and gain applied to audio sample + channelData[sample] = (channelData[sample] * (depthFloat * chnl1sineLFO.processSample(0.0f) + (1.0f - depthFloat))) * gainFloat; + } + } + else // Handles the second channel for stereo input but doesn't run for mono input + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + channelData[sample] = (channelData[sample] * (depthFloat * chnl2sineLFO.processSample(0.0f) + (1.0f - depthFloat))) * gainFloat; + } + } + break; + + case 2: // Saw Wave LFO + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + channelData[sample] = (channelData[sample] * (depthFloat * chnl1sawLFO.processSample(0.0f) + (1.0f - depthFloat))) * gainFloat; + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + channelData[sample] = (channelData[sample] * (depthFloat * chnl2sawLFO.processSample(0.0f) + (1.0f - depthFloat))) * gainFloat; + } + } + break; + + case 3: // Square Wave LFO + if (channel == 0) + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + channelData[sample] = (channelData[sample] * (depthFloat * chnl1squareLFO.processSample(0.0f) + (1.0f - depthFloat))) * gainFloat; + } + } + else + { + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + { + channelData[sample] = (channelData[sample] * (depthFloat * chnl2squareLFO.processSample(0.0f) + (1.0f - depthFloat))) * gainFloat; + } + } + break; + + default: // Pass-Through + break; + } + } + } + + //============================================================================== + juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); } + bool hasEditor() const override { return true; } + + //============================================================================== + const juce::String getName() const override { return "Tremolo PlugIn"; } + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0; } + + //============================================================================== + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const juce::String getProgramName (int) override { return "None"; } + void changeProgramName (int, const juce::String&) override {} + + //============================================================================== + void getStateInformation (juce::MemoryBlock& destData) override + { + juce::MemoryOutputStream (destData, true).writeFloat (*rate); + juce::MemoryOutputStream (destData, true).writeFloat (*depth); + juce::MemoryOutputStream (destData, true).writeFloat (*gain); + juce::MemoryOutputStream (destData, true).writeInt (*waveform); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + rate->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + depth->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + gain->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readFloat()); + waveform->setValueNotifyingHost (juce::MemoryInputStream (data, static_cast (sizeInBytes), false).readInt()); + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const auto& mainInLayout = layouts.getChannelSet (true, 0); + const auto& mainOutLayout = layouts.getChannelSet (false, 0); + + return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled())); + } + +private: + //============================================================================== + juce::AudioParameterFloat* rate; + juce::AudioParameterFloat* depth; + juce::AudioParameterFloat* gain; + juce::AudioParameterInt* waveform; + + float rateFloat; + float depthFloat; + float gainFloat; + int waveformInt; + + int totalNumInputChannels; + + // The last argument for the following lines is the number of points in the lookup table + juce::dsp::Oscillator chnl1sineLFO { [](float x) { return std::sin (x); }, 500 }; // Sine Wave + juce::dsp::Oscillator chnl1sawLFO { [](float x) { return x / juce::MathConstants::pi; }, 10000 }; // Saw Wave + juce::dsp::Oscillator chnl1squareLFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 10000 }; // Square Wave + juce::dsp::Oscillator chnl2sineLFO { [](float x) { return std::sin (x); }, 500 }; // Sine Wave + juce::dsp::Oscillator chnl2sawLFO { [](float x) { return x / juce::MathConstants::pi; }, 10000 }; // Saw Wave + juce::dsp::Oscillator chnl2squareLFO { [](float x) { return x < 0.0f ? -1.0f : 1.0f; }, 10000 }; // Square Wave + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TremoloProcessor) +}; diff --git a/gui/config/all_plugins.json b/gui/config/profiles/all_plugins.json similarity index 100% rename from gui/config/all_plugins.json rename to gui/config/profiles/all_plugins.json diff --git a/gui/config/checkoff.json b/gui/config/profiles/checkoff.json similarity index 100% rename from gui/config/checkoff.json rename to gui/config/profiles/checkoff.json diff --git a/gui/config/chorus.json b/gui/config/profiles/chorus.json similarity index 100% rename from gui/config/chorus.json rename to gui/config/profiles/chorus.json diff --git a/gui/config/chorus_2.json b/gui/config/profiles/chorus_2.json similarity index 100% rename from gui/config/chorus_2.json rename to gui/config/profiles/chorus_2.json diff --git a/gui/config/chorus_2_2.json b/gui/config/profiles/chorus_2_2.json similarity index 100% rename from gui/config/chorus_2_2.json rename to gui/config/profiles/chorus_2_2.json diff --git a/gui/config/compressor.json b/gui/config/profiles/compressor.json similarity index 100% rename from gui/config/compressor.json rename to gui/config/profiles/compressor.json diff --git a/gui/config/delay.json b/gui/config/profiles/delay.json similarity index 100% rename from gui/config/delay.json rename to gui/config/profiles/delay.json diff --git a/gui/config/distortion.json b/gui/config/profiles/distortion.json similarity index 100% rename from gui/config/distortion.json rename to gui/config/profiles/distortion.json diff --git a/gui/config/distortion_normal.json b/gui/config/profiles/distortion_normal.json similarity index 100% rename from gui/config/distortion_normal.json rename to gui/config/profiles/distortion_normal.json diff --git a/gui/config/flanger.json b/gui/config/profiles/flanger.json similarity index 100% rename from gui/config/flanger.json rename to gui/config/profiles/flanger.json diff --git a/gui/config/fundistortion.json b/gui/config/profiles/fundistortion.json similarity index 100% rename from gui/config/fundistortion.json rename to gui/config/profiles/fundistortion.json diff --git a/gui/config/fuzz.json b/gui/config/profiles/fuzz.json similarity index 100% rename from gui/config/fuzz.json rename to gui/config/profiles/fuzz.json diff --git a/gui/config/main.json b/gui/config/profiles/main.json similarity index 100% rename from gui/config/main.json rename to gui/config/profiles/main.json diff --git a/gui/config/tremolo.json b/gui/config/profiles/tremolo.json similarity index 100% rename from gui/config/tremolo.json rename to gui/config/profiles/tremolo.json diff --git a/gui/src/plugin_manager.py b/gui/src/plugin_manager.py index 3426621..18cc3ff 100644 --- a/gui/src/plugin_manager.py +++ b/gui/src/plugin_manager.py @@ -1,7 +1,7 @@ import json import os -from utils import config_dir +from utils import profiles_dir class Parameter(): @@ -94,7 +94,7 @@ def changeParameter(self, pluginIndex: int, parameterIndex: int, return None def all_plugins(): - json_path = os.path.join(config_dir, "all_plugins.json") + json_path = os.path.join(profiles_dir, "all_plugins.json") mgr = PluginManager() mgr.initFromJSON(json_path) return mgr.plugins @@ -194,7 +194,7 @@ def save_to_profile(self, profile_name: str) -> str: if not profile_name: raise ValueError("Profile name is required to save the board") - profile_path = os.path.join(config_dir, f"{profile_name}.json") + profile_path = os.path.join(profiles_dir, f"{profile_name}.json") with open(profile_path, "w") as file: json.dump(self.serialize(), file, indent=4) diff --git a/gui/src/qwidgets/core.py b/gui/src/qwidgets/core.py index e6f571a..d720ece 100644 --- a/gui/src/qwidgets/core.py +++ b/gui/src/qwidgets/core.py @@ -16,7 +16,7 @@ ScrollBarStyle, color_background, ControlDisplayStyle, BreadcrumbsBarStyle, styles_tabletitle, styles_tableitem ) -from utils import config_dir +from utils import config_dir, profiles_dir from qwidgets.parameter_widgets import ParameterPanel from qwidgets.controls import ControlDisplay, RotaryEncoder from qwidgets.graphics_utils import SCREEN_H, SCREEN_W @@ -80,7 +80,7 @@ def launch_board(self, selected_profile): board = PluginManager() selected_json = selected_profile + ".json" - json_path = os.path.join(config_dir, selected_json) + json_path = os.path.join(profiles_dir, selected_json) board.initFromJSON(json_path) # Restart mod-host so we can change profiles. @@ -527,8 +527,8 @@ def updateBypass(self, position: int, bypass): class ProfileSelectWindow(FloatingWindow): def __init__(self, callback): - self.json_dir = os.path.dirname(config_dir) - self.json_files = self.get_json_files(config_dir) + self.json_dir = profiles_dir + self.json_files = self.get_json_files(profiles_dir) self.json_files.sort() self.callback = callback diff --git a/gui/src/utils.py b/gui/src/utils.py index f0a01ff..acdff3e 100644 --- a/gui/src/utils.py +++ b/gui/src/utils.py @@ -6,4 +6,6 @@ src_dir = os.path.dirname(os.path.abspath(sys.argv[0])) root_dir = os.path.normpath(os.path.join(src_dir, "..")) config_dir = os.path.join(root_dir, "config") +profiles_dir = os.path.join(config_dir, "profiles") +plugins_dir = os.path.join(config_dir, "plugins") assets_dir = os.path.join(root_dir, "assets") From 293a155b47323905c229bc74a393a492f2da94b6 Mon Sep 17 00:00:00 2001 From: SillyGesture <69798195+Johnbob7@users.noreply.github.com> Date: Sat, 6 Dec 2025 16:35:00 -0500 Subject: [PATCH 3/8] Add profile builder flow --- gui/src/plugin_manager.py | 24 +++++ gui/src/qwidgets/core.py | 201 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 216 insertions(+), 9 deletions(-) diff --git a/gui/src/plugin_manager.py b/gui/src/plugin_manager.py index 18cc3ff..72dfdd6 100644 --- a/gui/src/plugin_manager.py +++ b/gui/src/plugin_manager.py @@ -77,6 +77,30 @@ def getPlugin(self, x: int): def addPlugin(self, plugin: Plugin): self.plugins.append(plugin) + @staticmethod + def clone_plugin(plugin: Plugin) -> Plugin: + parameters = [] + for param in plugin.parameters: + parameters.append(Parameter( + type=param.type, + name=param.name, + symbol=param.symbol, + mode=param.mode, + value=param.value, + min=param.minimum, + max=param.max, + )) + + return Plugin( + name=plugin.name, + uri=plugin.uri, + bypass=plugin.bypass, + channels=plugin.channels, + inputs=list(plugin.inputs), + outputs=list(plugin.outputs), + paramters=parameters, + ) + def changeParameter(self, pluginIndex: int, parameterIndex: int, value: float): try: diff --git a/gui/src/qwidgets/core.py b/gui/src/qwidgets/core.py index d720ece..56060a2 100644 --- a/gui/src/qwidgets/core.py +++ b/gui/src/qwidgets/core.py @@ -12,8 +12,8 @@ swap_plugins_final, swap_plugins_middle, swap_plugins_start ) from styles import ( - styles_window, color_foreground, - ScrollBarStyle, color_background, ControlDisplayStyle, + styles_window, color_foreground, styles_error, styles_label, + styles_sublabel, ScrollBarStyle, color_background, ControlDisplayStyle, BreadcrumbsBarStyle, styles_tabletitle, styles_tableitem ) from utils import config_dir, profiles_dir @@ -62,8 +62,8 @@ def __init__(self): self.breadcrumbs.setParent(self) # Create selection screen - self.start_screen = ProfileSelectWindow(self.launch_board) - self.stack.addWidget(self.start_screen) + self.start_screen = None + self.build_start_screen() self.board_window = None # Placeholder for later @@ -110,12 +110,23 @@ def launch_board(self, selected_profile): BreadcrumbsBar.navBackward() BreadcrumbsBar.navForward("view plugins") + def build_start_screen(self): + if self.start_screen is not None: + self.stack.removeWidget(self.start_screen) + self.start_screen.deleteLater() + self.start_screen = ProfileSelectWindow(self.launch_board) + self.stack.addWidget(self.start_screen) + def show_start_screen(self): """Switch back to the start screen.""" self.reset_modhost() patchThrough(modhost) # Bypass all before we load plugins + if self.board_window is not None: + self.stack.removeWidget(self.board_window) + self.board_window.deleteLater() + self.board_window = None + self.build_start_screen() self.stack.setCurrentWidget(self.start_screen) # Switch back - self.stack.removeWidget(self.board_window) self.start_screen.setFocus() BreadcrumbsBar.navBackward() ControlDisplay.setBind(RotaryEncoder.TOP, "select") @@ -144,6 +155,7 @@ def __init__( self.restart_callback = restart_callback self.profile_name = profile_name self.backgroundColor = color_background + self.available_plugins = PluginManager.all_plugins() self.param_page = 0 self.current = "plugins" @@ -355,7 +367,17 @@ def remove_current_plugin(self): self.pluginbox.scroll_group.drawItems() self.pluginbox.scroll_group.update_bar() - def add_plugin(self, plugin: Plugin): + def add_plugin(self, plugin_name: str): + template = next( + (plugin for plugin in self.available_plugins + if plugin.name == plugin_name), + None, + ) + if template is None: + print(f"Plugin {plugin_name} not found in all_plugins.json") + return + + plugin = PluginManager.clone_plugin(template) self.curItem().unhover() # add plugin to board visual n = len(self.plugins.plugins) @@ -525,16 +547,145 @@ def updateBypass(self, position: int, bypass): pass +class ProfileNameBuilder(QWidget): + ALPHABET = [chr(i) for i in range(ord('A'), ord('Z') + 1)] + + def __init__(self, on_save): + super().__init__() + self.on_save = on_save + self.setGeometry(0, 0, SCREEN_W, SCREEN_H) + self.current_index = 0 + self.current_name = "" + self.initUI() + self.setFocusPolicy(Qt.StrongFocus) + ControlDisplay.setBind(RotaryEncoder.TOP, "add letter") + ControlDisplay.setBind(RotaryEncoder.MIDDLE, "delete") + ControlDisplay.setBind(RotaryEncoder.BOTTOM, "save") + + def initUI(self): + self.title_label = QLabel("ADD NEW PROFILE", self) + self.title_label.setStyleSheet(styles_tabletitle) + self.title_label.adjustSize() + self.title_label.move( + self.width() // 2 - self.title_label.width() // 2, + int(self.height() * 0.2), + ) + + self.letter_label = QLabel(self.current_letter(), self) + self.letter_label.setStyleSheet(styles_label) + self.letter_label.adjustSize() + + self.name_label = QLabel(self.display_name(), self) + self.name_label.setStyleSheet(styles_sublabel) + self.name_label.adjustSize() + + self.instructions_label = QLabel( + "Q/E change letter • W add • S delete • X save", + self, + ) + self.instructions_label.setStyleSheet(styles_sublabel) + self.instructions_label.adjustSize() + + self.error_label = QLabel("", self) + self.error_label.setStyleSheet(styles_error) + self.error_label.adjustSize() + + self.position_labels() + + def position_labels(self): + self.letter_label.move( + self.width() // 2 - self.letter_label.width() // 2, + int(self.height() * 0.4), + ) + self.name_label.move( + self.width() // 2 - self.name_label.width() // 2, + int(self.height() * 0.6), + ) + self.instructions_label.move( + self.width() // 2 - self.instructions_label.width() // 2, + int(self.height() * 0.7), + ) + self.error_label.move( + self.width() // 2 - self.error_label.width() // 2, + int(self.height() * 0.75), + ) + + def display_name(self): + return self.current_name if self.current_name else "_" + + def current_letter(self): + return ProfileNameBuilder.ALPHABET[self.current_index] + + def update_display(self): + self.letter_label.setText(self.current_letter()) + self.letter_label.adjustSize() + self.name_label.setText(self.display_name()) + self.name_label.adjustSize() + self.position_labels() + self.clear_error() + self.repaint() + + def rotate_letter(self, direction: int): + self.current_index = (self.current_index + direction) % len(ProfileNameBuilder.ALPHABET) + self.update_display() + + def append_letter(self): + self.current_name += self.current_letter() + self.update_display() + + def delete_letter(self): + self.current_name = self.current_name[:-1] + self.update_display() + + def set_error(self, message: str): + self.error_label.setText(message) + self.error_label.adjustSize() + self.position_labels() + + def clear_error(self): + self.set_error("") + + def save_profile(self): + if not self.current_name: + self.set_error("Add at least one letter") + return + + profile_path = os.path.join(profiles_dir, f"{self.current_name}.json") + if os.path.exists(profile_path): + self.set_error("Profile already exists") + return + + self.on_save(self.current_name) + + def keyPressEvent(self, event): + key = event.key() + + match key: + case RotaryEncoder.TOP.keyLeft: + self.rotate_letter(-1) + case RotaryEncoder.TOP.keyRight: + self.rotate_letter(1) + case RotaryEncoder.TOP.keyPress: + self.append_letter() + case RotaryEncoder.MIDDLE.keyPress: + self.delete_letter() + case RotaryEncoder.BOTTOM.keyPress: + self.save_profile() + + class ProfileSelectWindow(FloatingWindow): + NEW_PROFILE_ID = "add new profile" + def __init__(self, callback): self.json_dir = profiles_dir self.json_files = self.get_json_files(profiles_dir) self.json_files.sort() self.callback = callback + self.builder_window = None # Floating window - dialog_items = [] + dialog_items = [DialogItem(ProfileSelectWindow.NEW_PROFILE_ID)] for p in self.json_files: item = DialogItem(p.replace(".json", "")) dialog_items.append(item) @@ -544,13 +695,26 @@ def __init__(self, callback): ControlDisplay.setBind(RotaryEncoder.BOTTOM, "delete") def keyPressEvent(self, event): - super().keyPressEvent(event) key = event.key() + if key in (self.encoder.keyPress, Qt.Key_R): + self.handle_selection() + return + + super().keyPressEvent(event) + match key: case RotaryEncoder.BOTTOM.keyPress: self.remove_profile() + def handle_selection(self): + cur_id = self.group.curItem().id + if cur_id == ProfileSelectWindow.NEW_PROFILE_ID: + self.start_profile_builder() + return + + self.callback(cur_id) + def remove_profile(self): # TODO: Prompt to confirm index = self.group.pos @@ -558,6 +722,8 @@ def remove_profile(self): n = len(items) if index >= n: return + if items[index].id == ProfileSelectWindow.NEW_PROFILE_ID: + return # Prevent last item from sticking around items[index].hide() items.pop(index) @@ -574,6 +740,23 @@ def remove_profile(self): super().update_continues() # TODO: actually delete profile + def start_profile_builder(self): + BreadcrumbsBar.navForward("new profile") + self.builder_window = ProfileNameBuilder(self.finish_new_profile) + MainWindow.stack.addWidget(self.builder_window) + MainWindow.stack.setCurrentWidget(self.builder_window) + self.builder_window.setFocus() + + def finish_new_profile(self, profile_name: str): + manager = PluginManager() + manager.save_to_profile(profile_name) + if self.builder_window is not None: + MainWindow.stack.removeWidget(self.builder_window) + self.builder_window.deleteLater() + self.builder_window = None + BreadcrumbsBar.navBackward() + self.callback(profile_name) + def get_json_files(self, directory): """Returns a list of all JSON files in the specified directory.""" return [f for f in os.listdir(directory) if f.endswith('.json')] @@ -659,7 +842,7 @@ def keyPressEvent(self, event): case RotaryEncoder.TOP.keyLeft: self.scroll_group.goPrev() case RotaryEncoder.TOP.keyPress: - self.add_callback(self.scroll_group.curItem().plugin) + self.add_callback(self.scroll_group.curItem().plugin.name) self.back_callback() case RotaryEncoder.TOP.keyRight: self.scroll_group.goNext() From 89446bca186f632e2a9dd2920d862984b148daf5 Mon Sep 17 00:00:00 2001 From: SillyGesture <69798195+Johnbob7@users.noreply.github.com> Date: Sat, 6 Dec 2025 16:42:48 -0500 Subject: [PATCH 4/8] Fix new profile builder stability and layout --- gui/src/qwidgets/core.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/gui/src/qwidgets/core.py b/gui/src/qwidgets/core.py index 56060a2..a407516 100644 --- a/gui/src/qwidgets/core.py +++ b/gui/src/qwidgets/core.py @@ -526,7 +526,8 @@ def initGroup(self): plugin = self.plugins.plugins[i] box = PluginBox(i, plugin, plugin.bypass) self.boxes.append(box) - self.boxes[n-1].isLast = True + if n > 0: + self.boxes[n-1].isLast = True self.add_plugin_box = AddPluginBox() self.boxes.append(self.add_plugin_box) self.scroll_bar = ScrollBar(RotaryEncoder.TOP) @@ -573,10 +574,14 @@ def initUI(self): self.letter_label = QLabel(self.current_letter(), self) self.letter_label.setStyleSheet(styles_label) + self.letter_label.setAlignment(Qt.AlignCenter) + self.letter_label.setFixedWidth(self.width()) self.letter_label.adjustSize() self.name_label = QLabel(self.display_name(), self) self.name_label.setStyleSheet(styles_sublabel) + self.name_label.setAlignment(Qt.AlignCenter) + self.name_label.setFixedWidth(self.width()) self.name_label.adjustSize() self.instructions_label = QLabel( @@ -584,31 +589,25 @@ def initUI(self): self, ) self.instructions_label.setStyleSheet(styles_sublabel) + self.instructions_label.setAlignment(Qt.AlignCenter) + self.instructions_label.setFixedWidth(self.width()) + self.instructions_label.setWordWrap(True) self.instructions_label.adjustSize() self.error_label = QLabel("", self) self.error_label.setStyleSheet(styles_error) + self.error_label.setAlignment(Qt.AlignCenter) + self.error_label.setFixedWidth(self.width()) + self.error_label.setWordWrap(True) self.error_label.adjustSize() self.position_labels() def position_labels(self): - self.letter_label.move( - self.width() // 2 - self.letter_label.width() // 2, - int(self.height() * 0.4), - ) - self.name_label.move( - self.width() // 2 - self.name_label.width() // 2, - int(self.height() * 0.6), - ) - self.instructions_label.move( - self.width() // 2 - self.instructions_label.width() // 2, - int(self.height() * 0.7), - ) - self.error_label.move( - self.width() // 2 - self.error_label.width() // 2, - int(self.height() * 0.75), - ) + self.letter_label.move(0, int(self.height() * 0.4)) + self.name_label.move(0, int(self.height() * 0.6)) + self.instructions_label.move(0, int(self.height() * 0.7)) + self.error_label.move(0, int(self.height() * 0.75)) def display_name(self): return self.current_name if self.current_name else "_" From d6ec9f5f76eb70f4db01d185afdab5b55eeeade6 Mon Sep 17 00:00:00 2001 From: SillyGesture <69798195+Johnbob7@users.noreply.github.com> Date: Sat, 6 Dec 2025 16:49:47 -0500 Subject: [PATCH 5/8] Remove profile builder instructions text --- gui/src/qwidgets/core.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gui/src/qwidgets/core.py b/gui/src/qwidgets/core.py index a407516..14f706d 100644 --- a/gui/src/qwidgets/core.py +++ b/gui/src/qwidgets/core.py @@ -584,10 +584,7 @@ def initUI(self): self.name_label.setFixedWidth(self.width()) self.name_label.adjustSize() - self.instructions_label = QLabel( - "Q/E change letter • W add • S delete • X save", - self, - ) + self.instructions_label = QLabel("", self) self.instructions_label.setStyleSheet(styles_sublabel) self.instructions_label.setAlignment(Qt.AlignCenter) self.instructions_label.setFixedWidth(self.width()) From 0c456b8c6d6db786bd4a2b929427cecd06f522c4 Mon Sep 17 00:00:00 2001 From: John Putnam Date: Sat, 6 Dec 2025 16:51:59 -0500 Subject: [PATCH 6/8] Delete core.py --- gui/src/qwidgets/core.py | 705 --------------------------------------- 1 file changed, 705 deletions(-) delete mode 100644 gui/src/qwidgets/core.py diff --git a/gui/src/qwidgets/core.py b/gui/src/qwidgets/core.py deleted file mode 100644 index d720ece..0000000 --- a/gui/src/qwidgets/core.py +++ /dev/null @@ -1,705 +0,0 @@ -"""Core widgets for the MultiFX GUI""" - -import os -from PyQt5.QtWidgets import QWidget, QStackedWidget, QVBoxLayout, QLabel -from PyQt5.QtGui import QPainter, QPen -from PyQt5.QtCore import Qt, QRect, QLine, QTimer -from plugin_manager import PluginManager, Plugin -from modhostmanager import ( - connectToModHost, setUpPlugins, setUpPatch, verifyParameters, - updateBypass, startModHost, patchThrough, removeFirst, removeMiddle, - removeLast, removeFinal, add_plugin_end, swap_plugins_end, - swap_plugins_final, swap_plugins_middle, swap_plugins_start -) -from styles import ( - styles_window, color_foreground, - ScrollBarStyle, color_background, ControlDisplayStyle, - BreadcrumbsBarStyle, styles_tabletitle, styles_tableitem -) -from utils import config_dir, profiles_dir -from qwidgets.parameter_widgets import ParameterPanel -from qwidgets.controls import ControlDisplay, RotaryEncoder -from qwidgets.graphics_utils import SCREEN_H, SCREEN_W -from qwidgets.navigation import ( - BreadcrumbsBar, ScrollBar, ScrollItem, ScrollGroup -) -from qwidgets.floating_window import FloatingWindow, DialogItem -from qwidgets.plugin_box import PluginBox, AddPluginBox -from offboard import try_save - -modhost = None - - -class MainWindow(QWidget): - stack: QStackedWidget = None - - def __init__(self): - super().__init__() - self.setGeometry(0, 0, SCREEN_W, SCREEN_H) - - self.stack = QStackedWidget(self) - MainWindow.stack = self.stack - self.layout = QVBoxLayout(self) - self.layout.addWidget(self.stack) - self.layout.setSpacing(0) - self.layout.setContentsMargins(0, 0, 0, 0) - self.setStyleSheet(styles_window) - - # Control Display - self.controlDisplay = ControlDisplay() - self.controlDisplay.move( - SCREEN_W - self.controlDisplay.width(), - SCREEN_H - self.controlDisplay.height() - ) - self.controlDisplay.setParent(self) - - # Breadcrumbs - self.breadcrumbs = BreadcrumbsBar("profile select") - self.breadcrumbs.move( - 0, - SCREEN_H - self.controlDisplay.height() - ) - self.breadcrumbs.setParent(self) - - # Create selection screen - self.start_screen = ProfileSelectWindow(self.launch_board) - self.stack.addWidget(self.start_screen) - - self.board_window = None # Placeholder for later - - self.reset_modhost() - patchThrough(modhost) # Bypass all before we load plugins - - self.show() - - def launch_board(self, selected_profile): - """Called when a JSON file is selected to load the board""" - # Loading the profile takes a little, here for feedback - BreadcrumbsBar.navForward("LOADING PLUGINS...") - self.repaint() - - board = PluginManager() - selected_json = selected_profile + ".json" - json_path = os.path.join(profiles_dir, selected_json) - board.initFromJSON(json_path) - - # Restart mod-host so we can change profiles. - # This is inefficient and can be improved, but it's easy. - self.reset_modhost() - setUpPlugins(modhost, board) - setUpPatch(modhost, board) - verifyParameters(modhost, board) - - # Remove old board window if it exists - if self.board_window is not None: - self.stack.removeWidget(self.board_window) - self.board_window.deleteLater() - - # Create new board window and add it to the stack - self.board_window = BoardWindow( - board, - mod_host_manager=modhost, - restart_callback=self.show_start_screen, - profile_name=selected_profile, - ) - self.stack.addWidget(self.board_window) - self.stack.setCurrentWidget(self.board_window) # Switch view - self.board_window.setFocus() - - # Update breadcrumbs - BreadcrumbsBar.navBackward() - BreadcrumbsBar.navForward("view plugins") - - def show_start_screen(self): - """Switch back to the start screen.""" - self.reset_modhost() - patchThrough(modhost) # Bypass all before we load plugins - self.stack.setCurrentWidget(self.start_screen) # Switch back - self.stack.removeWidget(self.board_window) - self.start_screen.setFocus() - BreadcrumbsBar.navBackward() - ControlDisplay.setBind(RotaryEncoder.TOP, "select") - ControlDisplay.setBind(RotaryEncoder.MIDDLE, "") - ControlDisplay.setBind(RotaryEncoder.BOTTOM, "delete") - - def reset_modhost(self): - """Starts or restarts modhost""" - startModHost() - - global modhost - modhost = connectToModHost() - if modhost is None: - print("Failed Closing...") - exit(1) - return - - -class BoardWindow(QWidget): - def __init__( - self, manager: PluginManager, mod_host_manager, restart_callback, - profile_name: str): - super().__init__() - self.plugins = manager - self.mod_host_manager = mod_host_manager - self.restart_callback = restart_callback - self.profile_name = profile_name - self.backgroundColor = color_background - - self.param_page = 0 - self.current = "plugins" - - self.setGeometry(0, 0, SCREEN_W, SCREEN_H) - - palette = self.palette() - palette.setColor(self.backgroundRole(), color_background) - self.setPalette(palette) - self.setAutoFillBackground(True) - - self.pluginbox = BoxOfPlugins(self.plugins) - self.pluginbox.setParent(self) - - self.setFocusPolicy(Qt.StrongFocus) - - def keyPressEvent(self, event): - key = event.key() - - match key: - # handle footswitches/bypass - case Qt.Key_R | RotaryEncoder.MIDDLE.keyPress: - self.changeBypass(self.curIndex()) - if self.curIndex() is None: - self.restart_callback() - case Qt.Key_F: - self.changeBypass(0) - case Qt.Key_G: - self.changeBypass(1) - case Qt.Key_H: - self.changeBypass(2) - case Qt.Key_J: - self.changeBypass(3) - case Qt.Key_K: - self.changeBypass(4) - case Qt.Key_L: - self.changeBypass(5) - # navigation - case RotaryEncoder.TOP.keyLeft: - self.pluginbox.scroll_group.goPrev() - case RotaryEncoder.TOP.keyPress: - cur = self.curItem() - if type(cur) is PluginBox: - self.show_param_screen(self.curItem()) - if type(cur) is AddPluginBox: - self.show_add_plugin_screen() - case RotaryEncoder.TOP.keyRight: - self.pluginbox.scroll_group.goNext() - case RotaryEncoder.MIDDLE.keyLeft: - if self.swap_plugins(-1): - self.pluginbox.scroll_group.goPrev() - case RotaryEncoder.MIDDLE.keyRight: - if self.swap_plugins(1): - self.pluginbox.scroll_group.goNext() - case RotaryEncoder.BOTTOM.keyPress: - cur = self.curItem() - if type(cur) is PluginBox: - self.remove_current_plugin() - if type(cur) is AddPluginBox: - BreadcrumbsBar.navForward("SAVING...") - BreadcrumbsBar.instance.repaint() - saved_locally = self.save_profile_to_disk() - saved_to_usb = False - if saved_locally: - saved_to_usb = try_save() - self.show_save_feedback(saved_locally and saved_to_usb) - - def swap_plugins(self, dist: int) -> bool: - index = self.curIndex() - # don't swap with add plugin widget - if index is None: - return False - n = len(self.pluginbox.scroll_group.items) - if index + dist < 0 or index + dist >= n-1: - return - items = self.pluginbox.boxes - - # swap in mod-host - # default, swap forward - instanceNumA = None - pluginA = None - instanceNumB = None - pluginB = None - beforePlugin = None - beforeInstanceNum = None - afterPlugin = None - afterInstanceNum = None - # when negative, swap backward - if dist < 0: - instanceNumB = items[index].instanceNum - pluginB = items[index].plugin - instanceNumA = items[index+dist].instanceNum - pluginA = items[index+dist].plugin - if index - 2 >= 0: - beforeInstanceNum = self.pluginbox.scroll_group.items[index-2].instanceNum - beforePlugin = self.pluginbox.scroll_group.items[index-2].plugin - if index + 1 < n-1: # adjust n for AppPluginBox - afterInstanceNum = self.pluginbox.scroll_group.items[index+1].instanceNum - afterPlugin = self.pluginbox.scroll_group.items[index+1].plugin - else: # swap forward - instanceNumA = items[index].instanceNum - pluginA = items[index].plugin - instanceNumB = items[index+dist].instanceNum - pluginB = items[index+dist].plugin - if index - 1 >= 0: - beforeInstanceNum = self.pluginbox.scroll_group.items[index-1].instanceNum - beforePlugin = self.pluginbox.scroll_group.items[index-1].plugin - if index + 2 < n-1: - afterInstanceNum = self.pluginbox.scroll_group.items[index+2].instanceNum - afterPlugin = self.pluginbox.scroll_group.items[index+2].plugin - # call mod-host manager based on what we determined - if beforeInstanceNum is None: - if afterInstanceNum is None: - swap_plugins_final( - modhost, - instanceNumA, - pluginA, - instanceNumB, - pluginB) - else: - swap_plugins_start( - modhost, - instanceNumA, - pluginA, - instanceNumB, - pluginB, - afterInstanceNum, - afterPlugin) - else: - if afterInstanceNum is None: - swap_plugins_end( - modhost, - instanceNumA, - pluginA, - instanceNumB, - pluginB, - beforeInstanceNum, - beforePlugin) - else: - swap_plugins_middle( - modhost, - instanceNumA, - pluginA, - instanceNumB, - pluginB, - beforeInstanceNum, - beforePlugin, - afterInstanceNum, - afterPlugin) - - # swap visually - temp = items[index] - other = items[index + dist] - if other.id == AddPluginBox.ID: - return - temp.unhover() - temp.index = index + dist - items[index+dist].index = index - items[index] = other - items[index+dist] = temp - temp.isLast = temp.index == n - 2 - other.isLast = other.index == n - 2 - - self.pluginbox.scroll_group.drawItems() - - return True - - def remove_current_plugin(self): - index = self.curIndex() - if index is None: # don't remove AddPluginBox - return - n = len(self.pluginbox.scroll_group.items) - if index >= n: - return - items = self.pluginbox.boxes - - # remove in mod-host - if n == 2: # use 2 because AddPluginBox will always stay - removeFinal(modhost, items[index].instanceNum) - elif index == n - 2: - removeLast(modhost, items[index].instanceNum, items[index].plugin, - items[index-1].instanceNum, items[index-1].plugin) - elif index == 0: - print(f"CALLING: removeFirst(modhost, {items[index].instanceNum}, {items[index].plugin}, {items[index+1].instanceNum}, {items[index+1].plugin})") - removeFirst(modhost, items[index].instanceNum, items[index].plugin, - items[index+1].instanceNum, items[index+1].plugin) - else: - removeMiddle(modhost, items[index].instanceNum, items[index].plugin, - items[index-1].instanceNum, items[index-1].plugin, - items[index+1].instanceNum, items[index+1].plugin) - - # remove and adjust visuals - # Prevent last item from sticking around - items[index].hide() - self.plugins.plugins.remove(items[index].plugin) - items.pop(index) - # reassign plugin-box indices - for i in range(index, n - 2): - items[i].index -= 1 - # skip scrollgroup logic when last item is removed - if n == 1: - return - # adjust to new position - if index < n - 1: - items[index].hover() - if self.pluginbox.scroll_group.window_top != 0 or index == n - 1: - self.pluginbox.scroll_group.goPrevEdge() - self.pluginbox.scroll_group.repaint() - self.pluginbox.scroll_group.drawItems() - self.pluginbox.scroll_group.update_bar() - - def add_plugin(self, plugin: Plugin): - self.curItem().unhover() - # add plugin to board visual - n = len(self.plugins.plugins) - self.plugins.plugins.append(plugin) - newbox = PluginBox(n, plugin, plugin.bypass) - newbox.setParent(self.pluginbox.scroll_group) - self.pluginbox.scroll_group.items.insert(n, newbox) - self.pluginbox.scroll_group.repaint() - self.pluginbox.scroll_group.drawItems() - self.pluginbox.scroll_group.update_bar() - # hover new item - newbox.hover() - self.pluginbox.scroll_group.pos = n - # add to mod-host - maxInstanceNum = n - for item in self.pluginbox.scroll_group.items: - if type(item) is AddPluginBox: - continue - if item.instanceNum > maxInstanceNum: - maxInstanceNum = item.instanceNum - add_plugin_end( - modhost, - maxInstanceNum, - plugin, - n-1, - self.plugins.plugins[n-1] - ) - - def paintEvent(self, event): - painter = QPainter(self) - - pen = QPen(Qt.black, 10) - painter.setPen(pen) - - rect = QRect(0, 0, self.width()-1, self.height()) - painter.drawRect(rect) - - def showEvent(self, event): - self.setFocus() - - def changeBypass(self, position): - if position is None: - return - try: - # get the value of bypass from the plugin our cursor is currently - # on - plugin = self.plugins.plugins[position] - bypass = plugin.bypass - - # flip value - bypass = bypass ^ 1 - plugin.bypass = bypass - updateBypass(self.mod_host_manager, position, plugin) - self.pluginbox.updateBypass(position, bypass) - except Exception as e: - print(e) - pass - - def show_param_screen(self, plugin: PluginBox): - """Switch to the param screen for a plugin. - Uses PluginBox for easier indexing. - """ - self.param_window = ParameterPanel(plugin, self.mod_host_manager, - self.back_to_board) - MainWindow.stack.addWidget(self.param_window) - MainWindow.stack.setCurrentWidget(self.param_window) - BreadcrumbsBar.navForward(plugin.id) - ControlDisplay.setBind(RotaryEncoder.TOP, "presets") - if len(plugin.plugin.parameters) > 3: - ControlDisplay.setBind(RotaryEncoder.MIDDLE, "next page") - else: - ControlDisplay.setBind(RotaryEncoder.MIDDLE, "") - ControlDisplay.setBind(RotaryEncoder.BOTTOM, "back") - - def show_add_plugin_screen(self): - self.add_plugin_window = PluginTable(self.plugins, self.back_to_board, - self.add_plugin) - MainWindow.stack.addWidget(self.add_plugin_window) - MainWindow.stack.setCurrentWidget(self.add_plugin_window) - BreadcrumbsBar.navForward("add plugin") - ControlDisplay.setBind(RotaryEncoder.TOP, "add plugin") - ControlDisplay.setBind(RotaryEncoder.MIDDLE, "") - ControlDisplay.setBind(RotaryEncoder.BOTTOM, "back") - - def back_to_board(self): - """Switches back to board screen from parameter screen""" - MainWindow.stack.setCurrentWidget(self) - if hasattr(self, 'param_window'): - MainWindow.stack.removeWidget(self.param_window) - del self.param_window - if hasattr(self, 'add_plugin_window'): - MainWindow.stack.removeWidget(self.add_plugin_window) - del self.add_plugin_window - BreadcrumbsBar.navBackward() - self.curItem().hover() - self.setFocusPolicy(Qt.StrongFocus) - - def curIndex(self) -> int | None: - if self.pluginbox.scroll_group.curItem().id == AddPluginBox.ID: - return None - return self.pluginbox.scroll_group.curItem().index - - def curItem(self) -> PluginBox | AddPluginBox: - index = self.curIndex() - if index is not None: - return self.pluginbox.scroll_group.items[self.curIndex()] - else: - return self.pluginbox.add_plugin_box - - def save_profile_to_disk(self) -> bool: - try: - self.plugins.save_to_profile(self.profile_name) - return True - except Exception as e: - print(f"Failed to save profile: {e}") - return False - - def show_save_feedback(self, success: bool): - message = "WRITE SUCCESSFUL" if success else "WRITE FAIL" - BreadcrumbsBar.crumbs[-1] = message - BreadcrumbsBar.instance.label.setText(BreadcrumbsBar.labelText()) - BreadcrumbsBar.instance.label.adjustSize() - QTimer.singleShot(1000, self.finish_save_feedback) - - def finish_save_feedback(self): - BreadcrumbsBar.navBackward() - - -class BoxOfPlugins(QWidget): - pluginsPerPage: int = 3 - - def __init__(self, plugins: PluginManager): - super().__init__() - self.setGeometry( - 0, 0, SCREEN_W, - int((1 - ControlDisplayStyle.REL_H) * SCREEN_H) - ) - self.plugins = plugins - self.initGroup() - - def initGroup(self): - # Create scroll group - self.boxes = [] - n = len(self.plugins.plugins) - for i in range(0, n): - plugin = self.plugins.plugins[i] - box = PluginBox(i, plugin, plugin.bypass) - self.boxes.append(box) - self.boxes[n-1].isLast = True - self.add_plugin_box = AddPluginBox() - self.boxes.append(self.add_plugin_box) - self.scroll_bar = ScrollBar(RotaryEncoder.TOP) - self.scroll_bar.setParent(self) - self.scroll_group = ScrollGroup( - BoxOfPlugins.pluginsPerPage, RotaryEncoder.TOP, - self.boxes, self.scroll_bar - ) - self.scroll_group.setParent(self) - self.scroll_group.update_bar() - self.scroll_bar.move(int((1 - ScrollBarStyle.REL_W) * self.width()), 0) - - def updateBypass(self, position: int, bypass): - try: - self.boxes[position].updateBypass(bypass) - except Exception as e: - print(e) - pass - - -class ProfileSelectWindow(FloatingWindow): - def __init__(self, callback): - self.json_dir = profiles_dir - self.json_files = self.get_json_files(profiles_dir) - self.json_files.sort() - - self.callback = callback - - # Floating window - dialog_items = [] - for p in self.json_files: - item = DialogItem(p.replace(".json", "")) - dialog_items.append(item) - scroll_group = ScrollGroup(4, RotaryEncoder.TOP, dialog_items) - super().__init__("SELECT PROFILE", scroll_group, RotaryEncoder.TOP, - callback) - ControlDisplay.setBind(RotaryEncoder.BOTTOM, "delete") - - def keyPressEvent(self, event): - super().keyPressEvent(event) - key = event.key() - - match key: - case RotaryEncoder.BOTTOM.keyPress: - self.remove_profile() - - def remove_profile(self): - # TODO: Prompt to confirm - index = self.group.pos - items = self.group.items - n = len(items) - if index >= n: - return - # Prevent last item from sticking around - items[index].hide() - items.pop(index) - # cleared group - if n == 1: - return - # adjust to new position - if index < n - 1: - items[index].hover() - if self.group.window_top != 0 or index == n - 1: - self.group.goPrevEdge() - self.group.repaint() - self.group.drawItems() - super().update_continues() - # TODO: actually delete profile - - def get_json_files(self, directory): - """Returns a list of all JSON files in the specified directory.""" - return [f for f in os.listdir(directory) if f.endswith('.json')] - - -class PluginTable(QWidget): - """Page for adding plugins to board""" - LINE_W = 3 - COL2_REL_W = 1/8 - PADDING = 8 - PAGE_SIZE = 8 - - def __init__(self, manager: PluginManager, back_callback, add_callback): - super().__init__() - self.setFixedSize( - SCREEN_W, - int((1 - BreadcrumbsBarStyle.REL_H) * SCREEN_H) - ) - self.plugins = manager - self.back_callback = back_callback - self.add_callback = add_callback - self.setFocusPolicy(Qt.StrongFocus) - self.initUI() - - def initUI(self): - self.col1title = QLabel("PLUGIN NAME", self) - self.col1title.setStyleSheet(styles_tabletitle) - self.col1title.adjustSize() - self.col1title.move(self.PADDING, self.PADDING) - self.col2title = QLabel("#", self) - self.col2title.setStyleSheet(styles_tabletitle) - self.col2title.adjustSize() - self.col2title.move( - int((1 - self.COL2_REL_W) * (1 - ScrollBarStyle.REL_W) * - self.width()) + 2*self.PADDING, self.PADDING - ) - self.start_y = 2*self.PADDING + self.col1title.height() - self.end_x = int((1 - ScrollBarStyle.REL_W) * self.width()) - - self.scroll_bar = ScrollBar(RotaryEncoder.TOP) - self.scroll_bar.setParent(self) - items = [] - # count number of plugins and place them in - plugincounts = {} - all_plugins = PluginManager.all_plugins() - for plugin in all_plugins: - plugincounts[plugin.name] = 0 - for plugin in self.plugins.plugins: - plugincounts[plugin.name] += 1 - for key, value in sorted(plugincounts.items(), key=lambda x: x): - actual_plugin = next((plugin for plugin in all_plugins if key == plugin.name), None) - if actual_plugin is not None: - items.append(PluginTableEntry(actual_plugin, value, self)) - self.scroll_group = ScrollGroup( - self.PAGE_SIZE, RotaryEncoder.TOP, items, self.scroll_bar - ) - self.scroll_group.setParent(self) - self.scroll_group.update_bar() - self.scroll_group.move(0, self.start_y) - self.scroll_group.setFixedSize( - self.end_x, - int((1 - BreadcrumbsBarStyle.REL_H) * SCREEN_H - self.start_y) - ) - self.scroll_bar.move(self.end_x, 0) - - def paintEvent(self, event): - painter = QPainter(self) - pen = QPen(color_foreground, self.LINE_W) - painter.setPen(pen) - y = self.start_y - bottom_line = QLine(0, y, self.end_x, y) - painter.drawLine(bottom_line) - x = self.col2title.x() - self.PADDING - seperator = QLine(x, 0, x, y) - painter.drawLine(seperator) - - def keyPressEvent(self, event): - key = event.key() - - match key: - case RotaryEncoder.BOTTOM.keyPress: - self.back_callback() - case RotaryEncoder.TOP.keyLeft: - self.scroll_group.goPrev() - case RotaryEncoder.TOP.keyPress: - self.add_callback(self.scroll_group.curItem().plugin) - self.back_callback() - case RotaryEncoder.TOP.keyRight: - self.scroll_group.goNext() - - -class PluginTableEntry(ScrollItem): - def __init__(self, plugin: Plugin, count: int, table: PluginTable): - super().__init__(plugin.name) - self.hover_fill = RotaryEncoder.TOP.color - self.unhover_fill = color_background - self.line_width = 0 # prevent drawing rectagle edges - self.table = table - self.plugin = plugin - self.setFixedSize( - table.end_x, - int((1 - BreadcrumbsBarStyle.REL_H) * SCREEN_H - table.start_y) // - PluginTable.PAGE_SIZE - ) - self.name_label = QLabel(plugin.name, self) - self.name_label.setStyleSheet(styles_tableitem) - self.name_label.adjustSize() - # pad and center - self.name_label.move(PluginTable.PADDING, - self.height()//2 - self.name_label.height()//2) - - self.count_label = QLabel(str(count), self) - self.count_label.setStyleSheet(styles_tableitem) - self.count_label.adjustSize() - # pad and center - self.count_label.move(self.table.col2title.x(), - self.height()//2 - self.count_label.height()//2) - - def paintEvent(self, event): - super().paintEvent(event) - painter = QPainter(self) - pen = QPen(color_foreground, self.table.LINE_W) - painter.setPen(pen) - y = self.height() - bottom_line = QLine(0, y, self.width(), y) - painter.drawLine(bottom_line) - x = self.table.col2title.x() - PluginTable.PADDING - seperator = QLine(x, 0, x, y) - painter.drawLine(seperator) From da89bab9050642a5910931db28d6b14c9711349f Mon Sep 17 00:00:00 2001 From: John Putnam Date: Sat, 6 Dec 2025 16:55:38 -0500 Subject: [PATCH 7/8] Create core.py --- gui/src/qwidgets/core.py | 705 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 705 insertions(+) create mode 100644 gui/src/qwidgets/core.py diff --git a/gui/src/qwidgets/core.py b/gui/src/qwidgets/core.py new file mode 100644 index 0000000..c182f35 --- /dev/null +++ b/gui/src/qwidgets/core.py @@ -0,0 +1,705 @@ +"""Core widgets for the MultiFX GUI""" + +import os +from PyQt5.QtWidgets import QWidget, QStackedWidget, QVBoxLayout, QLabel +from PyQt5.QtGui import QPainter, QPen +from PyQt5.QtCore import Qt, QRect, QLine, QTimer +from plugin_manager import PluginManager, Plugin +from modhostmanager import ( + connectToModHost, setUpPlugins, setUpPatch, verifyParameters, + updateBypass, startModHost, patchThrough, removeFirst, removeMiddle, + removeLast, removeFinal, add_plugin_end, swap_plugins_end, + swap_plugins_final, swap_plugins_middle, swap_plugins_start +) +from styles import ( + styles_window, color_foreground, + ScrollBarStyle, color_background, ControlDisplayStyle, + BreadcrumbsBarStyle, styles_tabletitle, styles_tableitem +) +from utils import config_dir, profiles_dir +from qwidgets.parameter_widgets import ParameterPanel +from qwidgets.controls import ControlDisplay, RotaryEncoder +from qwidgets.graphics_utils import SCREEN_H, SCREEN_W +from qwidgets.navigation import ( + BreadcrumbsBar, ScrollBar, ScrollItem, ScrollGroup +) +from qwidgets.floating_window import FloatingWindow, DialogItem +from qwidgets.plugin_box import PluginBox, AddPluginBox +from offboard import try_save + +modhost = None + + +class MainWindow(QWidget): + stack: QStackedWidget = None + + def __init__(self): + super().__init__() + self.setGeometry(0, 0, SCREEN_W, SCREEN_H) + + self.stack = QStackedWidget(self) + MainWindow.stack = self.stack + self.layout = QVBoxLayout(self) + self.layout.addWidget(self.stack) + self.layout.setSpacing(0) + self.layout.setContentsMargins(0, 0, 0, 0) + self.setStyleSheet(styles_window) + + # Control Display + self.controlDisplay = ControlDisplay() + self.controlDisplay.move( + SCREEN_W - self.controlDisplay.width(), + SCREEN_H - self.controlDisplay.height() + ) + self.controlDisplay.setParent(self) + + # Breadcrumbs + self.breadcrumbs = BreadcrumbsBar("profile select") + self.breadcrumbs.move( + 0, + SCREEN_H - self.controlDisplay.height() + ) + self.breadcrumbs.setParent(self) + + # Create selection screen + self.start_screen = ProfileSelectWindow(self.launch_board) + self.stack.addWidget(self.start_screen) + + self.board_window = None # Placeholder for later + + self.reset_modhost() + patchThrough(modhost) # Bypass all before we load plugins + + self.show() + + def launch_board(self, selected_profile): + """Called when a JSON file is selected to load the board""" + # Loading the profile takes a little, here for feedback + BreadcrumbsBar.navForward("LOADING PLUGINS...") + self.repaint() + + board = PluginManager() + selected_json = selected_profile + ".json" + json_path = os.path.join(profiles_dir, selected_json) + board.initFromJSON(json_path) + + # Restart mod-host so we can change profiles. + # This is inefficient and can be improved, but it's easy. + self.reset_modhost() + setUpPlugins(modhost, board) + setUpPatch(modhost, board) + verifyParameters(modhost, board) + + # Remove old board window if it exists + if self.board_window is not None: + self.stack.removeWidget(self.board_window) + self.board_window.deleteLater() + + # Create new board window and add it to the stack + self.board_window = BoardWindow( + board, + mod_host_manager=modhost, + restart_callback=self.show_start_screen, + profile_name=selected_profile, + ) + self.stack.addWidget(self.board_window) + self.stack.setCurrentWidget(self.board_window) # Switch view + self.board_window.setFocus() + + # Update breadcrumbs + BreadcrumbsBar.navBackward() + BreadcrumbsBar.navForward("view plugins") + + def show_start_screen(self): + """Switch back to the start screen.""" + self.reset_modhost() + patchThrough(modhost) # Bypass all before we load plugins + self.stack.setCurrentWidget(self.start_screen) # Switch back + self.stack.removeWidget(self.board_window) + self.start_screen.setFocus() + BreadcrumbsBar.navBackward() + ControlDisplay.setBind(RotaryEncoder.TOP, "select") + ControlDisplay.setBind(RotaryEncoder.MIDDLE, "") + ControlDisplay.setBind(RotaryEncoder.BOTTOM, "delete") + + def reset_modhost(self): + """Starts or restarts modhost""" + startModHost() + + global modhost + modhost = connectToModHost() + if modhost is None: + print("Failed Closing...") + exit(1) + return + + +class BoardWindow(QWidget): + def __init__( + self, manager: PluginManager, mod_host_manager, restart_callback, + profile_name: str): + super().__init__() + self.plugins = manager + self.mod_host_manager = mod_host_manager + self.restart_callback = restart_callback + self.profile_name = profile_name + self.backgroundColor = color_background + + self.param_page = 0 + self.current = "plugins" + + self.setGeometry(0, 0, SCREEN_W, SCREEN_H) + + palette = self.palette() + palette.setColor(self.backgroundRole(), color_background) + self.setPalette(palette) + self.setAutoFillBackground(True) + + self.pluginbox = BoxOfPlugins(self.plugins) + self.pluginbox.setParent(self) + + self.setFocusPolicy(Qt.StrongFocus) + + def keyPressEvent(self, event): + key = event.key() + + match key: + # handle footswitches/bypass + case Qt.Key_R | RotaryEncoder.MIDDLE.keyPress: + self.changeBypass(self.curIndex()) + if self.curIndex() is None: + self.restart_callback() + case Qt.Key_F: + self.changeBypass(0) + case Qt.Key_G: + self.changeBypass(1) + case Qt.Key_H: + self.changeBypass(2) + case Qt.Key_J: + self.changeBypass(3) + case Qt.Key_K: + self.changeBypass(4) + case Qt.Key_L: + self.changeBypass(5) + # navigation + case RotaryEncoder.TOP.keyLeft: + self.pluginbox.scroll_group.goPrev() + case RotaryEncoder.TOP.keyPress: + cur = self.curItem() + if type(cur) is PluginBox: + self.show_param_screen(self.curItem()) + if type(cur) is AddPluginBox: + self.show_add_plugin_screen() + case RotaryEncoder.TOP.keyRight: + self.pluginbox.scroll_group.goNext() + case RotaryEncoder.MIDDLE.keyLeft: + if self.swap_plugins(-1): + self.pluginbox.scroll_group.goPrev() + case RotaryEncoder.MIDDLE.keyRight: + if self.swap_plugins(1): + self.pluginbox.scroll_group.goNext() + case RotaryEncoder.BOTTOM.keyPress: + cur = self.curItem() + if type(cur) is PluginBox: + self.remove_current_plugin() + if type(cur) is AddPluginBox: + BreadcrumbsBar.navForward("SAVING...") + BreadcrumbsBar.instance.repaint() + saved_locally = self.save_profile_to_disk() + saved_to_usb = False + if saved_locally: + saved_to_usb = try_save() + self.show_save_feedback(saved_locally and saved_to_usb) + + def swap_plugins(self, dist: int) -> bool: + index = self.curIndex() + # don't swap with add plugin widget + if index is None: + return False + n = len(self.pluginbox.scroll_group.items) + if index + dist < 0 or index + dist >= n-1: + return + items = self.pluginbox.boxes + + # swap in mod-host + # default, swap forward + instanceNumA = None + pluginA = None + instanceNumB = None + pluginB = None + beforePlugin = None + beforeInstanceNum = None + afterPlugin = None + afterInstanceNum = None + # when negative, swap backward + if dist < 0: + instanceNumB = items[index].instanceNum + pluginB = items[index].plugin + instanceNumA = items[index+dist].instanceNum + pluginA = items[index+dist].plugin + if index - 2 >= 0: + beforeInstanceNum = self.pluginbox.scroll_group.items[index-2].instanceNum + beforePlugin = self.pluginbox.scroll_group.items[index-2].plugin + if index + 1 < n-1: # adjust n for AppPluginBox + afterInstanceNum = self.pluginbox.scroll_group.items[index+1].instanceNum + afterPlugin = self.pluginbox.scroll_group.items[index+1].plugin + else: # swap forward + instanceNumA = items[index].instanceNum + pluginA = items[index].plugin + instanceNumB = items[index+dist].instanceNum + pluginB = items[index+dist].plugin + if index - 1 >= 0: + beforeInstanceNum = self.pluginbox.scroll_group.items[index-1].instanceNum + beforePlugin = self.pluginbox.scroll_group.items[index-1].plugin + if index + 2 < n-1: + afterInstanceNum = self.pluginbox.scroll_group.items[index+2].instanceNum + afterPlugin = self.pluginbox.scroll_group.items[index+2].plugin + # call mod-host manager based on what we determined + if beforeInstanceNum is None: + if afterInstanceNum is None: + swap_plugins_final( + modhost, + instanceNumA, + pluginA, + instanceNumB, + pluginB) + else: + swap_plugins_start( + modhost, + instanceNumA, + pluginA, + instanceNumB, + pluginB, + afterInstanceNum, + afterPlugin) + else: + if afterInstanceNum is None: + swap_plugins_end( + modhost, + instanceNumA, + pluginA, + instanceNumB, + pluginB, + beforeInstanceNum, + beforePlugin) + else: + swap_plugins_middle( + modhost, + instanceNumA, + pluginA, + instanceNumB, + pluginB, + beforeInstanceNum, + beforePlugin, + afterInstanceNum, + afterPlugin) + + # swap visually + temp = items[index] + other = items[index + dist] + if other.id == AddPluginBox.ID: + return + temp.unhover() + temp.index = index + dist + items[index+dist].index = index + items[index] = other + items[index+dist] = temp + temp.isLast = temp.index == n - 2 + other.isLast = other.index == n - 2 + + self.pluginbox.scroll_group.drawItems() + + return True + + def remove_current_plugin(self): + index = self.curIndex() + if index is None: # don't remove AddPluginBox + return + n = len(self.pluginbox.scroll_group.items) + if index >= n: + return + items = self.pluginbox.boxes + + # remove in mod-host + if n == 2: # use 2 because AddPluginBox will always stay + removeFinal(modhost, items[index].instanceNum) + elif index == n - 2: + removeLast(modhost, items[index].instanceNum, items[index].plugin, + items[index-1].instanceNum, items[index-1].plugin) + elif index == 0: + print(f"CALLING: removeFirst(modhost, {items[index].instanceNum}, {items[index].plugin}, {items[index+1].instanceNum}, {items[index+1].plugin})") + removeFirst(modhost, items[index].instanceNum, items[index].plugin, + items[index+1].instanceNum, items[index+1].plugin) + else: + removeMiddle(modhost, items[index].instanceNum, items[index].plugin, + items[index-1].instanceNum, items[index-1].plugin, + items[index+1].instanceNum, items[index+1].plugin) + + # remove and adjust visuals + # Prevent last item from sticking around + items[index].hide() + self.plugins.plugins.remove(items[index].plugin) + items.pop(index) + # reassign plugin-box indices + for i in range(index, n - 2): + items[i].index -= 1 + # skip scrollgroup logic when last item is removed + if n == 1: + return + # adjust to new position + if index < n - 1: + items[index].hover() + if self.pluginbox.scroll_group.window_top != 0 or index == n - 1: + self.pluginbox.scroll_group.goPrevEdge() + self.pluginbox.scroll_group.repaint() + self.pluginbox.scroll_group.drawItems() + self.pluginbox.scroll_group.update_bar() + + def add_plugin(self, plugin: Plugin): + self.curItem().unhover() + # add plugin to board visual + n = len(self.plugins.plugins) + self.plugins.plugins.append(plugin) + newbox = PluginBox(n, plugin, plugin.bypass) + newbox.setParent(self.pluginbox.scroll_group) + self.pluginbox.scroll_group.items.insert(n, newbox) + self.pluginbox.scroll_group.repaint() + self.pluginbox.scroll_group.drawItems() + self.pluginbox.scroll_group.update_bar() + # hover new item + newbox.hover() + self.pluginbox.scroll_group.pos = n + # add to mod-host + maxInstanceNum = n + for item in self.pluginbox.scroll_group.items: + if type(item) is AddPluginBox: + continue + if item.instanceNum > maxInstanceNum: + maxInstanceNum = item.instanceNum + add_plugin_end( + modhost, + maxInstanceNum, + plugin, + n-1, + self.plugins.plugins[n-1] + ) + + def paintEvent(self, event): + painter = QPainter(self) + + pen = QPen(Qt.black, 10) + painter.setPen(pen) + + rect = QRect(0, 0, self.width()-1, self.height()) + painter.drawRect(rect) + + def showEvent(self, event): + self.setFocus() + + def changeBypass(self, position): + if position is None: + return + try: + # get the value of bypass from the plugin our cursor is currently + # on + plugin = self.plugins.plugins[position] + bypass = plugin.bypass + + # flip value + bypass = bypass ^ 1 + plugin.bypass = bypass + updateBypass(self.mod_host_manager, position, plugin) + self.pluginbox.updateBypass(position, bypass) + except Exception as e: + print(e) + pass + + def show_param_screen(self, plugin: PluginBox): + """Switch to the param screen for a plugin. + Uses PluginBox for easier indexing. + """ + self.param_window = ParameterPanel(plugin, self.mod_host_manager, + self.back_to_board) + MainWindow.stack.addWidget(self.param_window) + MainWindow.stack.setCurrentWidget(self.param_window) + BreadcrumbsBar.navForward(plugin.id) + ControlDisplay.setBind(RotaryEncoder.TOP, "presets") + if len(plugin.plugin.parameters) > 3: + ControlDisplay.setBind(RotaryEncoder.MIDDLE, "next page") + else: + ControlDisplay.setBind(RotaryEncoder.MIDDLE, "") + ControlDisplay.setBind(RotaryEncoder.BOTTOM, "back") + + def show_add_plugin_screen(self): + self.add_plugin_window = PluginTable(self.plugins, self.back_to_board, + self.add_plugin) + MainWindow.stack.addWidget(self.add_plugin_window) + MainWindow.stack.setCurrentWidget(self.add_plugin_window) + BreadcrumbsBar.navForward("add plugin") + ControlDisplay.setBind(RotaryEncoder.TOP, "add plugin") + ControlDisplay.setBind(RotaryEncoder.MIDDLE, "") + ControlDisplay.setBind(RotaryEncoder.BOTTOM, "back") + + def back_to_board(self): + """Switches back to board screen from parameter screen""" + MainWindow.stack.setCurrentWidget(self) + if hasattr(self, 'param_window'): + MainWindow.stack.removeWidget(self.param_window) + del self.param_window + if hasattr(self, 'add_plugin_window'): + MainWindow.stack.removeWidget(self.add_plugin_window) + del self.add_plugin_window + BreadcrumbsBar.navBackward() + self.curItem().hover() + self.setFocusPolicy(Qt.StrongFocus) + + def curIndex(self) -> int | None: + if self.pluginbox.scroll_group.curItem().id == AddPluginBox.ID: + return None + return self.pluginbox.scroll_group.curItem().index + + def curItem(self) -> PluginBox | AddPluginBox: + index = self.curIndex() + if index is not None: + return self.pluginbox.scroll_group.items[self.curIndex()] + else: + return self.pluginbox.add_plugin_box + + def save_profile_to_disk(self) -> bool: + try: + self.plugins.save_to_profile(self.profile_name) + return True + except Exception as e: + print(f"Failed to save profile: {e}") + return False + + def show_save_feedback(self, success: bool): + message = "WRITE SUCCESSFUL" if success else "WRITE FAIL" + BreadcrumbsBar.crumbs[-1] = message + BreadcrumbsBar.instance.label.setText(BreadcrumbsBar.labelText()) + BreadcrumbsBar.instance.label.adjustSize() + QTimer.singleShot(1000, self.finish_save_feedback) + + def finish_save_feedback(self): + BreadcrumbsBar.navBackward() + + +class BoxOfPlugins(QWidget): + pluginsPerPage: int = 3 + + def __init__(self, plugins: PluginManager): + super().__init__() + self.setGeometry( + 0, 0, SCREEN_W, + int((1 - ControlDisplayStyle.REL_H) * SCREEN_H) + ) + self.plugins = plugins + self.initGroup() + + def initGroup(self): + # Create scroll group + self.boxes = [] + n = len(self.plugins.plugins) + for i in range(0, n): + plugin = self.plugins.plugins[i] + box = PluginBox(i, plugin, plugin.bypass) + self.boxes.append(box) + self.boxes[n-1].isLast = True + self.add_plugin_box = AddPluginBox() + self.boxes.append(self.add_plugin_box) + self.scroll_bar = ScrollBar(RotaryEncoder.TOP) + self.scroll_bar.setParent(self) + self.scroll_group = ScrollGroup( + BoxOfPlugins.pluginsPerPage, RotaryEncoder.TOP, + self.boxes, self.scroll_bar + ) + self.scroll_group.setParent(self) + self.scroll_group.update_bar() + self.scroll_bar.move(int((1 - ScrollBarStyle.REL_W) * self.width()), 0) + + def updateBypass(self, position: int, bypass): + try: + self.boxes[position].updateBypass(bypass) + except Exception as e: + print(e) + pass + + +class ProfileSelectWindow(FloatingWindow): + def __init__(self, callback): + self.json_dir = profiles_dir + self.json_files = self.get_json_files(profiles_dir) + self.json_files.sort() + + self.callback = callback + + # Floating window + dialog_items = [] + for p in self.json_files: + item = DialogItem(p.replace(".json", "")) + dialog_items.append(item) + scroll_group = ScrollGroup(4, RotaryEncoder.TOP, dialog_items) + super().__init__("SELECT PROFILE", scroll_group, RotaryEncoder.TOP, + callback) + ControlDisplay.setBind(RotaryEncoder.BOTTOM, "delete") + + def keyPressEvent(self, event): + super().keyPressEvent(event) + key = event.key() + + match key: + case RotaryEncoder.BOTTOM.keyPress: + self.remove_profile() + + def remove_profile(self): + # TODO: Prompt to confirm + index = self.group.pos + items = self.group.items + n = len(items) + if index >= n: + return + # Prevent last item from sticking around + items[index].hide() + items.pop(index) + # cleared group + if n == 1: + return + # adjust to new position + if index < n - 1: + items[index].hover() + if self.group.window_top != 0 or index == n - 1: + self.group.goPrevEdge() + self.group.repaint() + self.group.drawItems() + super().update_continues() + # TODO: actually delete profile + + def get_json_files(self, directory): + """Returns a list of all JSON files in the specified directory.""" + return [f for f in os.listdir(directory) if f.endswith('.json')] + + +class PluginTable(QWidget): + """Page for adding plugins to board""" + LINE_W = 3 + COL2_REL_W = 1/8 + PADDING = 8 + PAGE_SIZE = 8 + + def __init__(self, manager: PluginManager, back_callback, add_callback): + super().__init__() + self.setFixedSize( + SCREEN_W, + int((1 - BreadcrumbsBarStyle.REL_H) * SCREEN_H) + ) + self.plugins = manager + self.back_callback = back_callback + self.add_callback = add_callback + self.setFocusPolicy(Qt.StrongFocus) + self.initUI() + + def initUI(self): + self.col1title = QLabel("PLUGIN NAME", self) + self.col1title.setStyleSheet(styles_tabletitle) + self.col1title.adjustSize() + self.col1title.move(self.PADDING, self.PADDING) + self.col2title = QLabel("#", self) + self.col2title.setStyleSheet(styles_tabletitle) + self.col2title.adjustSize() + self.col2title.move( + int((1 - self.COL2_REL_W) * (1 - ScrollBarStyle.REL_W) * + self.width()) + 2*self.PADDING, self.PADDING + ) + self.start_y = 2*self.PADDING + self.col1title.height() + self.end_x = int((1 - ScrollBarStyle.REL_W) * self.width()) + + self.scroll_bar = ScrollBar(RotaryEncoder.TOP) + self.scroll_bar.setParent(self) + items = [] + # count number of plugins and place them in + plugincounts = {} + all_plugins = PluginManager.all_plugins() + for plugin in all_plugins: + plugincounts[plugin.name] = 0 + for plugin in self.plugins.plugins: + plugincounts[plugin.name] += 1 + for key, value in sorted(plugincounts.items(), key=lambda x: x): + actual_plugin = next((plugin for plugin in all_plugins if key == plugin.name), None) + if actual_plugin is not None: + items.append(PluginTableEntry(actual_plugin, value, self)) + self.scroll_group = ScrollGroup( + self.PAGE_SIZE, RotaryEncoder.TOP, items, self.scroll_bar + ) + self.scroll_group.setParent(self) + self.scroll_group.update_bar() + self.scroll_group.move(0, self.start_y) + self.scroll_group.setFixedSize( + self.end_x, + int((1 - BreadcrumbsBarStyle.REL_H) * SCREEN_H - self.start_y) + ) + self.scroll_bar.move(self.end_x, 0) + + def paintEvent(self, event): + painter = QPainter(self) + pen = QPen(color_foreground, self.LINE_W) + painter.setPen(pen) + y = self.start_y + bottom_line = QLine(0, y, self.end_x, y) + painter.drawLine(bottom_line) + x = self.col2title.x() - self.PADDING + seperator = QLine(x, 0, x, y) + painter.drawLine(seperator) + + def keyPressEvent(self, event): + key = event.key() + + match key: + case RotaryEncoder.BOTTOM.keyPress: + self.back_callback() + case RotaryEncoder.TOP.keyLeft: + self.scroll_group.goPrev() + case RotaryEncoder.TOP.keyPress: + self.add_callback(self.scroll_group.curItem().plugin) + self.back_callback() + case RotaryEncoder.TOP.keyRight: + self.scroll_group.goNext() + + +class PluginTableEntry(ScrollItem): + def __init__(self, plugin: Plugin, count: int, table: PluginTable): + super().__init__(plugin.name) + self.hover_fill = RotaryEncoder.TOP.color + self.unhover_fill = color_background + self.line_width = 0 # prevent drawing rectagle edges + self.table = table + self.plugin = plugin + self.setFixedSize( + table.end_x, + int((1 - BreadcrumbsBarStyle.REL_H) * SCREEN_H - table.start_y) // + PluginTable.PAGE_SIZE + ) + self.name_label = QLabel(plugin.name, self) + self.name_label.setStyleSheet(styles_tableitem) + self.name_label.adjustSize() + # pad and center + self.name_label.move(PluginTable.PADDING, + self.height()//2 - self.name_label.height()//2) + + self.count_label = QLabel(str(count), self) + self.count_label.setStyleSheet(styles_tableitem) + self.count_label.adjustSize() + # pad and center + self.count_label.move(self.table.col2title.x(), + self.height()//2 - self.count_label.height()//2) + + def paintEvent(self, event): + super().paintEvent(event) + painter = QPainter(self) + pen = QPen(color_foreground, self.table.LINE_W) + painter.setPen(pen) + y = self.height() + bottom_line = QLine(0, y, self.width(), y) + painter.drawLine(bottom_line) + x = self.table.col2title.x() - PluginTable.PADDING + seperator = QLine(x, 0, x, y) + painter.drawLine(seperator) \ No newline at end of file From 9c8f493f01a2e0ad94da12a80abd5f9c1456f893 Mon Sep 17 00:00:00 2001 From: John Putnam Date: Sat, 6 Dec 2025 17:02:40 -0500 Subject: [PATCH 8/8] Update core.py --- gui/src/qwidgets/core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gui/src/qwidgets/core.py b/gui/src/qwidgets/core.py index 4acdac9..f0c5e2d 100644 --- a/gui/src/qwidgets/core.py +++ b/gui/src/qwidgets/core.py @@ -671,6 +671,7 @@ def keyPressEvent(self, event): class ProfileSelectWindow(FloatingWindow): NEW_PROFILE_ID = "add new profile" + HIDDEN_PROFILES = {"all_plugins.json"} def __init__(self, callback): self.json_dir = profiles_dir @@ -755,7 +756,10 @@ def finish_new_profile(self, profile_name: str): def get_json_files(self, directory): """Returns a list of all JSON files in the specified directory.""" - return [f for f in os.listdir(directory) if f.endswith('.json')] + return [ + f for f in os.listdir(directory) + if f.endswith('.json') and f not in self.HIDDEN_PROFILES + ] class PluginTable(QWidget): @@ -881,4 +885,4 @@ def paintEvent(self, event): painter.drawLine(bottom_line) x = self.table.col2title.x() - PluginTable.PADDING seperator = QLine(x, 0, x, y) - painter.drawLine(seperator) \ No newline at end of file + painter.drawLine(seperator)