Skip to content

Commit

Permalink
Poly octave updates (#357)
Browse files Browse the repository at this point in the history
* Setting up v1 mode

* Poly octave working with new filter bank

* Re-tuning filterbank params

* Update CHANGELOG

* Apply clang-format

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] committed Apr 30, 2024
1 parent 3562e3c commit cb19dc5
Show file tree
Hide file tree
Showing 7 changed files with 389 additions and 75 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

All notable changes to this project will be documented in this file.

## [UNRELEASED]
- Updated "Poly Octave" DSP to sound cleaner and use less CPU

## [1.3.0] - 2024-03-15
- Added "Laser Trem" module.
- Added "Poly Octave" module.
Expand Down
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ if(WIN32)
set(CMAKE_SYSTEM_VERSION 7.1 CACHE STRING INTERNAL FORCE) # Windows SDK for Windows 7 and up
endif()
if(APPLE AND (CMAKE_SYSTEM_NAME STREQUAL "iOS"))
project(BYOD VERSION 2.1.0)
project(BYOD VERSION 2.1.1)
else()
project(BYOD VERSION 1.3.0)
project(BYOD VERSION 1.3.1)
endif()
set(CMAKE_CXX_STANDARD 20)

Expand Down
5 changes: 3 additions & 2 deletions res/presets/OctaVerb.chowpreset
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<?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">
<Preset name="OctaVerb" plugin="BYOD" vendor="CHOW" category="Pedals" version="1.3.1">
<proc_chain state_plugin_version="1.3.1">
<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"/>
<PARAM id="v1_mode" value="0.0"/>
</Parameters>
<port_0 connection_0="3" connection_end_0="0"/>
</Poly_Octave>
Expand Down
188 changes: 141 additions & 47 deletions src/processors/other/poly_octave/PolyOctave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const String dryTag = "dry";
const String upOctaveTag = "up_octave";
const String up2OctaveTag = "up2_octave";
const String downOctaveTag = "down_octave";
const String v1Tag = "v1_mode";
} // namespace PolyOctaveTags

PolyOctave::PolyOctave (UndoManager* um)
Expand All @@ -34,6 +35,9 @@ PolyOctave::PolyOctave (UndoManager* um)
setupGainParam (PolyOctaveTags::upOctaveTag, upOctaveGain);
setupGainParam (PolyOctaveTags::up2OctaveTag, up2OctaveGain);

ParameterHelpers::loadParameterPointer (v1_mode, vts, PolyOctaveTags::v1Tag);
addPopupMenuParameter (PolyOctaveTags::v1Tag);

uiOptions.backgroundColour = Colour { 0xff9fa09d };
uiOptions.powerColour = Colour { 0xffe70510 };
uiOptions.info.description = "A \"polyphonic octave generator\" effect.";
Expand All @@ -49,6 +53,7 @@ ParamLayout PolyOctave::createParameterLayout()
createPercentParameter (params, PolyOctaveTags::dryTag, "+0 Oct", 0.5f);
createPercentParameter (params, PolyOctaveTags::upOctaveTag, "+1 Oct", 0.0f);
createPercentParameter (params, PolyOctaveTags::up2OctaveTag, "+2 Oct", 0.0f);
emplace_param<chowdsp::BoolParameter> (params, PolyOctaveTags::v1Tag, "V1 Mode", false); // @TODO: version streaming

return { params.begin(), params.end() };
}
Expand All @@ -61,14 +66,22 @@ void PolyOctave::prepare (double sampleRate, int samplesPerBlock)
downOctaveGain.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
downOctaveBuffer.setMaxSize (2, samplesPerBlock);

FilterBankHelpers::designFilterBank (octaveUpFilterBank, 2.0, 5.0, 6.0, sampleRate);
FilterBankHelpers::designFilterBank (octaveUp2FilterBank, 3.0, 7.0, 4.0, sampleRate);
up2OctaveBuffer_double.setMaxSize (2, 2 * samplesPerBlock); // allocate extra space for SIMD
upOctaveBuffer_double.setMaxSize (2, 2 * samplesPerBlock); // allocate extra space for SIMD
downOctaveBuffer_double.setMaxSize (2, samplesPerBlock);

poly_octave_v2::design_filter_bank<poly_octave_v2::N1> (octaveUpFilterBank, 2.0, 5.0, 4.5, sampleRate);
poly_octave_v2::design_filter_bank<poly_octave_v2::N1> (octaveUp2FilterBank, 3.0, 6.0, 2.75, sampleRate);
for (auto& shifter : downOctavePitchShifters)
{
shifter.prepare (sampleRate);
shifter.set_pitch_factor (0.5f);
}

poly_octave_v1::designFilterBank (octaveUpFilterBank_v1, 2.0, 5.0, 6.0, sampleRate);
poly_octave_v1::designFilterBank (octaveUp2FilterBank_v1, 3.0, 7.0, 4.0, sampleRate);

for (auto& shifter : downOctavePitchShifters_v1)
{
shifter.prepare (sampleRate);
shifter.set_pitch_factor (0.5);
Expand All @@ -81,51 +94,117 @@ void PolyOctave::prepare (double sampleRate, int samplesPerBlock)
}

mixOutBuffer.setSize (2, samplesPerBlock);
up1OutBuffer.setSize (2, samplesPerBlock);
up2OutBuffer.setSize (2, samplesPerBlock);
down1OutBuffer.setSize (2, samplesPerBlock);
up1OutBuffer.setSize (2, 4 * samplesPerBlock + 8); // padding for SIMD
up2OutBuffer.setSize (2, 4 * samplesPerBlock + 8); // padding for SIMD
down1OutBuffer.setSize (2, 4 * samplesPerBlock + 8); // padding for SIMD
}

void PolyOctave::processAudio (AudioBuffer<float>& buffer)
{
if (v1_mode->get())
{
processAudioV1 (buffer);
return;
}

const auto numChannels = buffer.getNumChannels();
const auto numSamples = buffer.getNumSamples();

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

// "down" processing
for (auto [ch, data_in, data_out] : chowdsp::buffer_iters::zip_channels (std::as_const (buffer), down1OutBuffer))
{
for (auto [x, y] : chowdsp::zip (data_in, data_out))
y = downOctavePitchShifters[(size_t) ch].process_sample (x);
}
downOctaveGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (down1OutBuffer, downOctaveGain);

// "up1" processing
for (auto [ch, data_in, data_out] : chowdsp::buffer_iters::zip_channels (std::as_const (buffer), up1OutBuffer))
{
poly_octave_v2::process<1> (octaveUpFilterBank[ch],
data_in.data(),
data_out.data(),
numSamples);
}
upOctaveGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (up1OutBuffer, upOctaveGain);

// "up2" processing
for (auto [ch, data_in, data_out] : chowdsp::buffer_iters::zip_channels (std::as_const (buffer), up2OutBuffer))
{
poly_octave_v2::process<2> (octaveUp2FilterBank[ch],
data_in.data(),
data_out.data(),
numSamples);
}
up2OctaveGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (up2OutBuffer, up2OctaveGain);

chowdsp::BufferMath::copyBufferData (buffer, mixOutBuffer);
dryGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (mixOutBuffer, dryGain);

chowdsp::BufferMath::addBufferData (up1OutBuffer, mixOutBuffer);
chowdsp::BufferMath::addBufferData (up2OutBuffer, mixOutBuffer);
chowdsp::BufferMath::addBufferData (down1OutBuffer, mixOutBuffer);

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

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

void PolyOctave::processAudioV1 (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);
downOctaveBuffer.setCurrentSize (numChannels, numSamples);
upOctaveBuffer_double.setCurrentSize (numChannels, numSamples);
up2OctaveBuffer_double.setCurrentSize (numChannels, numSamples);
downOctaveBuffer_double.setCurrentSize (numChannels, numSamples);

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

// "down" processing
for (auto [ch, data_in, data_out] : chowdsp::buffer_iters::zip_channels (std::as_const (doubleBuffer), downOctaveBuffer))
for (auto [ch, data_in, data_out] : chowdsp::buffer_iters::zip_channels (std::as_const (doubleBuffer), downOctaveBuffer_double))
{
for (auto [x, y] : chowdsp::zip (data_in, data_out))
y = downOctavePitchShifters[(size_t) ch].process_sample (x);
y = downOctavePitchShifters_v1[(size_t) ch].process_sample (x);
}

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

auto* upData = upOctaveBuffer.getWritePointer (ch);
auto* upDataSIMD = reinterpret_cast<float_2*> (upOctaveBuffer.getWritePointer (ch));
auto* upData = upOctaveBuffer_double.getWritePointer (ch);
auto* upDataSIMD = reinterpret_cast<float_2*> (upOctaveBuffer_double.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));
auto* up2Data = up2OctaveBuffer_double.getWritePointer (ch);
auto* up2DataSIMD = reinterpret_cast<float_2*> (up2OctaveBuffer_double.getWritePointer (ch));
jassert (juce::snapPointerToAlignment (up2DataSIMD, xsimd::default_arch::alignment()) == up2DataSIMD);
std::fill (up2DataSIMD, up2DataSIMD + numSamples, float_2 {});

auto& upFilterBank = octaveUpFilterBank[static_cast<size_t> (ch)];
auto& up2FilterBank = octaveUp2FilterBank[static_cast<size_t> (ch)];
auto& upFilterBank = octaveUpFilterBank_v1[static_cast<size_t> (ch)];
auto& up2FilterBank = octaveUp2FilterBank_v1[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)
for (size_t k = 0; k < poly_octave_v1::ComplexERBFilterBank::numFilterBands; k += float_2::size)
{
const auto filter_idx = k / float_2::size;
auto& realFilter = upFilterBank.erbFilterReal[filter_idx];
Expand All @@ -134,8 +213,8 @@ void PolyOctave::processAudio (AudioBuffer<float>& buffer)
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 = poly_octave_v1::processSample (realFilter, z_re.get(), dryData[n]);
auto x_im = poly_octave_v1::processSample<true> (imagFilter, z_im.get(), dryData[n]);

auto x_re_sq = x_re * x_re;
auto x_im_sq = x_im * x_im;
Expand All @@ -153,7 +232,7 @@ void PolyOctave::processAudio (AudioBuffer<float>& buffer)
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)
for (size_t k = 0; k < poly_octave_v1::ComplexERBFilterBank::numFilterBands; k += float_2::size)
{
const auto filter_idx = k / float_2::size;
auto& realFilter = up2FilterBank.erbFilterReal[filter_idx];
Expand All @@ -163,8 +242,8 @@ void PolyOctave::processAudio (AudioBuffer<float>& buffer)

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 = poly_octave_v1::processSample (realFilter, z_re.get(), dryData[n]);
auto x_im = poly_octave_v1::processSample<true> (imagFilter, z_im.get(), dryData[n]);

auto x_re_sq = x_re * x_re;
auto x_im_sq = x_im * x_im;
Expand All @@ -178,33 +257,32 @@ void PolyOctave::processAudio (AudioBuffer<float>& buffer)
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);

chowdsp::BufferMath::applyGain (downOctaveBuffer, juce::Decibels::decibelsToGain (3.0f));
downOctaveGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (downOctaveBuffer, downOctaveGain);

dryGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (doubleBuffer, dryGain);
chowdsp::BufferMath::addBufferData (up2OctaveBuffer, doubleBuffer);
chowdsp::BufferMath::addBufferData (upOctaveBuffer, doubleBuffer);
chowdsp::BufferMath::addBufferData (downOctaveBuffer, doubleBuffer);
chowdsp::BufferMath::applyGain (upOctaveBuffer_double, 2.0 / static_cast<double> (poly_octave_v1::ComplexERBFilterBank::numFilterBands));
chowdsp::BufferMath::applyGain (up2OctaveBuffer_double, 2.0 / static_cast<double> (poly_octave_v1::ComplexERBFilterBank::numFilterBands));
chowdsp::BufferMath::applyGain (downOctaveBuffer_double, juce::Decibels::decibelsToGain (3.0f));

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

chowdsp::BufferMath::copyBufferData (doubleBuffer, mixOutBuffer);
chowdsp::BufferMath::copyBufferData (upOctaveBuffer, up1OutBuffer);
chowdsp::BufferMath::copyBufferData (up2OctaveBuffer, up2OutBuffer);
chowdsp::BufferMath::copyBufferData (downOctaveBuffer, down1OutBuffer);
chowdsp::BufferMath::copyBufferData (upOctaveBuffer_double, up1OutBuffer);
chowdsp::BufferMath::copyBufferData (up2OctaveBuffer_double, up2OutBuffer);
chowdsp::BufferMath::copyBufferData (downOctaveBuffer_double, down1OutBuffer);

upOctaveGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (up1OutBuffer, upOctaveGain);
up2OctaveGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (up2OutBuffer, up2OctaveGain);
downOctaveGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (down1OutBuffer, downOctaveGain);

dryGain.process (numSamples);
chowdsp::BufferMath::applyGainSmoothedBuffer (mixOutBuffer, dryGain);
chowdsp::BufferMath::addBufferData (up1OutBuffer, mixOutBuffer);
chowdsp::BufferMath::addBufferData (up2OutBuffer, mixOutBuffer);
chowdsp::BufferMath::addBufferData (down1OutBuffer, mixOutBuffer);

dcBlocker[MixOutput].processBlock (mixOutBuffer);
dcBlocker[Up1Output].processBlock (up1OutBuffer);
Expand Down Expand Up @@ -256,3 +334,19 @@ String PolyOctave::getTooltipForPort (int portIndex, bool isInput)

return BaseProcessor::getTooltipForPort (portIndex, isInput);
}

void PolyOctave::fromXML (XmlElement* xml, const chowdsp::Version& version, bool loadPosition)
{
BaseProcessor::fromXML (xml, version, loadPosition);

using namespace std::string_view_literals;
#if JUCE_IOS
if (version <= chowdsp::Version { "2.1.0"sv })
#else
if (version <= chowdsp::Version { "1.3.0"sv })
#endif
{
// For versions before 1.3.0, default to v1 mode
v1_mode->setValueNotifyingHost (1.0f);
}
}
41 changes: 23 additions & 18 deletions src/processors/other/poly_octave/PolyOctave.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "DelayPitchShifter.h"
#include "PolyOctaveFilterBankTypes.h"
#include "processors/BaseProcessor.h"

class PolyOctave : public BaseProcessor
Expand All @@ -15,14 +16,9 @@ class PolyOctave : public BaseProcessor
void processAudio (AudioBuffer<float>& buffer) override;
void processAudioBypassed (AudioBuffer<float>& buffer) override;

String getTooltipForPort (int portIndex, bool isInput) override;
void fromXML (XmlElement* xml, const chowdsp::Version& version, bool loadPosition) 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;
};
String getTooltipForPort (int portIndex, bool isInput) override;

enum OutputPort
{
Expand All @@ -35,19 +31,28 @@ class PolyOctave : public BaseProcessor
static constexpr auto numOutputs = (int) magic_enum::enum_count<OutputPort>();

private:
chowdsp::SmoothedBufferValue<double> dryGain {};
chowdsp::SmoothedBufferValue<double> upOctaveGain {};
chowdsp::SmoothedBufferValue<double> up2OctaveGain {};
chowdsp::SmoothedBufferValue<double> downOctaveGain {};
void processAudioV1 (AudioBuffer<float>& buffer);

chowdsp::Buffer<double> doubleBuffer;
chowdsp::Buffer<double> upOctaveBuffer;
chowdsp::Buffer<double> up2OctaveBuffer;
chowdsp::Buffer<double> downOctaveBuffer;
chowdsp::BoolParameter* v1_mode = nullptr;

chowdsp::SmoothedBufferValue<float> dryGain {};
chowdsp::SmoothedBufferValue<float> upOctaveGain {};
chowdsp::SmoothedBufferValue<float> up2OctaveGain {};
chowdsp::SmoothedBufferValue<float> downOctaveGain {};

std::array<ComplexERBFilterBank, 2> octaveUpFilterBank;
std::array<ComplexERBFilterBank, 2> octaveUp2FilterBank;
std::array<pitch_shift::Processor<double>, 2> downOctavePitchShifters;
// V2 processing stuff...
std::array<poly_octave_v2::ComplexERBFilterBank<poly_octave_v2::N1>, 2> octaveUpFilterBank;
std::array<poly_octave_v2::ComplexERBFilterBank<poly_octave_v2::N1>, 2> octaveUp2FilterBank;
std::array<pitch_shift::Processor<float>, 2> downOctavePitchShifters;

// V1 processing stuff...
chowdsp::Buffer<double> doubleBuffer;
chowdsp::Buffer<double> upOctaveBuffer_double;
chowdsp::Buffer<double> up2OctaveBuffer_double;
chowdsp::Buffer<double> downOctaveBuffer_double;
std::array<poly_octave_v1::ComplexERBFilterBank, 2> octaveUpFilterBank_v1;
std::array<poly_octave_v1::ComplexERBFilterBank, 2> octaveUp2FilterBank_v1;
std::array<pitch_shift::Processor<double>, 2> downOctavePitchShifters_v1;

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

Expand Down
Loading

0 comments on commit cb19dc5

Please sign in to comment.