Skip to content

Stereo effects

Tim Sharii edited this page Sep 3, 2021 · 4 revisions

All stereo effects inherit from abstract class StereoEffect. Like AudioEffect, this class inherits from WetDryMixer with properties Wet and Dry. Wet-dry mixing functionality is the same as described here.

In other aspects StereoEffect is different, since this is a special kind of audio processors that produce stereo data (left channel data + right channel data) based on either one signal (mono) or two signals (stereo, left and right channels).

Hence, the family of Process() and ApplyTo() methods have different prototypes:

// Process two channels : [ input left , input right ] -> [ output left , output right ]
void Process(ref float left, ref float right);

// Process mono channel : input sample -> [ output left , output right ]
void Process(float sample, out float left, out float right);

// Process two channels (in blocks) : [ input left , input right ] -> [ output left , output right ]
void Process(float[] inputLeft,
             float[] inputRight,
             float[] outputLeft,
             float[] outputRight,
             int count = 0,
             int inputPos = 0,
             int outputPos = 0);

// Process mono channel (in blocks) : [ input ] -> [ output left , output right ]
void Process(float[] input,
             float[] outputLeft,
             float[] outputRight,
             int count = 0,
             int inputPos = 0,
             int outputPos = 0);

// Offline processing : [ input signal ] -> [ output left signal , output right signal ]
(DiscreteSignal, DiscreteSignal) ApplyTo(DiscreteSignal signal);

// Offline processing : [ input left signal , input right signal ] -> [ output left signal , output right signal ]
(DiscreteSignal, DiscreteSignal) ApplyTo(DiscreteSignal left, DiscreteSignal right);

All stereo effects (except BinauralPanEffect) also have Pan property, since, essentially, they do amplitude (volume) panning - apply different gain for samples in the left and the right channel depending on the Pan value (float value in range [-1, 1]).

Basic pan effect

The simplest stereo effect is PanEffect. It does amplitude panning depending on the Pan value and according to certain pan law (pan rule):

var panEffect = new PanEffect(0.6f, PanRule.Sin4_5Db);

// parameters can be tweaked anytime:

panEffect.Pan = -0.6f;                // change pan from 0.6 to -0.6
panEffect.PanRule = PanRule.Sin3Db;   // change pan rule

float left, right;

// online
//while (new sample is available)
{
    panEffect.Process(sample, out left, out right);
    // do smth. with left and right
}

Enum PanRule defines the constants for various well-known pan rules (pan laws) (linear, balanced, constant power, sin 3dB, 4.5dB and 6dB, squareRoot 3dB and 4.5dB).

Stereo delay

StereoDelayEffect is simply a convenient wrapper around two separate DelayEffects (for left and right channel):

var delayLeft = 0.075f;   // 75 ms delay in left channel
var delayRight = 0.12f;   // 120 ms delay in right channel
var feedbackLeft = 0.7f;  // feedback coeff for left channel
var feedbackRight = 0.5f; // feedback coeff for right channel

var stereoDelay = new StereoDelayEffect(0.6f, delayLeft, delayRight, feedbackLeft, feedbackRight);

// parameters can be tweaked anytime:

stereoDelay.Pan = -0.6f;         // change pan from 0.6 to -0.6
stereoDelay.DelayLeft = 0.18f;   // change delay from 75 ms to 180 ms

See comments regarding the last two parameters in constructor

Ping-pong delay

PingPongDelayEffect is a stereo feedback delay where the delay bounces back and forth between the left and right channels:

var pingpong = new PingPongDelayEffect(0.6f, 0.1f, 0.8f);

// parameters can be tweaked anytime:

pingpong.Pan = -0.6f;       // change pan from 0.6 to -0.6
pingpong.Delay = 0.18f;     // change delay from 100 ms to 180 ms
pingpong.Feedback = 0.25f;  // change feedback coeff from 0.8 to 0.25

See comments regarding the last two parameters in constructor

ITD-ILD panning

ItdIldPanEffectis the special stereo effect that incorporates knowledge about Interaural Time Difference (ITD) and Interaural Level Difference (ILD). ITD is realized via constant delay effects (using FractionalDelayLine) with different delays for left and right channels depending on Pan. ILD is realized via IIR filters with different coefficients for left and right channels depending on Pan.

var itd = new ItdIldPanEffect(signal.SamplingRate, 0.6f);

// Pan can be updated anytime:

itd.Pan = -0.6f;       // change pan from 0.6 to -0.6

Constructor has three more parameters (interpolation mode, reserved delay size and head radius), but in most cases there's no need to change their default values.

Binaural panning

BinauralPanEffect operates with finite set of HRIRs (Head-related impulse responses) (or BRIRs (binaural room impulse responses)). HRIR/BRIR is a response that characterizes how an ear receives a sound from a particular point in 3D space. Since the source of a sound can change its position, we need many HRIRs corresponding to different points in 3D space.

The following picture illustrates the main ideas of binaural panning:

The big sphere represents the "space" around the head of listener (big circle inside the sphere). The dots on the sphere represent the points in the 3D space for which the fully-defined HRIR/BRIR is available. Surely, it's impossible to have ready HRIRs for each point at contiguous surface of the sphere, so HRIR in particular point will be barycentric-interpolated from three nearest available surrounding points.

Azimuth and elevation. 0 deg azimuth and 0 deg elevation corresponds to directly infront of the subject at ear level. Positive elevation corresponds to moving up and positive azimuth corresponds to moving right. The darker circle on the image above corresponds to the position Azimuth=0, Elevation=0.

NWaves does not provide any ready-to-use databases of HRIRs. There are other web-resources where you will find them. SOFA is widely used file format (and there are Python packages for extracting data from SOFA files). But often HRIRs are duplicated in the form of WAV-files, and they can be easily loaded by means of NWaves.

In the code example below we load HRIRs from CIPIC database.

According to CIPIC docs:

Each subject was tested at an azimuth of -80 to 80 deg azimuth and an elevation of -45 to 231 deg.

Channel 1-50 of each .wav file corespond to this elevation matrix [-45 -39 -34 -28 -23 -17 -11 -6 0 6 11 17 23 28 34 39 45 51 56 62 68 73 79 84 90 96 101 107 113 118 124 129 135 141 146 152 158 163 169 174 180 186 191 197 203 208 214 219 225 231]

Each subject has 25 .wav files (one for each azimuth and ear) that consist of 50 channels that represent the 200 tap impulse response at -45 to 231 deg elevation

The wavfile is named XXazYY.wav Where YY is either "left" or "right" to indicate the ear used and XX is the azimuth with the prefix neg to indicate a negative azimuth.

EX: the second channel of neg50azright.wav coresponds to the response at -50 deg azimuth and -39 deg elevation for the right ear.

float[] _azimuths = { -80, -65, -55, -45, -40, -35, -30, -25, -20, -15, -10, -5, 
                        0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 55, 65, 80 };

float[] _elevations = { -45, -39, -34, -28, -23, -17, -11, -6, 0, 6, 11, 17, 23, 28, 34, 39,
                         45, 51, 56, 62, 68, 73, 79, 84, 90, 96, 101, 107, 113, 118, 124, 129,
                        135, 141, 146, 152, 158, 163, 169, 174, 180, 186, 191, 197, 203, 208, 214, 219, 225, 231};

var leftHrirs = LoadLeftHrirsFromCIPICFolder();     // float[25][50][200]
var rightHrirs = LoadRightHrirsFromCIPICFolder();   // float[25][50][200] 

var binaural = new BinauralPanEffect(azimuths, elevations, leftHrirs, rightHrirs);

Function LoadHrirsFromCipicFolder can look like this.

When BinauralPanEffect is constructed and ready, the azimuth and elevation are set to 0. They can be changed at anytime:

binaural.Azimuth += 10;//deg
binaural.Elevation = 5;//deg

Additionally, a crossover filter can be applied. By default it's turned off. This filter separates lower-frequency components (below crossover frequency) from higher-frequency components, so that the former ones will be passed without any change and only the latter ones will be processed by BinauralPanEffect algorithm.

// set crossover frequency
binaural.SetCrossoverParameters(150/*Hz*/, signal.SamplingRate);

// or even set your own custom  filters (any objects implementing IOnlineFilter)
// binaural.SetCrossoverFilters(
//                lowpassFilterLeft, highpassFilterLeft,
//                lowpassfilterRight, highpassFilterRight);

binaural.UseCrossover(true);

// ...

// turn-off crossover filtering
binaural.UseCrossover(false);

Sample projects

NWaves.DemoStereo project