Skip to content

Commit

Permalink
Merge pull request #11 from GuitarML/add-delay-reverb
Browse files Browse the repository at this point in the history
Add delay reverb
  • Loading branch information
GuitarML authored Aug 14, 2021
2 parents 7b211c4 + 020352d commit 2c0b9e5
Show file tree
Hide file tree
Showing 9 changed files with 454 additions and 9 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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(NeuralPi VERSION 1.1.0)
project(NeuralPi VERSION 1.2.0)

set(CMAKE_CXX_STANDARD 17)

Expand Down
1 change: 1 addition & 0 deletions NeuralPi.jucer
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<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="gT1tOY" name="Delay.h" compile="0" resource="0" file="Source/Delay.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"
Expand Down
26 changes: 25 additions & 1 deletion Source/AmpOSCReceiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ class AmpOSCReceiver :
return irValue;
}

Value& getDelayValue()
{
return delayValue;
}

Value& getReverbValue()
{
return reverbValue;
}

void changePort (int port)
{
if (! connect (port))
Expand Down Expand Up @@ -90,6 +100,8 @@ class AmpOSCReceiver :
presenceAddressPattern = "/parameter/" + ampName + "/Presence";
modelAddressPattern = "/parameter/" + ampName + "/Model";
irAddressPattern = "/parameter/" + ampName + "/Ir";
reverbAddressPattern = "/parameter/" + ampName + "/Reverb";
delayAddressPattern = "/parameter/" + ampName + "/Delay";
}

void oscMessageReceived(const OSCMessage& message) override
Expand Down Expand Up @@ -134,7 +146,14 @@ class AmpOSCReceiver :
{
irValue.setValue(jlimit(0.0f, 1.0f, message[0].getFloat32()));
}

else if (message.getAddressPattern().matches(reverbAddressPattern))
{
reverbValue.setValue(jlimit(0.0f, 1.0f, message[0].getFloat32()));
}
else if (message.getAddressPattern().matches(delayAddressPattern))
{
delayValue.setValue(jlimit(0.0f, 1.0f, message[0].getFloat32()));
}
}
}

Expand All @@ -149,6 +168,8 @@ class AmpOSCReceiver :
String presenceAddressPattern {"/parameter/elk_juce_example/Presence"};
String modelAddressPattern {"/parameter/elk_juce_example/Model"};
String irAddressPattern {"/parameter/elk_juce_example/Ir"};
String delayAddressPattern {"/parameter/elk_juce_example/Delay"};
String reverbAddressPattern {"/parameter/elk_juce_example/Reverb"};

Value gainValue {0.5f};
Value masterValue {0.5f};
Expand All @@ -160,6 +181,9 @@ class AmpOSCReceiver :
Value modelValue {0.0f};
Value irValue {0.0f};

Value delayValue {0.0f};
Value reverbValue {0.0f};

bool connected = false;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AmpOSCReceiver)
Expand Down
1 change: 1 addition & 0 deletions Source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

target_sources(NeuralPi PRIVATE
AmpOSCReceiver.h
Delay.h
Eq4Band.cpp
Eq4Band.h
PluginEditor.cpp
Expand Down
215 changes: 215 additions & 0 deletions Source/Delay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
==============================================================================
Delay
==============================================================================
*/
#include "../JuceLibraryCode/JuceHeader.h"

#pragma once

//==============================================================================
template <typename Type>
class DelayLine
{
public:
void clear() noexcept
{
std::fill (rawData.begin(), rawData.end(), Type (0));
}

size_t size() const noexcept
{
return rawData.size();
}

void resize (size_t newValue)
{
rawData.resize (newValue);
leastRecentIndex = 0;
}

Type back() const noexcept
{
return rawData[leastRecentIndex];
}

Type get (size_t delayInSamples) const noexcept
{
jassert (delayInSamples >= 0 && delayInSamples < size());

return rawData[(leastRecentIndex + 1 + delayInSamples) % size()]; // [3]
}

/** Set the specified sample in the delay line */
void set (size_t delayInSamples, Type newValue) noexcept
{
jassert (delayInSamples >= 0 && delayInSamples < size());

rawData[(leastRecentIndex + 1 + delayInSamples) % size()] = newValue; // [4]
}

/** Adds a new value to the delay line, overwriting the least recently added sample */
void push (Type valueToAdd) noexcept
{
rawData[leastRecentIndex] = valueToAdd; // [1]
leastRecentIndex = leastRecentIndex == 0 ? size() - 1 : leastRecentIndex - 1; // [2]
}

private:
std::vector<Type> rawData;
size_t leastRecentIndex = 0;
};

//==============================================================================
template <typename Type, size_t maxNumChannels = 2>
class Delay
{
public:
//==============================================================================
Delay()
{
setMaxDelayTime (2.0f);
setDelayTime (0, 0.7f);
setDelayTime (1, 0.5f);
setWetLevel (0.8f);
setFeedback (0.5f);
}

//==============================================================================
void prepare (const juce::dsp::ProcessSpec& spec)
{
jassert (spec.numChannels <= maxNumChannels);
sampleRate = (Type) spec.sampleRate;
updateDelayLineSize();
updateDelayTime();

//filterCoefs = juce::dsp::IIR::Coefficients<Type>::makeFirstOrderLowPass (sampleRate, Type (1e3));
filterCoefs = juce::dsp::IIR::Coefficients<Type>::makeFirstOrderHighPass (sampleRate, Type (1e3));

for (auto& f : filters)
{
f.prepare (spec);
f.coefficients = filterCoefs;
}
}

//==============================================================================
void reset() noexcept
{
for (auto& f : filters)
f.reset(); // [5]

for (auto& dline : delayLines)
dline.clear(); // [6]
}

//==============================================================================
size_t getNumChannels() const noexcept
{
return delayLines.size();
}

//==============================================================================
void setMaxDelayTime (Type newValue)
{
jassert (newValue > Type (0));
maxDelayTime = newValue;
updateDelayLineSize(); // [1]
}

//==============================================================================
void setFeedback (Type newValue) noexcept
{
jassert (newValue >= Type (0) && newValue <= Type (1));
feedback = newValue;
}

//==============================================================================
void setWetLevel (Type newValue) noexcept
{
jassert (newValue >= Type (0) && newValue <= Type (1));
wetLevel = newValue;
}

//==============================================================================
void setDelayTime (size_t channel, Type newValue)
{
if (channel >= getNumChannels())
{
jassertfalse;
return;
}

jassert (newValue >= Type (0));
delayTimes[channel] = newValue;

updateDelayTime(); // [3]
}

//==============================================================================
template <typename ProcessContext>
void process (const ProcessContext& context) noexcept
{
auto& inputBlock = context.getInputBlock();
auto& outputBlock = context.getOutputBlock();
auto numSamples = outputBlock.getNumSamples();
auto numChannels = outputBlock.getNumChannels();

jassert (inputBlock.getNumSamples() == numSamples);
jassert (inputBlock.getNumChannels() == numChannels);

for (size_t ch = 0; ch < numChannels; ++ch)
{
auto* input = inputBlock .getChannelPointer (ch);
auto* output = outputBlock.getChannelPointer (ch);
auto& dline = delayLines[ch];
auto delayTime = delayTimesSample[ch];
auto& filter = filters[ch];

for (size_t i = 0; i < numSamples; ++i)
{
//auto delayedSample = dline.get (delayTime);
auto delayedSample = filter.processSample (dline.get (delayTime));
auto inputSample = input[i];
auto dlineInputSample = std::tanh (inputSample + feedback * delayedSample);
dline.push (dlineInputSample);
auto outputSample = inputSample + wetLevel * delayedSample;
output[i] = outputSample;
}
}
}

private:
//==============================================================================
std::array<DelayLine<Type>, maxNumChannels> delayLines;
std::array<size_t, maxNumChannels> delayTimesSample;
std::array<Type, maxNumChannels> delayTimes;
Type feedback { Type (0) };
Type wetLevel { Type (0) };

std::array<juce::dsp::IIR::Filter<Type>, maxNumChannels> filters;
typename juce::dsp::IIR::Coefficients<Type>::Ptr filterCoefs;

Type sampleRate { Type (44.1e3) };
Type maxDelayTime { Type (2) };

//==============================================================================
void updateDelayLineSize()
{
auto delayLineSizeSamples = (size_t) std::ceil (maxDelayTime * sampleRate);

for (auto& dline : delayLines)
dline.resize (delayLineSizeSamples); // [2]
}

//==============================================================================
void updateDelayTime() noexcept
{
for (size_t ch = 0; ch < maxNumChannels; ++ch)
delayTimesSample[ch] = (size_t) juce::roundToInt (delayTimes[ch] * sampleRate);
}

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Delay)
};
Loading

0 comments on commit 2c0b9e5

Please sign in to comment.