Skip to content

Commit

Permalink
Poly Octave: multi-outputs and preset (#348)
Browse files Browse the repository at this point in the history
* Multi-output configuration for Poly Octave

* Re-organizing

* Add OctaVerb preset

* Apply clang-format

* Fix CI failure

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] authored Jan 30, 2024
1 parent 4642f2c commit 6dcae4d
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 174 deletions.
61 changes: 61 additions & 0 deletions res/presets/OctaVerb.chowpreset
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>

<Preset name="OctaVerb" plugin="BYOD" vendor="CHOW" category="Pedals" version="1.2.2">
<proc_chain state_plugin_version="1.2.2">
<Poly_Octave>
<Parameters x_pos="0.04100000113248825" y_pos="0.505660355091095">
<PARAM id="dry" value="0.0"/>
<PARAM id="on_off" value="1.0"/>
<PARAM id="up2_octave" value="0.5"/>
<PARAM id="up_octave" value="1.0"/>
</Parameters>
<port_0 connection_0="3" connection_end_0="0"/>
</Poly_Octave>
<Mixer>
<Parameters x_pos="0.4199999868869781" y_pos="0.05471698194742203">
<PARAM id="gain0" value="0.0"/>
<PARAM id="gain1" value="0.0"/>
<PARAM id="gain2" value="0.0"/>
<PARAM id="gain3" value="0.0"/>
<PARAM id="on_off" value="1.0"/>
</Parameters>
<port_0 connection_0="-1" connection_end_0="0"/>
</Mixer>
<Smooth_Reverb>
<Parameters x_pos="0.6769999861717224" y_pos="0.5490565896034241">
<PARAM id="decay" value="1996.0751953125"/>
<PARAM id="high_cut" value="5563.21142578125"/>
<PARAM id="low_cut" value="199.9999847412109"/>
<PARAM id="mix" value="1.0"/>
<PARAM id="on_off" value="1.0"/>
<PARAM id="relax" value="1.0"/>
</Parameters>
<port_0 connection_0="1" connection_end_0="1"/>
</Smooth_Reverb>
<Phaser4>
<Parameters x_pos="0.3610000014305115" y_pos="0.5415094494819641">
<PARAM id="depth" value="1.0"/>
<PARAM id="fb_stage" value="0.0"/>
<PARAM id="feedback" value="-1.0"/>
<PARAM id="mix" value="0.25"/>
<PARAM id="on_off" value="1.0"/>
<PARAM id="rate" value="0.4500000476837158"/>
<PARAM id="stereo" value="1.0"/>
<circuit_elements R6="24000.0" C1="4.700000033608376e-8"/>
</Parameters>
<port_0 connection_0="2" connection_end_0="0"/>
</Phaser4>
<Input>
<Parameters x_pos="0.006000000052154064" y_pos="0.03207547217607498">
<PARAM id="on_off" value="1.0"/>
</Parameters>
<port_0 connection_0="0" connection_end_0="0" connection_1="1" connection_end_1="0"/>
</Input>
<Output>
<Parameters x_pos="0.843999981880188" y_pos="0.1396226435899734">
<PARAM id="on_off" value="1.0"/>
</Parameters>
</Output>
</proc_chain>
<extra_info/>
</Preset>
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ target_sources(BYOD PRIVATE
processors/other/LevelDetective.cpp
processors/other/Gate.cpp
processors/other/Octaver.cpp
processors/other/PolyOctave.cpp
processors/other/ShimmerReverb.cpp
processors/other/SmoothReverb.cpp
processors/other/cry_baby/CryBaby.cpp
processors/other/cry_baby/CryBabyNDK.cpp
processors/other/poly_octave/PolyOctave.cpp
processors/other/spring_reverb/SpringReverb.cpp
processors/other/spring_reverb/SpringReverbProcessor.cpp
processors/other/krusher/Krusher.cpp
Expand Down
4 changes: 2 additions & 2 deletions src/processors/ProcessorStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@
#include "other/Gate.h"
#include "other/LevelDetective.h"
#include "other/Octaver.h"
#include "other/PolyOctave.h"
#include "other/ShimmerReverb.h"
#include "other/SmoothReverb.h"
#include "other/cry_baby/CryBaby.h"
#include "other/krusher/Krusher.h"
#include "other/poly_octave/PolyOctave.h"
#include "other/spring_reverb/SpringReverbProcessor.h"

#include "utility/CleanGain.h"
Expand Down Expand Up @@ -154,7 +154,7 @@ ProcessorStore::StoreMap ProcessorStore::store = {
{ "Level Detective", { &processorFactory<LevelDetective>, { ProcessorType::Other, 1, 1 } } },
{ "Gate", { &processorFactory<Gate>, { ProcessorType::Other, 1, 1 } } },
{ "Octaver", { &processorFactory<Octaver>, { ProcessorType::Other, 1, 1 } } },
{ "Poly Octave", { &processorFactory<PolyOctave>, { ProcessorType::Other, 1, 1 } } },
{ "Poly Octave", { &processorFactory<PolyOctave>, { ProcessorType::Other, 1, PolyOctave::numOutputs } } },
{ "Shimmer Reverb", { &processorFactory<ShimmerReverb>, { ProcessorType::Other, 1, 1 } } },
{ "Smooth Reverb", { &processorFactory<SmoothReverb>, { ProcessorType::Other, 1, 1 } } },
{ "Spring Reverb", { &processorFactory<SpringReverbProcessor>, { ProcessorType::Other, 1, 1 } } },
Expand Down
223 changes: 223 additions & 0 deletions src/processors/other/poly_octave/PolyOctave.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#include "PolyOctave.h"
#include "PolyOctaveFilterBandHelpers.h"
#include "processors/ParameterHelpers.h"

namespace PolyOctaveTags
{
const String dryTag = "dry";
const String upOctaveTag = "up_octave";
const String up2OctaveTag = "up2_octave";
} // namespace PolyOctaveTags

PolyOctave::PolyOctave (UndoManager* um)
: BaseProcessor (
"Poly Octave",
createParameterLayout(),
BasicInputPort {},
OutputPort {},
um)
{
using namespace ParameterHelpers;
const auto setupGainParam = [this] (const juce::String& paramID,
auto& gain)
{
gain.setParameterHandle (getParameterPointer<chowdsp::FloatParameter*> (vts, paramID));
gain.setRampLength (0.05);
gain.mappingFunction = [] (auto x)
{
return 2 * x * x;
};
};
setupGainParam (PolyOctaveTags::dryTag, dryGain);
setupGainParam (PolyOctaveTags::upOctaveTag, upOctaveGain);
setupGainParam (PolyOctaveTags::up2OctaveTag, up2OctaveGain);

uiOptions.backgroundColour = Colour { 0xff9fa09d };
uiOptions.powerColour = Colour { 0xffe70510 };
uiOptions.info.description = "A \"polyphonic octave generator\" effect.";
uiOptions.info.authors = StringArray { "Jatin Chowdhury" };
}

ParamLayout PolyOctave::createParameterLayout()
{
using namespace ParameterHelpers;
auto params = createBaseParams();

createPercentParameter (params, PolyOctaveTags::dryTag, "+0 Oct", 0.5f);
createPercentParameter (params, PolyOctaveTags::upOctaveTag, "+1 Oct", 0.0f);
createPercentParameter (params, PolyOctaveTags::up2OctaveTag, "+2 Oct", 0.0f);

return { params.begin(), params.end() };
}

void PolyOctave::prepare (double sampleRate, int samplesPerBlock)
{
dryGain.prepare (sampleRate, samplesPerBlock);
up2OctaveGain.prepare (sampleRate, samplesPerBlock);
upOctaveGain.prepare (sampleRate, samplesPerBlock);

doubleBuffer.setMaxSize (2, samplesPerBlock);
up2OctaveBuffer.setMaxSize (2, static_cast<int> (ComplexERBFilterBank::float_2::size) * samplesPerBlock); // allocate extra space for SIMD
upOctaveBuffer.setMaxSize (2, static_cast<int> (ComplexERBFilterBank::float_2::size) * samplesPerBlock); // allocate extra space for SIMD

FilterBankHelpers::designFilterBank (octaveUpFilterBank, 2.0, 5.0, 6.0, sampleRate);
FilterBankHelpers::designFilterBank (octaveUp2FilterBank, 3.0, 7.0, 4.0, sampleRate);

for (auto& busDCBlocker : dcBlocker)
{
busDCBlocker.prepare (2);
busDCBlocker.calcCoefs (20.0f, (float) sampleRate);
}

mixOutBuffer.setSize (2, samplesPerBlock);
up1OutBuffer.setSize (2, samplesPerBlock);
up2OutBuffer.setSize (2, samplesPerBlock);
}

void PolyOctave::processAudio (AudioBuffer<float>& buffer)
{
const auto numChannels = buffer.getNumChannels();
const auto numSamples = buffer.getNumSamples();

doubleBuffer.setCurrentSize (numChannels, numSamples);
upOctaveBuffer.setCurrentSize (numChannels, numSamples);
up2OctaveBuffer.setCurrentSize (numChannels, numSamples);

chowdsp::BufferMath::copyBufferData (buffer, doubleBuffer);

using float_2 = ComplexERBFilterBank::float_2;
for (int ch = 0; ch < numChannels; ++ch)
{
auto* dryData = doubleBuffer.getReadPointer (ch);

auto* upData = upOctaveBuffer.getWritePointer (ch);
auto* upDataSIMD = reinterpret_cast<float_2*> (upOctaveBuffer.getWritePointer (ch));
jassert (juce::snapPointerToAlignment (upDataSIMD, xsimd::default_arch::alignment()) == upDataSIMD);
std::fill (upDataSIMD, upDataSIMD + numSamples, float_2 {});

auto* up2Data = up2OctaveBuffer.getWritePointer (ch);
auto* up2DataSIMD = reinterpret_cast<float_2*> (up2OctaveBuffer.getWritePointer (ch));
jassert (juce::snapPointerToAlignment (up2DataSIMD, xsimd::default_arch::alignment()) == upDataSIMD);
std::fill (up2DataSIMD, up2DataSIMD + numSamples, float_2 {});

auto& upFilterBank = octaveUpFilterBank[static_cast<size_t> (ch)];
auto& up2FilterBank = octaveUp2FilterBank[static_cast<size_t> (ch)];
static constexpr auto eps = std::numeric_limits<double>::epsilon();

for (size_t k = 0; k < ComplexERBFilterBank::numFilterBands; k += float_2::size)
{
const auto filter_idx = k / float_2::size;
auto& realFilter = upFilterBank.erbFilterReal[filter_idx];
auto& imagFilter = upFilterBank.erbFilterImag[filter_idx];
chowdsp::ScopedValue z_re { realFilter.z[0] };
chowdsp::ScopedValue z_im { imagFilter.z[0] };
for (int n = 0; n < numSamples; ++n)
{
auto x_re = FilterBankHelpers::processSample (realFilter, z_re.get(), dryData[n]);
auto x_im = FilterBankHelpers::processSample<true> (imagFilter, z_im.get(), dryData[n]);

auto x_re_sq = x_re * x_re;
auto x_im_sq = x_im * x_im;
auto x_abs_sq = x_re_sq + x_im_sq;

auto x_abs_r = xsimd::select (x_abs_sq > eps, xsimd::rsqrt (x_abs_sq), {});
upDataSIMD[n] += (x_re_sq - x_im_sq) * x_abs_r;
}
}

// Collapsing the SIMD data into a single "channel".
// This is a little bit dangerous since upData, and upDataSIMD
// are aliased, but it should always be fine because we're "reading"
// faster than we're "writing".
for (int n = 0; n < numSamples; ++n)
upData[n] = xsimd::reduce_add (upDataSIMD[n]);

for (size_t k = 0; k < ComplexERBFilterBank::numFilterBands; k += float_2::size)
{
const auto filter_idx = k / float_2::size;
auto& realFilter = up2FilterBank.erbFilterReal[filter_idx];
auto& imagFilter = up2FilterBank.erbFilterImag[filter_idx];
chowdsp::ScopedValue z_re { realFilter.z[0] };
chowdsp::ScopedValue z_im { imagFilter.z[0] };

for (int n = 0; n < numSamples; ++n)
{
auto x_re = FilterBankHelpers::processSample (realFilter, z_re.get(), dryData[n]);
auto x_im = FilterBankHelpers::processSample<true> (imagFilter, z_im.get(), dryData[n]);

auto x_re_sq = x_re * x_re;
auto x_im_sq = x_im * x_im;
auto x_abs_sq = x_re_sq + x_im_sq;

auto x_abs_sq_r = xsimd::select (x_abs_sq > eps, xsimd::reciprocal (x_abs_sq), {});
up2DataSIMD[n] += x_re * (x_re_sq - 3.0 * x_im_sq) * x_abs_sq_r;
}
}
for (int n = 0; n < numSamples; ++n)
up2Data[n] = xsimd::reduce_add (up2DataSIMD[n]);
}

chowdsp::BufferMath::applyGain (upOctaveBuffer, 2.0 / static_cast<double> (ComplexERBFilterBank::numFilterBands));
upOctaveGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (upOctaveBuffer, upOctaveGain);

chowdsp::BufferMath::applyGain (up2OctaveBuffer, 2.0 / static_cast<double> (ComplexERBFilterBank::numFilterBands));
up2OctaveGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (up2OctaveBuffer, up2OctaveGain);

dryGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (doubleBuffer, dryGain);
chowdsp::BufferMath::addBufferData (up2OctaveBuffer, doubleBuffer);
chowdsp::BufferMath::addBufferData (upOctaveBuffer, doubleBuffer);

mixOutBuffer.setSize (numChannels, numSamples, false, false, true);
up1OutBuffer.setSize (numChannels, numSamples, false, false, true);
up2OutBuffer.setSize (numChannels, numSamples, false, false, true);

chowdsp::BufferMath::copyBufferData (doubleBuffer, mixOutBuffer);
chowdsp::BufferMath::copyBufferData (upOctaveBuffer, up1OutBuffer);
chowdsp::BufferMath::copyBufferData (up2OctaveBuffer, up2OutBuffer);

dcBlocker[MixOutput].processBlock (mixOutBuffer);
dcBlocker[Up1Output].processBlock (up1OutBuffer);
dcBlocker[Up2Output].processBlock (up2OutBuffer);

outputBuffers.getReference (MixOutput) = &mixOutBuffer;
outputBuffers.getReference (Up1Output) = &up1OutBuffer;
outputBuffers.getReference (Up2Output) = &up2OutBuffer;
}

void PolyOctave::processAudioBypassed (AudioBuffer<float>& buffer)
{
const auto numSamples = buffer.getNumSamples();

mixOutBuffer.setSize (buffer.getNumChannels(), numSamples, false, false, true);
up1OutBuffer.setSize (1, numSamples, false, false, true);
up2OutBuffer.setSize (1, numSamples, false, false, true);

chowdsp::BufferMath::copyBufferData (buffer, mixOutBuffer);
up1OutBuffer.clear();
up2OutBuffer.clear();

outputBuffers.getReference (MixOutput) = &mixOutBuffer;
outputBuffers.getReference (Up1Output) = &up1OutBuffer;
outputBuffers.getReference (Up2Output) = &up2OutBuffer;
}

String PolyOctave::getTooltipForPort (int portIndex, bool isInput)
{
if (! isInput)
{
switch ((OutputPort) portIndex)
{
case OutputPort::MixOutput:
return "Mix Output";
case OutputPort::Up1Output:
return "+1 Octave Output";
case OutputPort::Up2Output:
return "+2 Octave Output";
}
}

return BaseProcessor::getTooltipForPort (portIndex, isInput);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,26 @@ class PolyOctave : public BaseProcessor

void prepare (double sampleRate, int samplesPerBlock) override;
void processAudio (AudioBuffer<float>& buffer) override;
void processAudioBypassed (AudioBuffer<float>& buffer) override;

struct ERBFilterBank
String getTooltipForPort (int portIndex, bool isInput) override;

struct ComplexERBFilterBank
{
static constexpr size_t numFilterBands = 44;
using float_2 = xsimd::batch<double>;
std::array<chowdsp::IIRFilter<4, float_2>, numFilterBands / float_2::size> erbFilterReal, erbFilterImag;
};

enum OutputPort
{
MixOutput,
Up1Output,
Up2Output,
};

static constexpr auto numOutputs = (int) magic_enum::enum_count<OutputPort>();

private:
chowdsp::SmoothedBufferValue<double> dryGain {};
chowdsp::SmoothedBufferValue<double> upOctaveGain {};
Expand All @@ -29,10 +41,14 @@ class PolyOctave : public BaseProcessor
chowdsp::Buffer<double> upOctaveBuffer;
chowdsp::Buffer<double> up2OctaveBuffer;

std::array<ERBFilterBank, 2> octaveUpFilterBank;
std::array<ERBFilterBank, 2> octaveUp2FilterBank;
std::array<ComplexERBFilterBank, 2> octaveUpFilterBank;
std::array<ComplexERBFilterBank, 2> octaveUp2FilterBank;

std::array<chowdsp::FirstOrderHPF<float>, (size_t) numOutputs> dcBlocker;

chowdsp::FirstOrderHPF<float> dcBlocker;
juce::AudioBuffer<float> mixOutBuffer;
juce::AudioBuffer<float> up1OutBuffer;
juce::AudioBuffer<float> up2OutBuffer;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PolyOctave)
};
Loading

0 comments on commit 6dcae4d

Please sign in to comment.