Proteus

Guitar amp and pedal capture plugin using neural networks
Log | Files | Refs | Submodules | README

commit aa694ca0c79a427eb876a437028864f19d696b4c
parent d663c5783b3abc581ee3ab1d65d1552d4c9a9cf9
Author: Keith <smartguitarml@gmail.com>
Date:   Tue, 29 Nov 2022 18:53:37 -0600

Added 3 band EQ and default IR functionality

Diffstat:
MCMakeLists.txt | 2+-
Minstallers/linux/build_deb.sh | 2+-
Mresources/CMakeLists.txt | 3+++
Aresources/cab_switch_off.png | 0
Aresources/cab_switch_on.png | 0
Aresources/default_ir.wav | 0
Msrc/CMakeLists.txt | 3+++
Asrc/CabSim.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Eq4Band.cpp | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Eq4Band.h | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PluginEditor.cpp | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/PluginEditor.h | 8++++++++
Msrc/PluginProcessor.cpp | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PluginProcessor.h | 20++++++++++++++++++++
14 files changed, 369 insertions(+), 4 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.12" CACHE STRING "Minimum OS X deployment target") -project(Proteus VERSION 1.1.0) +project(Proteus VERSION 1.2.0) set(CMAKE_CXX_STANDARD 17) diff --git a/installers/linux/build_deb.sh b/installers/linux/build_deb.sh @@ -3,7 +3,7 @@ # Set the app name and version here app_name=Proteus -version=1.1 +version=1.2 # 1. Create the package directory structure and control file diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt @@ -5,6 +5,9 @@ juce_add_binary_data(BinaryData SOURCES background_on.jpg background_on_blue.jpg background_off.jpg + default_ir.wav + cab_switch_off.png + cab_switch_on.png ) diff --git a/resources/cab_switch_off.png b/resources/cab_switch_off.png Binary files differ. diff --git a/resources/cab_switch_on.png b/resources/cab_switch_on.png Binary files differ. diff --git a/resources/default_ir.wav b/resources/default_ir.wav Binary files differ. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt @@ -9,6 +9,9 @@ target_sources(Proteus PRIVATE PluginProcessor.h RTNeuralLSTM.cpp RTNeuralLSTM.h + CabSim.h + Eq4Band.cpp + Eq4Band.h ) #target_precompile_headers(Proteus PRIVATE pch.h) diff --git a/src/CabSim.h b/src/CabSim.h @@ -0,0 +1,59 @@ +/* + ============================================================================== + CabSim + ============================================================================== +*/ +#include "../JuceLibraryCode/JuceHeader.h" + +#pragma once + +//============================================================================== +class CabSim +{ +public: + //============================================================================== + CabSim() = default; + + //============================================================================== + void prepare (const juce::dsp::ProcessSpec& spec) + { + processorChain.prepare(spec); + } + + //============================================================================== + template <typename ProcessContext> + void process(const ProcessContext& context) noexcept + { + processorChain.process(context); + } + + //============================================================================== + void reset() noexcept + { + processorChain.reset(); + } + + void load(const void* sourceData, size_t sourceDataSize) noexcept + { + auto& convolution = processorChain.template get<convolutionIndex>(); + //loadImpulseResponse(const void* sourceData, size_t sourceDataSize, Stereo isStereo, Trim requiresTrimming, size_t size, Normalise requiresNormalisation = Normalise::yes) + convolution.loadImpulseResponse(sourceData, sourceDataSize, + juce::dsp::Convolution::Stereo::yes, + juce::dsp::Convolution::Trim::no, + 0); + //convolution.loadImpulseResponse(irFile, + // juce::dsp::Convolution::Stereo::yes, + // juce::dsp::Convolution::Trim::no, + // 0); // Set to 0 to use the full size of IR with no trimming + } + +private: + enum + { + convolutionIndex + }; + + juce::dsp::ProcessorChain<juce::dsp::Convolution> processorChain; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CabSim) +}; +\ No newline at end of file diff --git a/src/Eq4Band.cpp b/src/Eq4Band.cpp @@ -0,0 +1,76 @@ +/* + ============================================================================== + + Eq4Band + + ============================================================================== +*/ + +#include "Eq4Band.h" + +Eq4Band::Eq4Band() +{ + setParameters(0.0, 0.0, 0.0, 0.0); +} + +void Eq4Band::process (const float* inData, float* outData, + MidiBuffer& midiMessages, + const int numSamples, + const int numInputChannels, + const int sampleRate) +{ + // Reset params if new sampleRate detected + if (srate != sampleRate) { + srate = sampleRate; + resetSampleRate(); + } + for (int sample = 0; sample < numSamples; ++sample) { + spl0 = inData[sample]; + s0 = spl0; + low0 = (tmplMID = a0MID * s0 - b1MID * tmplMID + cDenorm); + spl0 = (tmplLOW = a0LOW * low0 - b1LOW * tmplLOW + cDenorm); + lowS0 = low0 - spl0; + hi0 = s0 - low0; + midS0 = (tmplHI = a0HI * hi0 - b1HI * tmplHI + cDenorm); + highS0 = hi0 - midS0; + spl0 = (spl0 * lVol + lowS0 * lmVol + midS0 * hmVol + highS0 * hVol);// * outVol; + + outData[sample] = spl0; + } +} + +void Eq4Band::setParameters(float bass_slider, float mid_slider, float treble_slider, float presence_slider) +{ + lVol = exp(bass_slider / cAmpDB); + lmVol = exp(mid_slider / cAmpDB); + hmVol = exp(treble_slider / cAmpDB); + hVol = exp(presence_slider / cAmpDB); + outVol = exp(0.0 / cAmpDB); + + xHI = exp(-2.0 * pi * treble_frequency / srate); + a0HI = 1.0 - xHI; + b1HI = -xHI; + + xMID = exp(-2.0 * pi * mid_frequency / srate); + a0MID = 1.0 - xMID; + b1MID = -xMID; + + xLOW = exp(-2.0 * pi * bass_frequency / srate); + a0LOW = 1.0 - xLOW; + b1LOW = -xLOW; +} + +void Eq4Band::resetSampleRate() +{ + xHI = exp(-2.0 * pi * treble_frequency / srate); + a0HI = 1.0 - xHI; + b1HI = -xHI; + + xMID = exp(-2.0 * pi * mid_frequency / srate); + a0MID = 1.0 - xMID; + b1MID = -xMID; + + xLOW = exp(-2.0 * pi * bass_frequency / srate); + a0LOW = 1.0 - xLOW; + b1LOW = -xLOW; +} +\ No newline at end of file diff --git a/src/Eq4Band.h b/src/Eq4Band.h @@ -0,0 +1,72 @@ +/* + ============================================================================== + + Eq4Band + + ============================================================================== +*/ + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" + + +//============================================================================== + +class Eq4Band +{ +public: + Eq4Band(); + void process (const float* inData, float* outData, MidiBuffer& midiMessages, const int numSamples, const int numInputChannels, const int sampleRate); + void setParameters(float bass_slider, float mid_slider, float treble_slider, float presence_slider); + void resetSampleRate(); + +private: + // Tone Knob related variables + float cDenorm = 10e-30; + float cAmpDB = 8.65617025; + + int bass_frequency = 180; + int mid_frequency = 1000; + int treble_frequency = 4000; + //int bass_frequency = 200; + //int mid_frequency = 2000; + //int treble_frequency = 5000; + // + //int presence_frequency = 5500; + + int srate = 44100; // Set default + + float pi = 3.1415926; + + float outVol; + float xHI = 0.0;// + float a0HI = 0.0;// + float b1HI = 0.0; + float xMID = 0.0; + float a0MID = 0.0; + float b1MID = 0.0; + float xLOW = 0.0; + float a0LOW = 0.0; + float b1LOW = 0.0; + + float lVol = 0.0; + float lmVol = 0.0; + float hmVol = 0.0; + float hVol = 0.0; + + float s0 = 0.0; + float low0 = 0.0; + float tmplMID = 0.0; + float spl0 = 0.0; + float hi0 = 0.0; + float midS0 = 0.0; + float highS0 = 0.0; + float tmplHI = 0.0; + float lowS0 = 0.0; + float tmplLOW = 0.0; + + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Eq4Band) +}; diff --git a/src/PluginEditor.cpp b/src/PluginEditor.cpp @@ -51,6 +51,14 @@ ProteusAudioProcessorEditor::ProteusAudioProcessorEditor (ProteusAudioProcessor& addAndMakeVisible(odFootSw); odFootSw.addListener(this); + cabOnButton.setImages(true, true, true, + ImageCache::getFromMemory(BinaryData::cab_switch_on_png, BinaryData::cab_switch_on_pngSize), 1.0, Colours::transparentWhite, + Image(), 1.0, Colours::transparentWhite, + ImageCache::getFromMemory(BinaryData::cab_switch_on_png, BinaryData::cab_switch_on_pngSize), 1.0, Colours::transparentWhite, + 0.0); + addAndMakeVisible(cabOnButton); + cabOnButton.addListener(this); + driveSliderAttach = std::make_unique<AudioProcessorValueTreeState::SliderAttachment>(processor.treeState, GAIN_ID, odDriveKnob); addAndMakeVisible(odDriveKnob); odDriveKnob.setLookAndFeel(&blackHexKnobLAF); @@ -67,8 +75,32 @@ ProteusAudioProcessorEditor::ProteusAudioProcessorEditor (ProteusAudioProcessor& odLevelKnob.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::NoTextBox, false, 50, 20); odLevelKnob.setDoubleClickReturnValue(true, 0.5); + bassSliderAttach = std::make_unique<AudioProcessorValueTreeState::SliderAttachment>(processor.treeState, BASS_ID, ampBassKnob); + addAndMakeVisible(ampBassKnob); + ampBassKnob.setLookAndFeel(&blackHexKnobLAF); + ampBassKnob.addListener(this); + ampBassKnob.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); + ampBassKnob.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::NoTextBox, false, 50, 20); + ampBassKnob.setDoubleClickReturnValue(true, 0.0); + + midSliderAttach = std::make_unique<AudioProcessorValueTreeState::SliderAttachment>(processor.treeState, MID_ID, ampMidKnob); + addAndMakeVisible(ampMidKnob); + ampMidKnob.setLookAndFeel(&blackHexKnobLAF); + ampMidKnob.addListener(this); + ampMidKnob.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); + ampMidKnob.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::NoTextBox, false, 50, 20); + ampMidKnob.setDoubleClickReturnValue(true, 0.0); + + trebleSliderAttach = std::make_unique<AudioProcessorValueTreeState::SliderAttachment>(processor.treeState, TREBLE_ID, ampTrebleKnob); + addAndMakeVisible(ampTrebleKnob); + ampTrebleKnob.setLookAndFeel(&blackHexKnobLAF); + ampTrebleKnob.addListener(this); + ampTrebleKnob.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); + ampTrebleKnob.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::NoTextBox, false, 50, 20); + ampTrebleKnob.setDoubleClickReturnValue(true, 0.0); + addAndMakeVisible(versionLabel); - versionLabel.setText("v1.1", juce::NotificationType::dontSendNotification); + versionLabel.setText("v1.2", juce::NotificationType::dontSendNotification); versionLabel.setJustificationType(juce::Justification::left); versionLabel.setColour(juce::Label::textColourId, juce::Colours::white); versionLabel.setFont(font); @@ -83,6 +115,11 @@ ProteusAudioProcessorEditor::ProteusAudioProcessorEditor (ProteusAudioProcessor& ProteusAudioProcessorEditor::~ProteusAudioProcessorEditor() { + odDriveKnob.setLookAndFeel(nullptr); + odLevelKnob.setLookAndFeel(nullptr); + ampBassKnob.setLookAndFeel(nullptr); + ampMidKnob.setLookAndFeel(nullptr); + ampTrebleKnob.setLookAndFeel(nullptr); } //============================================================================== @@ -120,11 +157,16 @@ void ProteusAudioProcessorEditor::resized() modelSelect.setBounds(52, 11, 400, 28); //modelLabel.setBounds(197, 2, 90, 25); versionLabel.setBounds(462, 632, 60, 10); + cabOnButton.setBounds(370, 565, 26, 11); // Overdrive Widgets odDriveKnob.setBounds(103, 97, 176, 176); odLevelKnob.setBounds(268, 97, 176, 176); odFootSw.setBounds(185, 416, 175, 160); + + ampBassKnob.setBounds(350, 480, 75, 75); + ampMidKnob.setBounds(350, 400, 75, 75); + ampTrebleKnob.setBounds(350, 320, 75, 75); } bool ProteusAudioProcessorEditor::isValidFormat(File configFile) @@ -261,6 +303,8 @@ void ProteusAudioProcessorEditor::buttonClicked(juce::Button* button) odFootSwClicked(); } else if (button == &loadButton) { loadButtonClicked(); + } else if (button == &cabOnButton) { + cabOnButtonClicked(); } } @@ -272,9 +316,23 @@ void ProteusAudioProcessorEditor::odFootSwClicked() { resetImages(); } +void ProteusAudioProcessorEditor::cabOnButtonClicked() { + if (processor.cab_state == 0) { + processor.cab_state = 1; + } + else { + processor.cab_state = 0; + } + resetImages(); + repaint(); +} + void ProteusAudioProcessorEditor::sliderValueChanged(Slider* slider) { - + // Amp + if (slider == &ampBassKnob || slider == &ampMidKnob || slider == &ampTrebleKnob) { + processor.set_ampEQ(ampBassKnob.getValue(), ampMidKnob.getValue(), ampTrebleKnob.getValue()); + } } void ProteusAudioProcessorEditor::modelSelectChanged() @@ -308,4 +366,19 @@ void ProteusAudioProcessorEditor::resetImages() ImageCache::getFromMemory(BinaryData::footswitch_down_png, BinaryData::footswitch_down_pngSize), 1.0, Colours::transparentWhite, 0.0); } + // Set On/Off cab graphic + if (processor.cab_state == 0) { + cabOnButton.setImages(true, true, true, + ImageCache::getFromMemory(BinaryData::cab_switch_off_png, BinaryData::cab_switch_off_pngSize), 1.0, Colours::transparentWhite, + Image(), 1.0, Colours::transparentWhite, + ImageCache::getFromMemory(BinaryData::cab_switch_off_png, BinaryData::cab_switch_off_pngSize), 1.0, Colours::transparentWhite, + 0.0); + } + else { + cabOnButton.setImages(true, true, true, + ImageCache::getFromMemory(BinaryData::cab_switch_on_png, BinaryData::cab_switch_on_pngSize), 1.0, Colours::transparentWhite, + Image(), 1.0, Colours::transparentWhite, + ImageCache::getFromMemory(BinaryData::cab_switch_on_png, BinaryData::cab_switch_on_pngSize), 1.0, Colours::transparentWhite, + 0.0); + } } diff --git a/src/PluginEditor.h b/src/PluginEditor.h @@ -58,10 +58,14 @@ private: ComboBox modelSelect; // Overdrive Widgets + Slider ampBassKnob; + Slider ampMidKnob; + Slider ampTrebleKnob; Slider odDriveKnob; Slider odLevelKnob; ImageButton odFootSw; //ImageButton odLED; + ImageButton cabOnButton; // LookandFeels @@ -73,10 +77,14 @@ private: void odFootSwClicked(); void modelSelectChanged(); + void cabOnButtonClicked(); bool model_loaded = false; public: + std::unique_ptr <AudioProcessorValueTreeState::SliderAttachment> bassSliderAttach; + std::unique_ptr <AudioProcessorValueTreeState::SliderAttachment> midSliderAttach; + std::unique_ptr <AudioProcessorValueTreeState::SliderAttachment> trebleSliderAttach; std::unique_ptr <AudioProcessorValueTreeState::SliderAttachment> driveSliderAttach; std::unique_ptr <AudioProcessorValueTreeState::SliderAttachment> masterSliderAttach; diff --git a/src/PluginProcessor.cpp b/src/PluginProcessor.cpp @@ -24,6 +24,9 @@ ProteusAudioProcessor::ProteusAudioProcessor() ), treeState(*this, nullptr, "PARAMETER", { std::make_unique<AudioParameterFloat>(GAIN_ID, GAIN_NAME, NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.5f), + std::make_unique<AudioParameterFloat>(BASS_ID, BASS_NAME, NormalisableRange<float>(-8.0f, 8.0f, 0.01f), 0.0f), + std::make_unique<AudioParameterFloat>(MID_ID, MID_NAME, NormalisableRange<float>(-8.0f, 8.0f, 0.01f), 0.0f), + std::make_unique<AudioParameterFloat>(TREBLE_ID, TREBLE_NAME, NormalisableRange<float>(-8.0f, 8.0f, 0.01f), 0.0f), std::make_unique<AudioParameterFloat>(MASTER_ID, MASTER_NAME, NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.5) }) @@ -31,8 +34,21 @@ ProteusAudioProcessor::ProteusAudioProcessor() { driveParam = treeState.getRawParameterValue (GAIN_ID); masterParam = treeState.getRawParameterValue (MASTER_ID); + bassParam = treeState.getRawParameterValue (BASS_ID); + midParam = treeState.getRawParameterValue (MID_ID); + trebleParam = treeState.getRawParameterValue (TREBLE_ID); + + auto bassValue = static_cast<float> (bassParam->load()); + auto midValue = static_cast<float> (midParam->load()); + auto trebleValue = static_cast<float> (trebleParam->load()); + + eq4band.setParameters(bassValue, midValue, trebleValue, 0.0); + eq4band2.setParameters(bassValue, midValue, trebleValue, 0.0); pauseVolume = 3; + + cabSimIRa.load(BinaryData::default_ir_wav, BinaryData::default_ir_wavSize); + } ProteusAudioProcessor::~ProteusAudioProcessor() @@ -123,6 +139,9 @@ void ProteusAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBloc LSTM.reset(); LSTM2.reset(); + // Set up IR + cabSimIRa.prepare(spec); + } void ProteusAudioProcessor::releaseResources() @@ -162,10 +181,14 @@ void ProteusAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer auto driveValue = static_cast<float> (driveParam->load()); auto masterValue = static_cast<float> (masterParam->load()); + auto bassValue = static_cast<float> (bassParam->load()); + auto midValue = static_cast<float> (midParam->load()); + auto trebleValue = static_cast<float> (trebleParam->load()); // Setup Audio Data const int numSamples = buffer.getNumSamples(); const int numInputChannels = getTotalNumInputChannels(); + const int sampleRate = getSampleRate(); dsp::AudioBlock<float> block(buffer); dsp::ProcessContextReplacing<float> context(block); @@ -215,6 +238,25 @@ void ProteusAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer dcBlocker.process(context); + for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + { + // Apply EQ + if (ch == 0) { + eq4band.process(buffer.getReadPointer(0), buffer.getWritePointer(0), midiMessages, numSamples, numInputChannels, sampleRate); + + } + else if (ch == 1) { + eq4band2.process(buffer.getReadPointer(1), buffer.getWritePointer(1), midiMessages, numSamples, numInputChannels, sampleRate); + } + } + + if (cab_state == 1) { + cabSimIRa.process(context); // Process IR a on channel 0 + buffer.applyGain(2.0); + //} else { + // buffer.applyGain(0.7); + } + // Master Volume // Apply ramped changes for gain smoothing if (masterValue == previousMasterValue) @@ -263,6 +305,7 @@ void ProteusAudioProcessor::getStateInformation (MemoryBlock& destData) xml->setAttribute("folder", folder.getFullPathName().toStdString()); xml->setAttribute("saved_model", saved_model.getFullPathName().toStdString()); xml->setAttribute("current_model_index", current_model_index); + xml->setAttribute ("cab_state", cab_state); copyXmlToBinary (*xml, destData); } @@ -282,6 +325,7 @@ void ProteusAudioProcessor::setStateInformation (const void* data, int sizeInByt fw_state = xmlState->getBoolAttribute ("fw_state"); File temp_saved_model = xmlState->getStringAttribute("saved_model"); saved_model = temp_saved_model; + cab_state = xmlState->getBoolAttribute ("cab_state"); current_model_index = xmlState->getIntAttribute("current_model_index"); File temp = xmlState->getStringAttribute("folder"); @@ -297,6 +341,11 @@ void ProteusAudioProcessor::setStateInformation (const void* data, int sizeInByt } } +void ProteusAudioProcessor::set_ampEQ(float bass_slider, float mid_slider, float treble_slider) +{ + eq4band.setParameters(bass_slider, mid_slider, treble_slider, 0.0f); + eq4band2.setParameters(bass_slider, mid_slider, treble_slider, 0.0f); +} void ProteusAudioProcessor::loadConfig(File configFile) { diff --git a/src/PluginProcessor.h b/src/PluginProcessor.h @@ -17,9 +17,17 @@ #define GAIN_NAME "Drive" #define MASTER_ID "level" #define MASTER_NAME "Level" +#define BASS_ID "bass" +#define BASS_NAME "Bass" +#define MID_ID "mid" +#define MID_NAME "Mid" +#define TREBLE_ID "treble" +#define TREBLE_NAME "Treble" #include <nlohmann/json.hpp> #include "RTNeuralLSTM.h" +#include "Eq4Band.h" +#include "CabSim.h" //============================================================================== /** @@ -64,11 +72,14 @@ public: void getStateInformation (MemoryBlock& destData) override; void setStateInformation (const void* data, int sizeInBytes) override; + void set_ampEQ(float bass_slider, float mid_slider, float treble_slider); + // Files and configuration void loadConfig(File configFile); // Pedal/amp states int fw_state = 1; // 0 = off, 1 = on + int cab_state = 1; // 0 = off, 1 = on File currentDirectory = File::getCurrentWorkingDirectory().getFullPathName(); int current_model_index = 0; @@ -91,8 +102,14 @@ public: private: + Eq4Band eq4band; // Amp EQ + Eq4Band eq4band2; // Amp EQ + std::atomic<float>* driveParam = nullptr; std::atomic<float>* masterParam = nullptr; + std::atomic<float>* bassParam = nullptr; + std::atomic<float>* midParam = nullptr; + std::atomic<float>* trebleParam = nullptr; float previousDriveValue = 0.5; float previousMasterValue = 0.5; @@ -105,6 +122,9 @@ private: chowdsp::ResampledProcess<chowdsp::ResamplingTypes::SRCResampler<>> resampler; + // IR processing + CabSim cabSimIRa; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProteusAudioProcessor) };