commit 7b211c46d34f03a447fffeae72c06b3c57ee86ff
parent 1e888909f5f46aa4e30489e66adecde57e40785c
Author: Keith Bloemer <32459398+GuitarML@users.noreply.github.com>
Date: Tue, 10 Aug 2021 11:45:13 -0500
Merge pull request #10 from GuitarML/ir-loader
Ir loader
Diffstat:
12 files changed, 422 insertions(+), 71 deletions(-)
diff --git a/NeuralPi.jucer b/NeuralPi.jucer
@@ -16,6 +16,7 @@
<GROUP id="{70CE292C-E9C5-C029-B95A-F7DF41E5F74C}" name="Source">
<FILE id="VgCJPH" name="AmpOSCReceiver.h" compile="0" resource="0"
file="Source/AmpOSCReceiver.h"/>
+ <FILE id="HvJFu8" name="CabSim.h" compile="0" resource="0" file="Source/CabSim.h"/>
<FILE id="s1HQuK" name="Eq4Band.cpp" compile="1" resource="0" file="Source/Eq4Band.cpp"/>
<FILE id="xtLEtv" name="Eq4Band.h" compile="0" resource="0" file="Source/Eq4Band.h"/>
<FILE id="hNjQV9" name="PluginEditor.cpp" compile="1" resource="0"
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/CabSim.h b/Source/CabSim.h
@@ -0,0 +1,59 @@
+/*
+ ==============================================================================
+
+ CabSim
+
+ ==============================================================================
+*/
+#include "../JuceLibraryCode/JuceHeader.h"
+
+#pragma once
+
+//==============================================================================
+class CabSim
+{
+public:
+ //==============================================================================
+ CabSim()
+ {
+
+ }
+
+ //==============================================================================
+ 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(File irFile) noexcept
+ {
+ auto& convolution = processorChain.template get<convolutionIndex>();
+ convolution.loadImpulseResponse(irFile,
+ juce::dsp::Convolution::Stereo::yes,
+ juce::dsp::Convolution::Trim::no,
+ 1024);
+ }
+
+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/Source/PluginEditor.cpp b/Source/PluginEditor.cpp
@@ -80,7 +80,74 @@ NeuralPiAudioProcessorEditor::NeuralPiAudioProcessorEditor (NeuralPiAudioProcess
loadButton.setColour(juce::Label::textColourId, juce::Colours::black);
loadButton.addListener(this);
- //gainSliderAttach = std::make_unique<AudioProcessorValueTreeState::SliderAttachment>(processor.treeState, GAIN_ID, ampGainKnob);
+
+ //addAndMakeVisible(irKnob);
+ //irKnob.setLookAndFeel(&SilverKnobLAF);
+ 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;
+ for (const auto& jsonFile : processor.irFiles) {
+ irSelect.addItem(jsonFile.getFileNameWithoutExtension(), i);
+ i += 1;
+ }
+ irSelect.onChange = [this] {irSelectChanged(); };
+ irSelect.setSelectedItemIndex(processor.current_ir_index, juce::NotificationType::dontSendNotification);
+ irSelect.setScrollWheelEnabled(true);
+
+ addAndMakeVisible(loadIR);
+ loadIR.setButtonText("Import IR");
+ loadIR.setColour(juce::Label::textColourId, juce::Colours::black);
+ loadIR.addListener(this);
+
+ // Toggle IR
+ //addAndMakeVisible(irButton); // Toggle is for testing purposes
+ irButton.setToggleState(true, juce::NotificationType::dontSendNotification);
+ irButton.onClick = [this] { updateToggleState(&irButton, "IR"); };
+
+ // Toggle LSTM
+ //addAndMakeVisible(lstmButton); // Toggle is for testing purposes
+ lstmButton.setToggleState(true, juce::NotificationType::dontSendNotification);
+ lstmButton.onClick = [this] { updateToggleState(&lstmButton, "LSTM"); };
+
+
addAndMakeVisible(ampGainKnob);
//ampGainKnob.setLookAndFeel(&SilverKnobLAF);
ampGainKnob.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::TextBoxBelow, false, 50, 20);
@@ -382,14 +449,14 @@ NeuralPiAudioProcessorEditor::NeuralPiAudioProcessorEditor (NeuralPiAudioProcess
oscReceiver.getPresenceValue().addListener(this);
oscReceiver.getModelValue().addListener(this);
+ oscReceiver.getIrValue().addListener(this);
updateInConnectedLabel();
connectSender();
// Size of plugin GUI
- setSize(276, 430);
-
+ setSize(260, 455);
}
NeuralPiAudioProcessorEditor::~NeuralPiAudioProcessorEditor()
@@ -414,50 +481,77 @@ void NeuralPiAudioProcessorEditor::resized()
{
// This is generally where you'll want to lay out the positions of any
// subcomponents in your editor..
- modelSelect.setBounds(19, 10, 234, 25);
- loadButton.setBounds(19, 42, 100, 25);
+ modelSelect.setBounds(11, 10, 234, 25);
+ loadButton.setBounds(19, 74, 100, 25);
modelKnob.setBounds(140, 40, 75, 95);
+ irSelect.setBounds(11, 42, 234, 25);
+ loadIR.setBounds(125, 74, 100, 25);
+ irButton.setBounds(248, 42, 257, 25);
+ lstmButton.setBounds(248, 10, 257, 25);
+
// Amp Widgets
- ampGainKnob.setBounds(15, 90, 75, 95);
- ampMasterKnob.setBounds(100, 90, 75, 95);
- ampBassKnob.setBounds(15, 225, 75, 95);
- ampMidKnob.setBounds(100, 225, 75, 95);
- ampTrebleKnob.setBounds(185, 225, 75, 95);
- ampPresenceKnob.setBounds(185, 90, 75, 95);
-
- GainLabel.setBounds(11, 78, 80, 10);
- LevelLabel.setBounds(98, 78, 80, 10);
- BassLabel.setBounds(11, 213, 80, 10);
- MidLabel.setBounds(97, 213, 80, 10);
- TrebleLabel.setBounds(183, 213, 80, 10);
- PresenceLabel.setBounds(183, 78, 80, 10);
+ ampGainKnob.setBounds(10, 120, 75, 95);
+ ampMasterKnob.setBounds(95, 120, 75, 95);
+ ampBassKnob.setBounds(10, 250, 75, 95);
+ ampMidKnob.setBounds(95, 250, 75, 95);
+ ampTrebleKnob.setBounds(180, 250, 75, 95);
+ ampPresenceKnob.setBounds(180, 120, 75, 95);
+
+ GainLabel.setBounds(6, 108, 80, 10);
+ LevelLabel.setBounds(93, 108, 80, 10);
+ BassLabel.setBounds(6, 238, 80, 10);
+ MidLabel.setBounds(91, 238, 80, 10);
+ TrebleLabel.setBounds(178, 238, 80, 10);
+ PresenceLabel.setBounds(178, 108, 80, 10);
addAndMakeVisible(ampNameLabel);
ampNameField.setEditable(true, true, true);
addAndMakeVisible(ampNameField);
// IP controls:
- ipField.setBounds(150, 340, 100, 25);
- ipLabel.setBounds(15, 340, 150, 25);
+ ipField.setBounds(150, 365, 100, 25);
+ ipLabel.setBounds(15, 365, 150, 25);
// Port controls:
- outPortNumberLabel.setBounds(15, 370, 150, 25);
- outPortNumberField.setBounds(160, 370, 75, 25);
- inPortNumberLabel.setBounds(15, 400, 150, 25);
- inPortNumberField.setBounds(160, 400, 75, 25);
+ outPortNumberLabel.setBounds(15, 395, 150, 25);
+ outPortNumberField.setBounds(160, 395, 75, 25);
+ inPortNumberLabel.setBounds(15, 425, 150, 25);
+ inPortNumberField.setBounds(160, 425, 75, 25);
}
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();
+ 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));
+ irKnob.setValue(newValue);
+}
+
+void NeuralPiAudioProcessorEditor::updateToggleState(juce::Button* button, juce::String name)
+{
+ if (name == "IR")
+ processor.ir_state = button->getToggleState();
+ else
+ processor.lstm_state = button->getToggleState();
}
void NeuralPiAudioProcessorEditor::loadButtonClicked()
@@ -486,7 +580,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());
@@ -495,36 +589,66 @@ void NeuralPiAudioProcessorEditor::loadButtonClicked()
}
}
+void NeuralPiAudioProcessorEditor::loadIRClicked()
+{
+ FileChooser chooser("Select one or more .wav IR files to import",
+ {},
+ "*.wav");
+ if (chooser.browseForMultipleFilesToOpen())
+ {
+ int import_fail = 1;
+ Array<File> files = chooser.getResults();
+ for (auto file : files) {
+ File fullpath = processor.userAppDataDirectory_irs.getFullPathName() + "/" + file.getFileName();
+ bool b = fullpath.existsAsFile();
+ if (b == false) {
+
+ processor.loadIR(file);
+ fname = file.getFileName();
+ processor.loaded_ir = file;
+ processor.loaded_ir_name = fname;
+ processor.custom_ir = 1;
+
+ // Copy selected file to model directory and load into dropdown menu
+ bool a = file.copyFileTo(fullpath);
+ if (a == true) {
+ irSelect.addItem(file.getFileNameWithoutExtension(), processor.irFiles.size() + 1);
+ irSelect.setSelectedItemIndex(processor.irFiles.size(), juce::NotificationType::dontSendNotification);
+ processor.irFiles.push_back(file);
+ processor.num_irs += 1;
+ }
+ // Sort jsonFiles alphabetically
+ std::sort(processor.irFiles.begin(), processor.irFiles.end());
+ }
+ }
+ }
+}
+
void NeuralPiAudioProcessorEditor::buttonClicked(juce::Button* button)
{
if (button == &loadButton) {
loadButtonClicked();
}
+ else
+ {
+ loadIRClicked();
+ }
}
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 == &BassKnob || slider == &MidKnob || slider == &TrebleKnob) {
- 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 == &PresenceKnob) {
- 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()
@@ -562,6 +686,11 @@ Slider& NeuralPiAudioProcessorEditor::getModelSlider()
return modelKnob;
}
+Slider& NeuralPiAudioProcessorEditor::getIrSlider()
+{
+ return irKnob;
+}
+
Label& NeuralPiAudioProcessorEditor::getOutPortNumberField()
{
@@ -602,6 +731,7 @@ void NeuralPiAudioProcessorEditor::buildAddressPatterns()
trebleAddressPattern = "/parameter/" + ampName + "/Treble";
presenceAddressPattern = "/parameter/" + ampName + "/Presence";
modelAddressPattern = "/parameter/" + ampName + "/Model";
+ irAddressPattern = "/parameter/" + ampName + "/Ir";
}
void NeuralPiAudioProcessorEditor::connectSender()
@@ -741,6 +871,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()
@@ -752,6 +890,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,9 +73,11 @@ private:
Slider ampGainKnob;
Slider ampMasterKnob;
Slider modelKnob;
+ Slider irKnob;
//ImageButton ampOnButton;
//ImageButton ampLED;
ComboBox modelSelect;
+ ComboBox irSelect;
Slider ampBassKnob;
Slider ampMidKnob;
Slider ampTrebleKnob;
@@ -91,10 +94,17 @@ private:
File model_folder;
TextButton loadButton;
+ TextButton loadIR;
+ ToggleButton irButton;
+ ToggleButton lstmButton;
+
juce::String fname;
virtual void buttonClicked(Button* button) override;
void modelSelectChanged();
void loadButtonClicked();
+ void updateToggleState(juce::Button* button, juce::String name);
+ void irSelectChanged();
+ void loadIRClicked();
virtual void sliderValueChanged(Slider* slider) override;
@@ -122,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
@@ -30,12 +30,18 @@ NeuralPiAudioProcessor::NeuralPiAudioProcessor()
setupDataDirectories();
installTones();
resetDirectory(userAppDataDirectory_tones);
+ // Sort jsonFiles alphabetically
+ std::sort(jsonFiles.begin(), jsonFiles.end());
if (jsonFiles.size() > 0) {
loadConfig(jsonFiles[current_model_index]);
}
- // Sort jsonFiles alphabetically
- std::sort(jsonFiles.begin(), jsonFiles.end());
+ resetDirectoryIR(userAppDataDirectory_irs);
+ // Sort irFiles alphabetically
+ std::sort(irFiles.begin(), irFiles.end());
+ if (irFiles.size() > 0) {
+ loadIR(irFiles[current_ir_index]);
+ }
// initialize parameters:
addParameter(gainParam = new AudioParameterFloat(GAIN_ID, GAIN_NAME, NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.5f));
@@ -45,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));
}
@@ -125,6 +132,9 @@ void NeuralPiAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlo
dcBlocker.coefficients = dsp::IIR::Coefficients<float>::makeHighPass(sampleRate, 35.0f);
dsp::ProcessSpec spec{ sampleRate, static_cast<uint32> (samplesPerBlock), 2 };
dcBlocker.prepare(spec);
+
+ // Set up IR
+ cabSimIR.prepare(spec);
}
void NeuralPiAudioProcessor::releaseResources()
@@ -171,21 +181,24 @@ void NeuralPiAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffe
if (amp_state == 1) {
auto gain = static_cast<float> (gainParam->get());
auto master = static_cast<float> (masterParam->get());
- // Note: Default 0.0 -> 1.0 param range is converted to +-8.0 here
- auto bass = (static_cast<float> (bassParam->get() - 0.5) * 16.0);
- auto mid = (static_cast<float> (midParam->get() - 0.5) * 16.0);
- auto treble = (static_cast<float> (trebleParam->get() - 0.5) * 16.0);
- auto presence = (static_cast<float> (presenceParam->get() - 0.5) * 16.0);
+ // Note: Default 0.0 -> 1.0 param range is converted to +-12.0 here
+ auto bass = (static_cast<float> (bassParam->get() - 0.5) * 24.0);
+ auto mid = (static_cast<float> (midParam->get() - 0.5) * 24.0);
+ auto treble = (static_cast<float> (trebleParam->get() - 0.5) * 24.0);
+ auto presence = (static_cast<float> (presenceParam->get() - 0.5) * 24.0);
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);
// Apply LSTM model
- if (model_loaded == 1) {
+ if (model_loaded == 1 && lstm_state == true) {
if (current_model_index != model_index) {
loadConfig(jsonFiles[model_index]);
current_model_index = model_index;
@@ -193,6 +206,20 @@ void NeuralPiAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffe
LSTM.process(buffer.getReadPointer(0), buffer.getWritePointer(0), numSamples);
}
+ // Process IR
+ if (ir_state == true && num_irs > 0) {
+ 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);
+
+ // IR generally makes output quieter, add volume here to make ir on/off volume more even
+ buffer.applyGain(2.0);
+ }
+
// Master Volume
buffer.applyGain(master);
}
@@ -228,6 +255,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)
@@ -241,12 +269,11 @@ 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)
{
- //return static_cast<int>(model_param * (jsonFiles.size() - 1.0));
- //return static_cast<int>(model_param * (num_models - 1.0));
int a = static_cast<int>(round(model_param * (num_models - 1.0)));
if (a > num_models - 1) {
a = num_models - 1;
@@ -257,16 +284,48 @@ 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);
- model_loaded = 1;
String path = configFile.getFullPathName();
char_filename = path.toUTF8();
- // TODO Add check here for invalid files
- LSTM.load_json(char_filename);
+ try {
+ LSTM.load_json(char_filename);
+ model_loaded = 1;
+ }
+ catch (const std::exception& e) {
+ DBG("Unable to load IR file: " + configFile.getFullPathName());
+ std::cout << e.what();
+ }
+
+ this->suspendProcessing(false);
+}
+
+void NeuralPiAudioProcessor::loadIR(File irFile)
+{
+ this->suspendProcessing(true);
+ try {
+ cabSimIR.load(irFile);
+ ir_loaded = 1;
+ }
+ catch (const std::exception& e) {
+ DBG("Unable to load IR file: " + irFile.getFullPathName());
+ std::cout << e.what();
+ }
this->suspendProcessing(false);
}
@@ -282,6 +341,19 @@ void NeuralPiAudioProcessor::resetDirectory(const File& file)
}
}
+void NeuralPiAudioProcessor::resetDirectoryIR(const File& file)
+{
+ irFiles.clear();
+ if (file.isDirectory())
+ {
+ juce::Array<juce::File> results;
+ file.findChildFiles(results, juce::File::findFiles, false, "*.wav");
+ for (int i = results.size(); --i >= 0;)
+ irFiles.push_back(File(results.getReference(i).getFullPathName()));
+
+ }
+}
+
void NeuralPiAudioProcessor::addDirectory(const File& file)
{
if (file.isDirectory())
@@ -296,6 +368,20 @@ void NeuralPiAudioProcessor::addDirectory(const File& file)
}
}
+void NeuralPiAudioProcessor::addDirectoryIR(const File& file)
+{
+ if (file.isDirectory())
+ {
+ juce::Array<juce::File> results;
+ file.findChildFiles(results, juce::File::findFiles, false, "*.wav");
+ for (int i = results.size(); --i >= 0;)
+ {
+ irFiles.push_back(File(results.getReference(i).getFullPathName()));
+ num_irs = num_irs + 1.0;
+ }
+ }
+}
+
void NeuralPiAudioProcessor::setupDataDirectories()
{
// User app data directory
@@ -303,6 +389,7 @@ void NeuralPiAudioProcessor::setupDataDirectories()
File userAppDataTempFile_tones = userAppDataDirectory_tones.getChildFile("tmp.pdl");
+ File userAppDataTempFile_irs = userAppDataDirectory_irs.getChildFile("tmp.pdl");
// Create (and delete) temp file if necessary, so that user doesn't have
// to manually create directories
@@ -320,9 +407,17 @@ void NeuralPiAudioProcessor::setupDataDirectories()
userAppDataTempFile_tones.deleteFile();
}
+ if (!userAppDataDirectory_irs.exists()) {
+ userAppDataTempFile_irs.create();
+ }
+ if (userAppDataTempFile_irs.existsAsFile()) {
+ userAppDataTempFile_irs.deleteFile();
+ }
+
// Add the tones directory and update tone list
addDirectory(userAppDataDirectory_tones);
+ addDirectoryIR(userAppDataDirectory_irs);
}
void NeuralPiAudioProcessor::installTones()
diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h
@@ -12,6 +12,7 @@
#include "RTNeuralLSTM.h"
#include "AmpOSCReceiver.h"
#include "Eq4Band.h"
+#include "CabSim.h"
#pragma once
@@ -21,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"
@@ -75,31 +78,29 @@ public:
void getStateInformation (MemoryBlock& destData) override;
void setStateInformation (const void* data, int sizeInBytes) override;
- bool compareFunction(juce::File a, juce::File b);
int getModelIndex(float model_param);
+ int getIrIndex(float ir_param);
void loadConfig(File configFile);
+ void loadIR(File irFile);
void setupDataDirectories();
void installTones();
void set_ampEQ(float bass_slider, float mid_slider, float treble_slider, float presence_slider);
- // Overdrive Pedal
float convertLogScale(float in_value, float x_min, float x_max, float y_min, float y_max);
- // Amp
- /*
- void set_ampDrive(float db_ampCleanDrive);
- void set_ampMaster(float db_ampMaster);
- void set_ampEQ(float bass_slider, float mid_slider, float treble_slider, float presence_slider);
- */
float decibelToLinear(float dbValue);
void addDirectory(const File& file);
+ void addDirectoryIR(const File& file);
void resetDirectory(const File& file);
+ void resetDirectoryIR(const File& file);
std::vector<File> jsonFiles;
+ std::vector<File> irFiles;
File currentDirectory = File::getCurrentWorkingDirectory().getFullPathName();
File userAppDataDirectory = File::getSpecialLocation(File::userDocumentsDirectory).getChildFile(JucePlugin_Manufacturer).getChildFile(JucePlugin_Name);
File userAppDataDirectory_tones = userAppDataDirectory.getFullPathName() + "/tones";
+ File userAppDataDirectory_irs = userAppDataDirectory.getFullPathName() + "/irs";
// Pedal/amp states
int amp_state = 1; // 0 = off, 1 = on
@@ -111,6 +112,16 @@ public:
int current_model_index = 0;
float num_models = 0.0;
int model_index = 0; // Used in processBlock when converting slider param to model index
+ bool lstm_state = true;
+
+ juce::String loaded_ir_name;
+ float num_irs = 0.0;
+ int ir_loaded = 0;
+ 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;
@@ -125,9 +136,13 @@ private:
AudioParameterFloat* trebleParam;
AudioParameterFloat* presenceParam;
AudioParameterFloat* modelParam;
+ AudioParameterFloat* irParam;
dsp::IIR::Filter<float> dcBlocker;
+ // IR processing
+ CabSim cabSimIR;
+
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralPiAudioProcessor)
};
diff --git a/resources/cassette_recorder.wav b/resources/cassette_recorder.wav
Binary files differ.
diff --git a/resources/guitar_amp.wav b/resources/guitar_amp.wav
Binary files differ.
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.
::############################################################################
@@ -15,17 +15,26 @@
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"
+:: Typical Windows 10 Path (shouldn't need to change)
+set "host_model_path=%userprofile%/Documents/GuitarML/NeuralPi/tones"
+set "host_ir_path=%userprofile%/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/