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 b12615b..72dfdd6 100644 --- a/gui/src/plugin_manager.py +++ b/gui/src/plugin_manager.py @@ -1,6 +1,7 @@ import json import os -from utils import config_dir + +from utils import profiles_dir class Parameter(): @@ -76,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: @@ -93,7 +118,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 @@ -126,7 +151,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: @@ -149,3 +176,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(profiles_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 2c982a8..f0c5e2d 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, @@ -12,11 +12,11 @@ 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 +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 @@ -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 @@ -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. @@ -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 @@ -109,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") @@ -135,12 +147,15 @@ 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.available_plugins = PluginManager.all_plugins() self.param_page = 0 self.current = "plugins" @@ -202,8 +217,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() @@ -349,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) @@ -459,6 +487,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 @@ -480,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) @@ -501,16 +548,141 @@ 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.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("", 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(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 "_" + + 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" + HIDDEN_PROFILES = {"all_plugins.json"} + 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 + 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) @@ -520,13 +692,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 @@ -534,6 +719,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) @@ -550,9 +737,29 @@ 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')] + return [ + f for f in os.listdir(directory) + if f.endswith('.json') and f not in self.HIDDEN_PROFILES + ] class PluginTable(QWidget): @@ -635,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() 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")