Skip to content

Audioeffects

Tim Sharii edited this page Sep 2, 2021 · 6 revisions

Audio effects are special filters used extensively in speech and music processing:

WetDryMixer

Each effect inherits from abstract class AudioEffect. This class implements IFilter and IOnlineFilter interfaces (leaving methods Process() and Reset() abstract) and inherits from class WetDryMixer with properties Wet and Dry. These are conventional mixing parameters. By default Wet = 1, Dry = 0. Although any values can be set manually to these properties, usually some mixing rule should be applied. Method WetDryMix() computes wet and dry values based on mix parameter:

var effect = new <Some>Effect(...);
// or
// var effect = new WetDryMixer();

effect.WetDryMix(0.7f, MixingRule.Linear);    // Wet = 0.7, Dry = 0.3
effect.WetDryMix(0.7f, MixingRule.Balanced);  // Wet = 1.0, Dry = 0.6
effect.WetDryMix(0.7f, MixingRule.Sin3Db);    // Wet = 0.8910065, Dry = 0.45399052

// ...

Enum MixingRule defines the constants for various well-known mixing rules (linear, balanced, sin 3dB, 4.5dB and 6dB, squareRoot 3dB and 4.5dB).

Method WetDryDb() computes wet and dry values based on wet and dry values in decibels:

var effect = new <Some>Effect(...);
// or
// var effect = new WetDryMixer();

effect.WetDryMix(0/*dB*/, -6/*dB*/);   // Wet = 0.6661394, Dry = 0.33386058

// mixing rule is linear

The general audioeffect workflow is similar to any other filtering:

AudioEffect effect = new <Some>Effect(signal.SamplingRate, <effect parameters>)
{
    Wet = 0.8f,
    Dry = 0.2f
}

// online
//while (new sample is available)
{
    var outputSample = effect.Process(sample);
    // ...
    // also effect parameters can be changed online, e.g. for FlangerEffect:
    //   effect.Depth += 0.1f
}

effect.Reset();

// offline
var outputSignal = effect.ApplyTo(signal);

Parameters can be tweaked at anytime during online processing.

Audioeffects are customizable, some of their blocks can be reused. Many of them are based on LFOs (low frequency oscillators), and you can set your own LFO anytime (or pass it as a constructor parameter).

All audioeffects support online filtering, except PitchShiftEffect (for online pitch-shifting the class PitchShiftVocoderEffect can be used).

WahWah effect

Technically, wahwah effect is LFO + bandpass filter. There are two ways to construct WahwahEffect object:

  1. Specify all parameters in constructor:
var wahwah = new WahwahEffect(signal.SamplingRate, 1, 50, 1800, 0.7f);

wahwah.LfoFrequency = 1.5f;  // change LFO frequency from 1 to 1.5 Hz
wahwah.MinFrequency = 200;   // change min frequency from 50 to 200 Hz
wahwah.MaxFrequency = 1500;  // change max frequency from 1800 to 1500 Hz
wahwah.Q = 0.4f;             // change Q from 0.7 to 0.4

Note. LFO will be triangular by default.

  1. Specify your own LFO (perhaps, sinusoidal or any other SignalBuilder-derived class):
var fs = signal.SamplingRate;

var sawtooth = new SawtoothBuilder()
                       .SetParameter("freq", 1.2f)
                       .SampledAt(fs);

var wahwah = new WahwahEffect(fs, sawtooth, q: 0.6f);

// ...
// change LFO anytime

var squareWave = new SquareWaveBuilder()
                         .SetParameter("freq", 1.5f)
                         .SampledAt(fs);
wahwah.Lfo = squareWave;

// ...or only LFO frequency
wahwah.LfoFrequency = 0.5f;

AutoWah effect

AutoWah effect is envelope follower + bandpass filter:

var autowah = new AutowahEffect(signal.SamplingRate, 100, 4000, 0.7f, 0.01f, 0.05f);

autowah.MinFrequency = 200;  // change min frequency from 100 to 200 Hz
autowah.MaxFrequency = 3500; // change max frequency from 4000 to 3500 Hz
autowah.Q = 0.4f;            // change Q from 0.7 to 0.4

// envelope follower settings

autowah.AttackTime = 0.02f;  // change attack time from 0.01 to 0.02 sec
autowah.ReleaseTime = 0.09f; // change release time from 0.05 to 0.09 sec

Phaser effect

Technically, phaser effect is LFO + bandreject filter. The class is PhaserEffect, whose construction and usage is analogous to WahwahEffect.

Tremolo effect

Technically, tremolo effect is LFO + ring modulator. Like in case of WahWah, there are two ways to construct TremoloEffect object:

// 1) all parameters in constructor
var tremolo = new TremoloEffect(signal.SamplingRate, 5, 0.5);

tremolo.Frequency = 3.4f;     // change tremolo frequency from 5 to 3.4 Hz
tremolo.TremoloIndex = 0.9f;  // change tremolo index from 0.5 to 0.9


// 2) prepare LFO separately
var modulator = new SineBuilder()
                        .SetParameter("freq", 3)
                        .SetParameter("min", 0)
                        .SetParameter("max", 2.5)
                        .SampledAt(signal.SamplingRate);

tremolo = new TremoloEffect(signal.SamplingRate, modulator);

// change LFO
tremolo.Lfo = new TriangleWaveBuilder()/*set parameters*/.SampledAt(signal.SamplingRate);

Note. LFO will be CosineBuilder by default.

Vibrato effect

Technically, vibrato effect is LFO + modulated delay filter + interpolation of fractional delay. Like in previous cases, we can tweak all parameters of VibratoEffect object at anytime:

var vibrato = new VibratoEffect(signal.SamplingRate, 1/*Hz*/, 0.003f/*sec*/, InterpolationMode.Linear, 0.01f/*sec*/);

vibrato.Width = 0.005f;       // change width (max delay) from 3ms to 5ms
vibrato.LfoFrequency = 1.5f;  // change LFO frequency from 1 to 1.5 Hz

vibrato.Lfo = new SawtoothBuilder().SampledAt(signal.SamplingRate);
// parameters min and max will be always set as: min = 0, max = 1

Note. LFO will be SineBuilder by default. There's also an overloaded constructor that allows setting any other SignalBuilder object as LFO.

Note about last two parameters in the constructor. The effect uses FractionalDelayLine under the hood. Hence: 1) you can specify different interpolation algorithms for getting each sample from a delay line (linear, cubic, nearest); 2) you can also specify how much space should be reserved in the delay line by setting reserveWidth parameter (in seconds). This is useful if the Width parameter is going to be changed within some range during processing. The new memory will be allocated only if the width will exceed reserveWidth. By default, reserveWidth = width so everytime the width will increase, new delay line will be created (same data, but new size).

Flanger effect

Technically, flanger effect is LFO + modulated delay filter with feedback + interpolation of fractional delay. It's similar to vibrato, but it has additional parameters:

  • Depth
  • Feedback
  • Inverted
var flanger = new FlangerEffect(fs, lfoFrequency, width, depth, feedback, true);

Read more about flanger parameters.

Note. LFO will be SineBuilder by default. There's also an overloaded constructor that allows setting any other SignalBuilder object as LFO.

Note about last two parameters in the constructor. The effect uses FractionalDelayLine under the hood. Hence: 1) you can specify different interpolation algorithms for getting each sample from a delay line (linear, cubic, nearest); 2) you can also specify how much space should be reserved in the delay line by setting reserveWidth parameter (in seconds). This is useful if the Width parameter is going to be changed within some range during processing. The new memory will be allocated only if the width will exceed reserveWidth. By default, reserveWidth = width so everytime the width will increase, new delay line will be created (same data, but new size).

Chorus effect

Currently, chorus effect is implemented not very efficiently - simply as array of vibrato effects (voices):

// 4 voices:

var lfoFrequencies = new[] { 1f, 1f, 1f, 1f };
var widths = new[] { 0.004f, 0.0042f, 0.0045f, 0.0038f };

var chorus = new ChorusEffect(samplingRate, lfoFrequencies, widths);

Note. LFOs will be SineBuilders by default. There's also an overloaded constructor that allows setting array of any other SignalBuilder objects as LFOs.

Delay effect

Technically, it's just a feedforward (FIR) comb filter.

var delay = new DelayEffect(signal.SamplingRate, 0.024f/*sec*/, 0.4f);

delay.Delay = 0.018f;   // change length of delay from 24ms to 18ms
delay.Feedback = 0.6f;  // change feedback coefficient from 0.4 to 0.6

Note about last two parameters in the constructor. The effect uses FractionalDelayLine under the hood. Hence: 1) you can specify different interpolation algorithms for getting each sample from a delay line (linear, cubic, nearest); 2) you can also specify how much space should be reserved in the delay line by setting reserveWidth parameter (in seconds). This is useful if the Width parameter is going to be changed within some range during processing. The new memory will be allocated only if the width will exceed reserveWidth. By default, reserveWidth = width so everytime the width will increase, new delay line will be created (same data, but new size).

Echo effect

Echo effects, in general, may be implemented different ways, but in NWaves it's just a feedback (IIR) comb filter (using FractionalDelayLine under the hood, though). The class is EchoEffect and its construction and usage is similar to DelayEffect.

Distortion effect

var dist = new DistortionEffect(DistortionMode.SoftClipping, 15, -10);

dist.InputGain = 20;   // change input gain from 15 to 20 dB
dist.OutputGain = -12; // change output gain from -10 to -12 dB

Distortion types are defined in DistortionMode enum:

  • Hard-clipping: essentially it's clamp(-0.5, 0.5)

  • Soft-clipping:

    [Udo Zoelzer] DAFX book, p.118.

  • Exponential:

    [Udo Zoelzer] DAFX book, p.124-125.

  • Full-wave rectifier

  • Half-wave rectifier

Tube distortion effect

Asymmetrical tube distortion described in

[Udo Zoelzer] DAFX book, p.123-24.

var dist = new TubeDistortionEffect(20, -10, q: -0.5, dist: 5);

dist.InputGain = 25;   // change input gain from 20 to 25 dB
dist.OutputGain = -12; // change output gain from -10 to -12 dB

Bitcrusher effect

Audio effect that produces distortion by reducing the bit resolution (bit depth) of audio samples.

var bitcrusher = new BitCrusherEffect(4);

bitcrusher.BitDepth = 3;    // change bit depth from 4 to 3

Pitch shift effect

There are two classes for pitch shifting:

  • PitchShiftEffect
  • PitchShiftVocoderEffect

PitchShiftEffect produces better results in general, but it provides only offline method based on 2-stage algorithm: 1) time stretch; 2) linear interpolation. In order to construct an object of class PitchShiftEffect, one needs to specify the pitch shift ratio and parameters of time stretching algorithm: FFT size, hop size and TSM algorithm. In general, the quality of pitch shifted signal is satisfactory, however, in some cases you may want to apply anti-aliasing LP filter to the resulting signal.

var pitchShift = new PitchShiftEffect(1.12, 2048, 200);
var shifted = pitchShift.ApplyTo(signal);

PitchShiftVocoderEffect provides both offline and online methods based on phase vocoder technique with "spectral stretching" in frequency domain.

var pitchShift = new PitchShiftVocoderEffect(samplingRate, 1.12, 1024, 64);
var shifted = pitchShift.ApplyTo(signal);

// online:
// while input sample is available
{
    var outputSample = pitchShift.Process(inputSample);
    //...
    // pitchShift.Shift += 0.06f;  // can be changed
}

Robotize

Phase vocoder-based effect. At each step phases are just set to 0.

var robot = new RobotEffect(hopSize: 120, fftSize: 512);
var robotized = robot.ApplyTo(signal);

// online:
// while input sample is available
{
    var outputSample = robot.Process(inputSample);
    //...
}

Whisperize

Phase vocoder-based effect. At each step phases are simply randomized. Best results are achieved when window size and hop size are relatively small:

var whisper = new WhisperEffect(hopSize: 60, fftSize: 256);
var whispered = whisper.ApplyTo(signal);

// online:
// while input sample is available
{
    var outputSample = whisper.Process(inputSample);
    //...
}

MorphEffect

Phase vocoder-based effect. At each processing step the output spectrum is combined from magnitude of the first signal and phase of the second signal. Also, it's the only audioeffect that processes two signals instead of one, so ApplyTo() method is overloaded:

var morpher = new MorphEffect(hopSize: 200, fftSize: 1024);
var morphed = morpher.ApplyTo(signal1, signal2);

// online:
// while input sample is available
{
    // get inputSample and morphSample

    var outputSample = morpher.Process(inputSample, morphSample);
    //...
}

Create your own audioeffect

Just for example, let's see how to create very simple effect of adding some red "waterfall" noise to each signal sample:

public class NoisyEffect : AudioEffect
{ 
    private SignalBuilder noise;

    public NoisyEffect(int samplingRate, double noiseLevel = 0.5)
    {
        noise = new RedNoiseBuilder()
                        .SetParameter("min", -noiseLevel)
                        .SetParameter("max",  noiseLevel)
                        .SampledAt(samplingRate);
    }
 
    public override float Process(float sample)
    {
        var output = sample + noise.NextSample();
        return output * Wet + sample * Dry;
    }

    public override void Reset()
    {
        noise.Reset();
    }
}