diff --git a/modules/cmake/WarningFlags.cmake b/modules/cmake/WarningFlags.cmake index b657f7e6..e049e6d8 100644 --- a/modules/cmake/WarningFlags.cmake +++ b/modules/cmake/WarningFlags.cmake @@ -43,6 +43,7 @@ if(WIN32) -Wno-implicit-int-float-conversion -Wno-implicit-const-int-float-conversion -Wno-unsafe-buffer-usage + -Wno-unknown-warning-option -Wno-header-hygiene ) elseif((CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") OR (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")) diff --git a/src/processors/other/poly_octave/DelayPitchShifter.h b/src/processors/other/poly_octave/DelayPitchShifter.h new file mode 100644 index 00000000..9de03f80 --- /dev/null +++ b/src/processors/other/poly_octave/DelayPitchShifter.h @@ -0,0 +1,89 @@ +#pragma once + +#include + +namespace pitch_shift +{ +template +struct Delay +{ + std::vector delay_buffer {}; + size_t write_pointer = 0; + T read_pointer {}; + size_t N_int = 0; + T N {}; + T phase_offset_01 {}; + T read_pointer_increment = (T) 1; + + void prepare (double sample_rate, T initial_phase_offset_01 = {}) + { + N_int = static_cast (sample_rate * 0.075); + delay_buffer.resize (2 * N_int, T {}); + N = static_cast (N_int); + write_pointer = 0; + + phase_offset_01 = initial_phase_offset_01; + read_pointer = N * phase_offset_01; + } + + void reset() + { + std::fill (delay_buffer.begin(), delay_buffer.end(), T {}); + write_pointer = 0; + read_pointer = N * phase_offset_01; + } + + T get_phase_01() const noexcept + { + const auto offset = static_cast (write_pointer); + const auto wrapped_offset = read_pointer >= offset ? offset : offset - N; + return (read_pointer - wrapped_offset) / N; + } + + T process_sample (T x) noexcept + { + delay_buffer[write_pointer] = x; + delay_buffer[write_pointer + N_int] = x; + + const auto rp_int = static_cast (read_pointer); + const auto alpha = read_pointer - static_cast (rp_int); + const auto y = ((T) 1 - alpha) * delay_buffer[rp_int] + alpha * delay_buffer[rp_int + 1]; + + read_pointer = std::fmod (read_pointer + read_pointer_increment, N); + write_pointer = (write_pointer + 1) % N_int; + + const auto amplitude_mod = (T) 0.5 * ((T) 1 - math_approx::cos<5> (juce::MathConstants::twoPi * get_phase_01())); + return y * amplitude_mod; + } +}; + +template +struct Processor +{ + Delay d_0 {}; + Delay d_half {}; + + void prepare (double sample_rate) + { + d_0.prepare (sample_rate); + d_half.prepare (sample_rate, (T) 0.5); + } + + void reset() + { + d_0.reset(); + d_half.reset(); + } + + void set_pitch_factor (T shift_factor) + { + d_0.read_pointer_increment = shift_factor; + d_half.read_pointer_increment = shift_factor; + } + + T process_sample (T x) noexcept + { + return d_0.process_sample (x) + d_half.process_sample (x); + } +}; +} // namespace pitch_shift diff --git a/src/processors/other/poly_octave/PolyOctave.cpp b/src/processors/other/poly_octave/PolyOctave.cpp index d16b0a6e..99c9ab11 100644 --- a/src/processors/other/poly_octave/PolyOctave.cpp +++ b/src/processors/other/poly_octave/PolyOctave.cpp @@ -7,6 +7,7 @@ namespace PolyOctaveTags const String dryTag = "dry"; const String upOctaveTag = "up_octave"; const String up2OctaveTag = "up2_octave"; +const String downOctaveTag = "down_octave"; } // namespace PolyOctaveTags PolyOctave::PolyOctave (UndoManager* um) @@ -28,6 +29,7 @@ PolyOctave::PolyOctave (UndoManager* um) return 2 * x * x; }; }; + setupGainParam (PolyOctaveTags::downOctaveTag, downOctaveGain); setupGainParam (PolyOctaveTags::dryTag, dryGain); setupGainParam (PolyOctaveTags::upOctaveTag, upOctaveGain); setupGainParam (PolyOctaveTags::up2OctaveTag, up2OctaveGain); @@ -43,6 +45,7 @@ ParamLayout PolyOctave::createParameterLayout() using namespace ParameterHelpers; auto params = createBaseParams(); + createPercentParameter (params, PolyOctaveTags::downOctaveTag, "-1 Oct", 0.0f); createPercentParameter (params, PolyOctaveTags::dryTag, "+0 Oct", 0.5f); createPercentParameter (params, PolyOctaveTags::upOctaveTag, "+1 Oct", 0.0f); createPercentParameter (params, PolyOctaveTags::up2OctaveTag, "+2 Oct", 0.0f); @@ -55,14 +58,22 @@ void PolyOctave::prepare (double sampleRate, int samplesPerBlock) dryGain.prepare (sampleRate, samplesPerBlock); up2OctaveGain.prepare (sampleRate, samplesPerBlock); upOctaveGain.prepare (sampleRate, samplesPerBlock); + downOctaveGain.prepare (sampleRate, samplesPerBlock); doubleBuffer.setMaxSize (2, samplesPerBlock); up2OctaveBuffer.setMaxSize (2, static_cast (ComplexERBFilterBank::float_2::size) * samplesPerBlock); // allocate extra space for SIMD upOctaveBuffer.setMaxSize (2, static_cast (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); + for (auto& shifter : downOctavePitchShifters) + { + shifter.prepare (sampleRate); + shifter.set_pitch_factor (0.5); + } + for (auto& busDCBlocker : dcBlocker) { busDCBlocker.prepare (2); @@ -72,6 +83,7 @@ void PolyOctave::prepare (double sampleRate, int samplesPerBlock) mixOutBuffer.setSize (2, samplesPerBlock); up1OutBuffer.setSize (2, samplesPerBlock); up2OutBuffer.setSize (2, samplesPerBlock); + down1OutBuffer.setSize (2, samplesPerBlock); } void PolyOctave::processAudio (AudioBuffer& buffer) @@ -82,9 +94,18 @@ void PolyOctave::processAudio (AudioBuffer& buffer) doubleBuffer.setCurrentSize (numChannels, numSamples); upOctaveBuffer.setCurrentSize (numChannels, numSamples); up2OctaveBuffer.setCurrentSize (numChannels, numSamples); + downOctaveBuffer.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 [x, y] : chowdsp::zip (data_in, data_out)) + y = downOctavePitchShifters[(size_t) ch].process_sample (x); + } + + // "up" processing using float_2 = ComplexERBFilterBank::float_2; for (int ch = 0; ch < numChannels; ++ch) { @@ -165,26 +186,35 @@ void PolyOctave::processAudio (AudioBuffer& buffer) 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); 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); 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::processAudioBypassed (AudioBuffer& buffer) @@ -194,14 +224,17 @@ void PolyOctave::processAudioBypassed (AudioBuffer& buffer) mixOutBuffer.setSize (buffer.getNumChannels(), numSamples, false, false, true); up1OutBuffer.setSize (1, numSamples, false, false, true); up2OutBuffer.setSize (1, numSamples, false, false, true); + down1OutBuffer.setSize (1, numSamples, false, false, true); chowdsp::BufferMath::copyBufferData (buffer, mixOutBuffer); up1OutBuffer.clear(); up2OutBuffer.clear(); + down1OutBuffer.clear(); outputBuffers.getReference (MixOutput) = &mixOutBuffer; outputBuffers.getReference (Up1Output) = &up1OutBuffer; outputBuffers.getReference (Up2Output) = &up2OutBuffer; + outputBuffers.getReference (Down1Output) = &down1OutBuffer; } String PolyOctave::getTooltipForPort (int portIndex, bool isInput) @@ -216,6 +249,8 @@ String PolyOctave::getTooltipForPort (int portIndex, bool isInput) return "+1 Octave Output"; case OutputPort::Up2Output: return "+2 Octave Output"; + case OutputPort::Down1Output: + return "-1 Octave Output"; } } diff --git a/src/processors/other/poly_octave/PolyOctave.h b/src/processors/other/poly_octave/PolyOctave.h index 9f2230b9..08ebf96c 100644 --- a/src/processors/other/poly_octave/PolyOctave.h +++ b/src/processors/other/poly_octave/PolyOctave.h @@ -1,5 +1,6 @@ #pragma once +#include "DelayPitchShifter.h" #include "processors/BaseProcessor.h" class PolyOctave : public BaseProcessor @@ -26,8 +27,9 @@ class PolyOctave : public BaseProcessor enum OutputPort { MixOutput, - Up1Output, Up2Output, + Up1Output, + Down1Output, }; static constexpr auto numOutputs = (int) magic_enum::enum_count(); @@ -36,19 +38,23 @@ class PolyOctave : public BaseProcessor chowdsp::SmoothedBufferValue dryGain {}; chowdsp::SmoothedBufferValue upOctaveGain {}; chowdsp::SmoothedBufferValue up2OctaveGain {}; + chowdsp::SmoothedBufferValue downOctaveGain {}; chowdsp::Buffer doubleBuffer; chowdsp::Buffer upOctaveBuffer; chowdsp::Buffer up2OctaveBuffer; + chowdsp::Buffer downOctaveBuffer; std::array octaveUpFilterBank; std::array octaveUp2FilterBank; + std::array, 2> downOctavePitchShifters; std::array, (size_t) numOutputs> dcBlocker; juce::AudioBuffer mixOutBuffer; juce::AudioBuffer up1OutBuffer; juce::AudioBuffer up2OutBuffer; + juce::AudioBuffer down1OutBuffer; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PolyOctave) };