Skip to content

Commit

Permalink
Add WidthPanner processor class (#483)
Browse files Browse the repository at this point in the history
* Add WidthPanner processor class

* Apply clang-format

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] authored Jan 18, 2024
1 parent 7287433 commit 422eecb
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 22 deletions.
34 changes: 25 additions & 9 deletions modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_Panner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ void Panner<SampleType>::reset()
template <typename SampleType>
void Panner<SampleType>::update()
{
SampleType leftValue, rightValue, boostValue;
SampleType leftValue, rightValue;

auto normalisedPan = static_cast<SampleType> (0.5) * (pan + static_cast<SampleType> (1.0));

Expand All @@ -84,56 +84,72 @@ void Panner<SampleType>::update()
case Rule::balanced:
leftValue = juce::jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - normalisedPan);
rightValue = juce::jmin (static_cast<SampleType> (0.5), normalisedPan);
boostValue = static_cast<SampleType> (2.0);
break;

case Rule::linear:
leftValue = static_cast<SampleType> (1.0) - normalisedPan;
rightValue = normalisedPan;
boostValue = static_cast<SampleType> (2.0);
break;

case Rule::sin3dB:
leftValue = static_cast<SampleType> (std::sin (0.5 * juce::MathConstants<double>::pi * (1.0 - normalisedPan)));
rightValue = static_cast<SampleType> (std::sin (0.5 * juce::MathConstants<double>::pi * normalisedPan));
boostValue = std::sqrt (static_cast<SampleType> (2.0));
break;

case Rule::sin4p5dB:
leftValue = static_cast<SampleType> (std::pow (std::sin (0.5 * juce::MathConstants<double>::pi * (1.0 - normalisedPan)), 1.5));
rightValue = static_cast<SampleType> (std::pow (std::sin (0.5 * juce::MathConstants<double>::pi * normalisedPan), 1.5));
boostValue = static_cast<SampleType> (std::pow (2.0, 3.0 / 4.0));
break;

case Rule::sin6dB:
leftValue = static_cast<SampleType> (std::pow (std::sin (0.5 * juce::MathConstants<double>::pi * (1.0 - normalisedPan)), 2.0));
rightValue = static_cast<SampleType> (std::pow (std::sin (0.5 * juce::MathConstants<double>::pi * normalisedPan), 2.0));
boostValue = static_cast<SampleType> (2.0);
break;

case Rule::squareRoot3dB:
leftValue = std::sqrt (static_cast<SampleType> (1.0) - normalisedPan);
rightValue = std::sqrt (normalisedPan);
boostValue = std::sqrt (static_cast<SampleType> (2.0));
break;

case Rule::squareRoot4p5dB:
leftValue = static_cast<SampleType> (std::pow (std::sqrt (1.0 - normalisedPan), 1.5));
rightValue = static_cast<SampleType> (std::pow (std::sqrt (normalisedPan), 1.5));
boostValue = static_cast<SampleType> (std::pow (2.0, 3.0 / 4.0));
break;

default:
leftValue = juce::jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - normalisedPan);
rightValue = juce::jmin (static_cast<SampleType> (0.5), normalisedPan);
boostValue = static_cast<SampleType> (2.0);
break;
}

const auto boostValue = getBoostForRule (currentRule);
leftVolume.setTargetValue (leftValue * boostValue);
rightVolume.setTargetValue (rightValue * boostValue);
}

template <typename SampleType>
SampleType Panner<SampleType>::getBoostForRule (Rule rule)
{
switch (rule)
{
case Rule::balanced:
case Rule::linear:
case Rule::sin6dB:
return static_cast<SampleType> (2.0);

case Rule::sin3dB:
case Rule::squareRoot3dB:
return std::sqrt (static_cast<SampleType> (2.0));

case Rule::sin4p5dB:
case Rule::squareRoot4p5dB:
return static_cast<SampleType> (std::pow (2.0, 3.0 / 4.0));

default:
return static_cast<SampleType> (2.0);
}
}

//==============================================================================
template class Panner<float>;
template class Panner<double>;
Expand Down
31 changes: 18 additions & 13 deletions modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_Panner.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@

namespace chowdsp
{
/** Panning rules: determine the balance of left/right channels at a given pan setting */
enum class PanningRule
{
linear, /**< regular 6 dB or linear panning rule, allows the panned sound to be
perceived as having a constant level when summed to mono */
balanced, /**< both left and right are 1 when pan value is 0.5, with left decreasing
to 0 above this value and right decreasing to 0 below it */
sin3dB, /**< alternate version of the regular 3 dB panning rule with a sine curve */
sin4p5dB, /**< alternate version of the regular 4.5 dB panning rule with a sine curve */
sin6dB, /**< alternate version of the regular 6 dB panning rule with a sine curve */
squareRoot3dB, /**< regular 3 dB or constant power panning rule, allows the panned sound
to be perceived as having a constant level regardless of the pan position */
squareRoot4p5dB /**< regular 4.5 dB panning rule, a compromise option between 3 dB and 6 dB panning rules */
};

/**
A processor to perform panning operations on stereo buffers.
Expand All @@ -37,19 +52,7 @@ class Panner
{
public:
/** Panning rules: determine the balance of left/right channels at a given pan setting */
enum class Rule
{
linear, /**< regular 6 dB or linear panning rule, allows the panned sound to be
perceived as having a constant level when summed to mono */
balanced, /**< both left and right are 1 when pan value is 0.5, with left decreasing
to 0 above this value and right decreasing to 0 below it */
sin3dB, /**< alternate version of the regular 3 dB panning rule with a sine curve */
sin4p5dB, /**< alternate version of the regular 4.5 dB panning rule with a sine curve */
sin6dB, /**< alternate version of the regular 6 dB panning rule with a sine curve */
squareRoot3dB, /**< regular 3 dB or constant power panning rule, allows the panned sound
to be perceived as having a constant level regardless of the pan position */
squareRoot4p5dB /**< regular 4.5 dB panning rule, a compromise option between 3 dB and 6 dB panning rules */
};
using Rule = PanningRule;

//==============================================================================
/** Constructor. */
Expand Down Expand Up @@ -144,6 +147,8 @@ class Panner
return { x * leftVolume.getNextValue(), x * rightVolume.getNextValue() };
}

static SampleType getBoostForRule (Rule rule);

private:
//==============================================================================
void update();
Expand Down
85 changes: 85 additions & 0 deletions modules/dsp/chowdsp_dsp_utils/Processors/chowdsp_WidthPanner.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#pragma once

#include "chowdsp_Panner.h"

namespace chowdsp
{
/**
* A panner-style processor that affects the "width" of the
* processed signal.
*/
template <typename SampleType>
class WidthPanner
{
public:
WidthPanner()
{
setRule (rule);
setPan (1.0f);
}

/** Sets the panning rule. */
void setRule (PanningRule newRule)
{
rule = newRule;
leftPanner.setRule (rule);
rightPanner.setRule (rule);
}

/** Sets the current panning value, between 1 (stereo), 0 (mono) and -1 (inverted stereo). */
void setPan (SampleType newPan)
{
leftPanner.setPan (-newPan);
rightPanner.setPan (newPan);
}

/** Initialises the processor. */
void prepare (const juce::dsp::ProcessSpec& spec)
{
leftPanner.prepare (spec);
rightPanner.prepare (spec);

leftPanBuffer.setMaxSize (2, static_cast<int> (spec.maximumBlockSize));
}

/** Resets the internal state variables of the processor. */
void reset()
{
leftPanner.reset();
rightPanner.reset();
}

/** Processes a stereo buffer. */
void processBlock (const BufferView<SampleType>& buffer) noexcept
{
jassert (buffer.getNumChannels() == 2);

const auto numSamples = buffer.getNumSamples();
leftPanBuffer.setCurrentSize (2, numSamples);

// copy left signal into both channels of leftPanBuffer
for (int ch = 0; ch < 2; ++ch)
BufferMath::copyBufferChannels (buffer, leftPanBuffer, 0, ch);

// copy right signal into both channels of buffer
BufferMath::copyBufferChannels (buffer, buffer, 1, 0);

leftPanner.processBlock (leftPanBuffer);
rightPanner.processBlock (buffer);

BufferMath::addBufferData (leftPanBuffer, buffer);

BufferMath::applyGain (buffer, static_cast<SampleType> (1) / Panner<SampleType>::getBoostForRule (rule));
}

private:
PanningRule rule = PanningRule::linear;

Panner<SampleType> leftPanner;
Panner<SampleType> rightPanner;

Buffer<SampleType> leftPanBuffer {};

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WidthPanner)
};
} // namespace chowdsp
1 change: 1 addition & 0 deletions modules/dsp/chowdsp_dsp_utils/chowdsp_dsp_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ BEGIN_JUCE_MODULE_DECLARATION
#include "Processors/chowdsp_Gain.h"
#include "Processors/chowdsp_LevelDetector.h"
#include "Processors/chowdsp_Panner.h"
#include "Processors/chowdsp_WidthPanner.h"
#include "Processors/chowdsp_TunerProcessor.h"
#include "Processors/chowdsp_OvershootLimiter.h"

Expand Down
1 change: 1 addition & 0 deletions tests/dsp_tests/chowdsp_dsp_utils_test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ target_sources(chowdsp_dsp_utils_test
TunerTest.cpp
LevelDetectorTest.cpp
OvershootLimiterTest.cpp
WidthPannerTest.cpp

BBDTest.cpp
PitchShiftTest.cpp
Expand Down
68 changes: 68 additions & 0 deletions tests/dsp_tests/chowdsp_dsp_utils_test/WidthPannerTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <CatchUtils.h>
#include <chowdsp_dsp_utils/chowdsp_dsp_utils.h>

TEST_CASE ("Width Panner Test", "[dsp][misc]")
{
const auto spec = juce::dsp::ProcessSpec { 48000.0, 4, 2 };

chowdsp::Buffer<float> buffer { 2, 4 };
for (auto [ch, data] : chowdsp::buffer_iters::channels (buffer))
std::fill (data.begin(), data.end(), ch == 0 ? -1.0f : 1.0f);

const auto testPanner = [spec, &buffer] (chowdsp::PanningRule rule, float pan, auto&& getExpected)
{
chowdsp::WidthPanner<float> panner;
panner.setRule (rule);
panner.setPan (pan);
panner.prepare (spec);
panner.processBlock (buffer);
for (auto [ch, data] : chowdsp::buffer_iters::channels (std::as_const (buffer)))
{
const auto expected = getExpected (ch);
for (auto& x : data)
REQUIRE (x == Catch::Approx { expected }.margin (1.0e-3));
}
};

SECTION ("Stereo -> Mono")
{
testPanner (chowdsp::PanningRule::linear, 0.0f, [] (int)
{ return 0.0f; });
testPanner (chowdsp::PanningRule::sin3dB, 0.0f, [] (int)
{ return 0.0f; });
testPanner (chowdsp::PanningRule::sin6dB, 0.0f, [] (int)
{ return 0.0f; });
}

SECTION ("Stereo -> Stereo")
{
testPanner (chowdsp::PanningRule::linear, 1.0f, [] (int ch)
{ return ch == 0 ? -1.0f : 1.0f; });
testPanner (chowdsp::PanningRule::sin3dB, 1.0f, [] (int ch)
{ return ch == 0 ? -1.0f : 1.0f; });
testPanner (chowdsp::PanningRule::sin6dB, 1.0f, [] (int ch)
{ return ch == 0 ? -1.0f : 1.0f; });
}

SECTION ("Stereo -> Inverse Stereo")
{
testPanner (chowdsp::PanningRule::sin4p5dB, -1.0f, [] (int ch)
{ return ch == 0 ? 1.0f : -1.0f; });
testPanner (chowdsp::PanningRule::squareRoot3dB, -1.0f, [] (int ch)
{ return ch == 0 ? -1.0f : 1.0f; });
testPanner (chowdsp::PanningRule::squareRoot4p5dB, -1.0f, [] (int ch)
{ return ch == 0 ? 1.0f : -1.0f; });
}

SECTION ("Stereo -> Half-Mid (Linear)")
{
testPanner (chowdsp::PanningRule::linear, 0.5f, [] (int ch)
{ return ch == 0 ? -0.5f : 0.5f; });
}

SECTION ("Stereo -> Half-Mid (Sin 6dB)")
{
testPanner (chowdsp::PanningRule::sin6dB, 0.5f, [] (int ch)
{ return ch == 0 ? -0.7071f : 0.7071f; });
}
}

0 comments on commit 422eecb

Please sign in to comment.