Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Poly octave updates #357

Merged
merged 6 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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