NeuralPi

Raspberry Pi guitar pedal using neural networks to emulate real amps and effects
Log | Files | Refs | Submodules | README

commit 4c7ca0c6ede0129915b740f6a6ad6d2e6233a9c6
parent b3f649deb8108e073908e8f9ec2f103f07df15b9
Author: keith <kbloemer89@gmail.com>
Date:   Sat, 17 Jul 2021 05:20:12 -0500

Updated IR for OSC messages, updated scripts for ir

Diffstat:
MSource/AmpOSCReceiver.h | 12++++++++++++
MSource/PluginEditor.cpp | 101++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
MSource/PluginEditor.h | 5++++-
MSource/PluginProcessor.cpp | 24++++++++++++++++++++++--
MSource/PluginProcessor.h | 6+++++-
Mresources/npi_background.jpg | 0
Mscripts/update_models.bat | 17+++++++++++++----
Mscripts/update_models.sh | 11++++++++++-
8 files changed, 141 insertions(+), 35 deletions(-)

diff --git a/Source/AmpOSCReceiver.h b/Source/AmpOSCReceiver.h @@ -49,6 +49,11 @@ public: return modelValue; } + Value& getIrValue() + { + return irValue; + } + void changePort (int port) { if (! connect (port)) @@ -84,6 +89,7 @@ private: trebleAddressPattern = "/parameter/" + ampName + "/Treble"; presenceAddressPattern = "/parameter/" + ampName + "/Presence"; modelAddressPattern = "/parameter/" + ampName + "/Model"; + irAddressPattern = "/parameter/" + ampName + "/Ir"; } void oscMessageReceived(const OSCMessage& message) override @@ -124,6 +130,10 @@ private: { modelValue.setValue(jlimit(0.0f, 1.0f, message[0].getFloat32())); } + else if (message.getAddressPattern().matches(irAddressPattern)) + { + irValue.setValue(jlimit(0.0f, 1.0f, message[0].getFloat32())); + } } } @@ -138,6 +148,7 @@ private: String trebleAddressPattern {"/parameter/elk_juce_example/Treble"}; String presenceAddressPattern {"/parameter/elk_juce_example/Presence"}; String modelAddressPattern {"/parameter/elk_juce_example/Model"}; + String irAddressPattern {"/parameter/elk_juce_example/Ir"}; Value gainValue {0.5f}; Value masterValue {0.5f}; @@ -147,6 +158,7 @@ private: Value presenceValue {0.5f}; Value modelValue {0.0f}; + Value irValue {0.0f}; bool connected = false; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp @@ -80,6 +80,47 @@ NeuralPiAudioProcessorEditor::NeuralPiAudioProcessorEditor (NeuralPiAudioProcess loadButton.setColour(juce::Label::textColourId, juce::Colours::black); loadButton.addListener(this); + + //addAndMakeVisible(irKnob); + //irKnob.setLookAndFeel(&ampSilverKnobLAF); + irKnob.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::TextBoxBelow, false, 50, 20); + irKnob.setNumDecimalPlacesToDisplay(1); + irKnob.addListener(this); + //irKnob.setRange(0, processor.irFiles.size() - 1); + irKnob.setRange(0.0, 1.0); + irKnob.setValue(0.0); + irKnob.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); + irKnob.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::NoTextBox, false, 50, 20); + irKnob.setNumDecimalPlacesToDisplay(1); + irKnob.setDoubleClickReturnValue(true, 0.0); + + auto irValue = getParameterValue(irName); + Slider& irSlider = getIrSlider(); + irSlider.setValue(irValue, NotificationType::dontSendNotification); + + irKnob.onValueChange = [this] + { + const float sliderValue = static_cast<float> (getIrSlider().getValue()); + const float irValue = getParameterValue(irName); + + if (!approximatelyEqual(irValue, sliderValue)) + { + setParameterValue(irName, sliderValue); + + // create and send an OSC message with an address and a float value: + float value = static_cast<float> (getIrSlider().getValue()); + + if (!oscSender.send(irAddressPattern, value)) + { + updateOutConnectedLabel(false); + } + else + { + DBG("Sent value " + String(value) + " to AP " + irAddressPattern); + } + } + }; + addAndMakeVisible(irSelect); irSelect.setColour(juce::Label::textColourId, juce::Colours::black); int i = 1; @@ -409,6 +450,7 @@ NeuralPiAudioProcessorEditor::NeuralPiAudioProcessorEditor (NeuralPiAudioProcess oscReceiver.getPresenceValue().addListener(this); oscReceiver.getModelValue().addListener(this); + oscReceiver.getIrValue().addListener(this); updateInConnectedLabel(); @@ -416,7 +458,6 @@ NeuralPiAudioProcessorEditor::NeuralPiAudioProcessorEditor (NeuralPiAudioProcess // Size of plugin GUI setSize(276, 455); - } NeuralPiAudioProcessorEditor::~NeuralPiAudioProcessorEditor() @@ -483,25 +524,27 @@ void NeuralPiAudioProcessorEditor::resized() void NeuralPiAudioProcessorEditor::modelSelectChanged() { const int selectedFileIndex = modelSelect.getSelectedItemIndex(); + File selectedFile = processor.userAppDataDirectory_tones.getFullPathName() + "/" + modelSelect.getText() + ".json"; if (selectedFileIndex >= 0 && selectedFileIndex < processor.jsonFiles.size()) { - processor.loadConfig(processor.jsonFiles[selectedFileIndex]); - processor.current_model_index = modelSelect.getSelectedItemIndex(); + //processor.loadConfig(processor.jsonFiles[selectedFileIndex]); + processor.loadConfig(selectedFile); + processor.current_model_index = selectedFileIndex; } auto newValue = static_cast<float>(processor.current_model_index / (processor.num_models - 1.0)); modelKnob.setValue(newValue); - //modelKnob.setValue(processor.current_model_index); } void NeuralPiAudioProcessorEditor::irSelectChanged() { const int selectedFileIndex = irSelect.getSelectedItemIndex(); - if (selectedFileIndex >= 0 && selectedFileIndex < processor.jsonFiles.size()) { - processor.loadIR(processor.irFiles[selectedFileIndex]); - processor.current_ir_index = irSelect.getSelectedItemIndex(); + File selectedFile = processor.userAppDataDirectory_irs.getFullPathName() + "/" + irSelect.getText() + ".wav"; + if (selectedFileIndex >= 0 && selectedFileIndex < processor.irFiles.size()) { + //processor.loadIR(processor.irFiles[selectedFileIndex]); + processor.loadIR(selectedFile); + processor.current_ir_index = selectedFileIndex; } - //auto newValue = static_cast<float>(processor.current_ir_index / (processor.num_irs - 1.0)); - //modelKnob.setValue(newValue); - //modelKnob.setValue(processor.current_model_index); + auto newValue = static_cast<float>(processor.current_ir_index / (processor.num_irs - 1.0)); + irKnob.setValue(newValue); } void NeuralPiAudioProcessorEditor::updateToggleState(juce::Button* button, juce::String name) @@ -538,7 +581,7 @@ void NeuralPiAudioProcessorEditor::loadButtonClicked() modelSelect.addItem(file.getFileNameWithoutExtension(), processor.jsonFiles.size() + 1); modelSelect.setSelectedItemIndex(processor.jsonFiles.size(), juce::NotificationType::dontSendNotification); processor.jsonFiles.push_back(file); - //processor.num_models += 1; + processor.num_models += 1; } // Sort jsonFiles alphabetically std::sort(processor.jsonFiles.begin(), processor.jsonFiles.end()); @@ -573,7 +616,7 @@ void NeuralPiAudioProcessorEditor::loadIRClicked() irSelect.addItem(file.getFileNameWithoutExtension(), processor.irFiles.size() + 1); irSelect.setSelectedItemIndex(processor.irFiles.size(), juce::NotificationType::dontSendNotification); processor.irFiles.push_back(file); - //processor.num_models += 1; + processor.num_irs += 1; } // Sort jsonFiles alphabetically std::sort(processor.irFiles.begin(), processor.irFiles.end()); @@ -597,25 +640,16 @@ void NeuralPiAudioProcessorEditor::buttonClicked(juce::Button* button) void NeuralPiAudioProcessorEditor::sliderValueChanged(Slider* slider) { - if (slider == &modelKnob) + if (slider == &modelKnob) { if (slider->getValue() >= 0 && slider->getValue() < processor.jsonFiles.size()) { modelSelect.setSelectedItemIndex(processor.getModelIndex(slider->getValue()), juce::NotificationType::dontSendNotification); } -} -/* - else if (slider == &ampBassKnob || slider == &ampMidKnob || slider == &ampTrebleKnob) { - processor.set_ampEQ(ampBassKnob.getValue(), ampMidKnob.getValue(), ampTrebleKnob.getValue(), ampPresenceKnob.getValue()); - // Set knob states for saving positions when closing/reopening GUI - processor.ampBassKnobState = ampBassKnob.getValue(); - processor.ampMidKnobState = ampMidKnob.getValue(); - processor.ampTrebleKnobState = ampTrebleKnob.getValue(); - } - else if (slider == &ampPresenceKnob) { - processor.set_ampEQ(ampBassKnob.getValue(), ampMidKnob.getValue(), ampTrebleKnob.getValue(), ampPresenceKnob.getValue()); + } else if (slider == &irKnob) { + if (slider->getValue() >= 0 && slider->getValue() < processor.irFiles.size()) { + irSelect.setSelectedItemIndex(processor.getIrIndex(slider->getValue()), juce::NotificationType::dontSendNotification); + } } } -*/ - // OSC Messages Slider& NeuralPiAudioProcessorEditor::getGainSlider() @@ -653,6 +687,11 @@ Slider& NeuralPiAudioProcessorEditor::getModelSlider() return modelKnob; } +Slider& NeuralPiAudioProcessorEditor::getIrSlider() +{ + return irKnob; +} + Label& NeuralPiAudioProcessorEditor::getOutPortNumberField() { @@ -693,6 +732,7 @@ void NeuralPiAudioProcessorEditor::buildAddressPatterns() trebleAddressPattern = "/parameter/" + ampName + "/Treble"; presenceAddressPattern = "/parameter/" + ampName + "/Presence"; modelAddressPattern = "/parameter/" + ampName + "/Model"; + modelAddressPattern = "/parameter/" + ampName + "/Ir"; } void NeuralPiAudioProcessorEditor::connectSender() @@ -832,6 +872,14 @@ void NeuralPiAudioProcessorEditor::valueChanged(Value& value) NotificationType::sendNotification); } } + else if (value.refersToSameSourceAs(oscReceiver.getIrValue())) + { + if (!approximatelyEqual(static_cast<double> (value.getValue()), getIrSlider().getValue())) + { + getIrSlider().setValue(static_cast<double> (value.getValue()), + NotificationType::sendNotification); + } + } } void NeuralPiAudioProcessorEditor::timerCallback() @@ -843,6 +891,7 @@ void NeuralPiAudioProcessorEditor::timerCallback() getTrebleSlider().setValue(getParameterValue(trebleName), NotificationType::dontSendNotification); getPresenceSlider().setValue(getParameterValue(presenceName), NotificationType::dontSendNotification); getModelSlider().setValue(getParameterValue(modelName), NotificationType::dontSendNotification); + getIrSlider().setValue(getParameterValue(irName), NotificationType::dontSendNotification); } AudioProcessorParameter* NeuralPiAudioProcessorEditor::getParameter(const String& paramId) diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h @@ -46,6 +46,7 @@ public: String gainAddressPattern{ "/parameter/NeuralPi/Gain" }; String masterAddressPattern{ "/parameter/NeuralPi/Master" }; String modelAddressPattern{ "/parameter/NeuralPi/Model" }; + String irAddressPattern{ "/parameter/NeuralPi/Ir" }; String bassAddressPattern{ "/parameter/NeuralPi/Bass" }; String midAddressPattern{ "/parameter/NeuralPi/Mid" }; String trebleAddressPattern{ "/parameter/NeuralPi/Treble" }; @@ -59,7 +60,7 @@ public: const String presenceName{ "presence" }; const String modelName{ "model" }; - + const String irName{ "ir" }; private: // This reference is provided as a quick way for your editor to @@ -72,6 +73,7 @@ private: Slider ampGainKnob; Slider ampMasterKnob; Slider modelKnob; + Slider irKnob; //ImageButton ampOnButton; //ImageButton ampLED; ComboBox modelSelect; @@ -130,6 +132,7 @@ private: Slider& getGainSlider(); Slider& getMasterSlider(); Slider& getModelSlider(); + Slider& getIrSlider(); Slider& getBassSlider(); Slider& getMidSlider(); Slider& getTrebleSlider(); diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp @@ -51,6 +51,7 @@ NeuralPiAudioProcessor::NeuralPiAudioProcessor() addParameter(trebleParam = new AudioParameterFloat(TREBLE_ID, TREBLE_NAME, NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.5f)); addParameter(presenceParam = new AudioParameterFloat(PRESENCE_ID, PRESENCE_NAME, NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.5f)); addParameter(modelParam = new AudioParameterFloat(MODEL_ID, MODEL_NAME, NormalisableRange<float>(0.0f, 1.0f, 0.001f), 0.0f)); + addParameter(irParam = new AudioParameterFloat(IR_ID, IR_NAME, NormalisableRange<float>(0.0f, 1.0f, 0.001f), 0.0f)); } @@ -189,6 +190,9 @@ void NeuralPiAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffe auto model = static_cast<float> (modelParam->get()); model_index = getModelIndex(model); + auto ir = static_cast<float> (irParam->get()); + ir_index = getIrIndex(ir); + buffer.applyGain(gain * 2.0); eq4band.setParameters(bass, mid, treble, presence);// Better to move this somewhere else? Only need to set when value changes eq4band.process(buffer.getReadPointer(0), buffer.getWritePointer(0), midiMessages, numSamples, numInputChannels, sampleRate); @@ -204,6 +208,10 @@ void NeuralPiAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffe // Process IR if (ir_state == true) { + if (current_ir_index != ir_index) { + loadIR(irFiles[ir_index]); + current_ir_index = ir_index; + } auto block = dsp::AudioBlock<float>(buffer).getSingleChannelBlock(0); auto context = juce::dsp::ProcessContextReplacing<float>(block); cabSimIR.process(context); @@ -244,6 +252,7 @@ void NeuralPiAudioProcessor::getStateInformation(MemoryBlock& destData) stream.writeFloat(*trebleParam); stream.writeFloat(*presenceParam); stream.writeFloat(*modelParam); + stream.writeFloat(*irParam); } void NeuralPiAudioProcessor::setStateInformation(const void* data, int sizeInBytes) @@ -257,6 +266,7 @@ void NeuralPiAudioProcessor::setStateInformation(const void* data, int sizeInByt trebleParam->setValueNotifyingHost(stream.readFloat()); presenceParam->setValueNotifyingHost(stream.readFloat()); modelParam->setValueNotifyingHost(stream.readFloat()); + irParam->setValueNotifyingHost(stream.readFloat()); } int NeuralPiAudioProcessor::getModelIndex(float model_param) @@ -271,6 +281,18 @@ int NeuralPiAudioProcessor::getModelIndex(float model_param) return a; } +int NeuralPiAudioProcessor::getIrIndex(float ir_param) +{ + int a = static_cast<int>(round(ir_param * (num_irs - 1.0))); + if (a > num_irs - 1) { + a = num_irs - 1; + } + else if (a < 0) { + a = 0; + } + return a; +} + void NeuralPiAudioProcessor::loadConfig(File configFile) { this->suspendProcessing(true); @@ -288,8 +310,6 @@ void NeuralPiAudioProcessor::loadIR(File irFile) { this->suspendProcessing(true); ir_loaded = 1; - //String path = irFile.getFullPathName(); - //char_filename = path.toUTF8(); // TODO Add check here for invalid files cabSimIR.load(irFile); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h @@ -22,6 +22,8 @@ #define GAIN_NAME "Gain" #define MODEL_ID "model" #define MODEL_NAME "Model" +#define IR_ID "ir" +#define IR_NAME "Ir" #define MASTER_ID "master" #define MASTER_NAME "Master" #define BASS_ID "bass" @@ -77,6 +79,7 @@ public: void setStateInformation (const void* data, int sizeInBytes) override; int getModelIndex(float model_param); + int getIrIndex(float ir_param); void loadConfig(File configFile); void loadIR(File irFile); void setupDataDirectories(); @@ -117,8 +120,8 @@ public: int custom_ir = 0; // 0 = custom tone loaded, 1 = default channel tone File loaded_ir; bool ir_state = true; - int current_ir_index = 0; + int ir_index = 0; RT_LSTM LSTM; @@ -133,6 +136,7 @@ private: AudioParameterFloat* trebleParam; AudioParameterFloat* presenceParam; AudioParameterFloat* modelParam; + AudioParameterFloat* irParam; dsp::IIR::Filter<float> dcBlocker; diff --git a/resources/npi_background.jpg b/resources/npi_background.jpg Binary files differ. diff --git a/scripts/update_models.bat b/scripts/update_models.bat @@ -1,10 +1,10 @@ ::############################################################################ :: NeuralPi - Update Models - Windows Script :: -:: This script transfers models from a Windows computer to the NeuralPi, -:: and from the NeuralPi back to the host computer. Edit the Raspberry Pi -:: IP address (after connecting to a local Wifi Network), and run this -:: script from a Windows computer running the NeuralPi plugin. +:: This script transfers models and impulse responses from a Windows computer +:: to the NeuralPi, and from the NeuralPi back to the host computer. Edit +:: the Raspberry Pi IP address (after connecting to a local Wifi Network), +:: and run this script from a Windows computer running the NeuralPi plugin. :: :: Note: Ensure OpenSSH is installed. This comes installed with Windows as of 2018. ::############################################################################ @@ -17,15 +17,24 @@ set "rpi_ip_address=127.0.0.1" :: Typical Windows 10 Path, edit <YOUR_USERNAME> with your Windows Username set "host_model_path=C:/Users/<YOUR_USERNAME>/Documents/GuitarML/NeuralPi/tones" +set "host_ir_path=C:/Users/<YOUR_USERNAME>/Documents/GuitarML/NeuralPi/irs" :: Rpi with Elk OS Path (shouldn't need to change) set "rpi_model_path=/home/mind/Documents/GuitarML/NeuralPi/tones" +set "rpi_ir_path=/home/mind/Documents/GuitarML/NeuralPi/irs" :: ############################################################################ :: Copy all models from local computer to Rpi scp %host_model_path%/*.json root@%rpi_ip_address%:%rpi_model_path%/ +:: Copy all IRs from local computer to Rpi +scp %host_ir_path%/*.wav root@%rpi_ip_address%:%rpi_ir_path%/ + + :: Copy all models from Rpi to local computer scp root@%rpi_ip_address%:%rpi_model_path%/*.json %host_model_path%/ + +:: Copy all IRs from Rpi to local computer +scp root@%rpi_ip_address%:%rpi_ir_path%/*.wav %host_ir_path%/ diff --git a/scripts/update_models.sh b/scripts/update_models.sh @@ -18,13 +18,22 @@ rpi_ip_address=127.0.0.1 # Update this field with the Raspberry Pi's IP address # Uncomment the appropriate path for your computer: host_model_path=~/Documents/GuitarML/NeuralPi/tones #Typical Mac/Linux Path (shouldn't need to change) +host_ir_path=~/Documents/GuitarML/NeuralPi/irs #Typical Mac/Linux Path (shouldn't need to change) rpi_model_path=/home/mind/Documents/GuitarML/NeuralPi/tones # Rpi with Elk OS Path (shouldn't need to change) +rpi_ir_path=/home/mind/Documents/GuitarML/NeuralPi/irs # Rpi with Elk OS Path (shouldn't need to change) ############################################################################# echo "Copying all models from local computer to Rpi.." scp $host_model_path/*.json root@$rpi_ip_address:$rpi_model_path/ +echo "Copying all IRs from local computer to Rpi.." +scp $host_ir_path/*.json root@$rpi_ip_address:$rpi_ir_path/ + + echo "Copying all models from Rpi to local computer.." -scp root@$rpi_ip_address:$rpi_model_path/*.json $host_model_path/ +scp root@$rpi_ip_address:$rpi_model_path/*.wav $host_model_path/ + +echo "Copying all IRs from Rpi to local computer.." +scp root@$rpi_ip_address:$rpi_ir_path/*.wav $host_ir_path/