import torch
import numpy as np
from typing import Dict, Union
from ..base import ProcessorsBase, EffectParam
from ..base_utils import check_params
from ..filters import DCFilter
class BaseDistortion(ProcessorsBase):
"""Base class for implementing various distortion effects with optional waveshaping controls.
This class provides a foundation for implementing different types of distortion effects,
with optional waveshaping parameters for more detailed control over the distortion characteristics.
It includes pre/post gain staging, DC bias control, and automatic DC filtering when in shaping mode.
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable additional waveshaping controls. Defaults to False.
When True, enables:
- Pre-gain control
- DC bias adjustment
- Post-gain control
- Automatic DC filtering
Default Parameters:
Basic Mode (shaping_mode=False):
mix: Wet/dry mix ratio
- Range: 0.0 to 1.0
- 0.0: Only clean signal
- 1.0: Only distorted signal
Shaping Mode (shaping_mode=True):
Additional parameters:
pre_gain_db: Input gain before distortion
- Range: -24.0 to 24.0 dB
- Controls amount of drive into distortion
- Higher values create more saturation
post_gain_db: Output gain after distortion
- Range: -24.0 to 0.0 dB
- Compensates for level changes
- Prevents output clipping
dc_bias: DC offset added before distortion
- Range: -0.2 to 0.2
- Controls asymmetric clipping
- Affects harmonic content
Note:
- Subclasses must implement _apply_distortion method
- DC filtering is automatically applied in shaping mode
- Parameters can be controlled via norm_params or dsp_params
- Additional parameters can be added through _add_specific_parameters
Example:
```python
class CustomDistortion(BaseDistortion):
def _add_specific_parameters(self):
self.params['drive'] = EffectParam(min_val=1.0, max_val=10.0)
def _apply_distortion(self, x, params):
drive = params['drive'].unsqueeze(-1).unsqueeze(-1)
return torch.tanh(drive * x)
```
"""
def __init__(self, sample_rate, param_range=None,shaping_mode=False):
"""Initialize the distortion processor.
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable waveshaping controls. Defaults to False.
"""
self.shaping_mode = shaping_mode
super().__init__(sample_rate, param_range)
if self.shaping_mode:
self.dc_filter = DCFilter(sample_rate, learnable=False)
def _register_default_parameters(self):
"""Register default parameters for the distortion processor.
Sets up:
Basic Mode:
- mix: Wet/dry balance (0.0 to 1.0)
Shaping Mode:
- pre_gain_db: Input gain (-12.0 to 12.0 dB)
- post_gain_db: Output gain (-12.0 to 12.0 dB)
- dc_bias: DC offset (-0.2 to 0.2)
"""
base_params = {
'mix': EffectParam(min_val=0.0, max_val=1.0)
}
shaping_params = {
'pre_gain_db': EffectParam(min_val=-12.0, max_val=12.0),
'post_gain_db': EffectParam(min_val=-12.0, max_val=12.0),
'dc_bias': EffectParam(min_val=-0.2, max_val=0.2),
}
if self.shaping_mode:
self.params = {**base_params, **shaping_params}
else:
self.params = base_params
# Add any additional parameters specific to the distortion type
self._add_specific_parameters()
def _add_specific_parameters(self):
"""Add parameters specific to each distortion type.
This method should be overridden by subclasses to add parameters
specific to their distortion implementation.
Example:
```python
def _add_specific_parameters(self):
self.params['drive'] = EffectParam(min_val=1.0, max_val=10.0)
```
"""
pass
def _apply_distortion(self, x):
"""Apply the distortion transfer function to the input signal.
Args:
x (torch.Tensor): Input audio tensor
Returns:
torch.Tensor: Distorted audio tensor
Raises:
NotImplementedError: Must be implemented by subclasses
"""
raise NotImplementedError
def process(self, x: torch.Tensor, norm_params: Union[Dict[str, torch.Tensor], None]=None, dsp_params: Union[Dict[str, torch.Tensor], None] = None):
"""Process input signal through the distortion effect.
Args:
x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples)
norm_params (Dict[str, torch.Tensor]): Normalized parameters (0 to 1)
Basic Mode must contain:
- 'mix': Wet/dry balance (0 to 1)
Shaping Mode adds:
- 'pre_gain_db': Input gain (0 to 1)
- 'post_gain_db': Output gain (0 to 1)
- 'dc_bias': DC offset (0 to 1)
Each value should be a tensor of shape (batch_size,)
dsp_params (Dict[str, Union[float, torch.Tensor]], optional): Direct DSP parameters.
Can specify distortion parameters as:
- float/int: Single value applied to entire batch
- 0D tensor: Single value applied to entire batch
- 1D tensor: Batch of values matching input batch size
Parameters will be automatically expanded to match batch size
and moved to input device if necessary.
If provided, norm_params must be None.
Returns:
torch.Tensor: Processed audio tensor of same shape as input
"""
check_params(norm_params, dsp_params)
if norm_params is not None:
params = self.map_parameters(norm_params)
else:
params = dsp_params
# Extract parameters
mix = params['mix']
# Apply shaping if enabled
if self.shaping_mode:
pre_gain = 10 ** (params['pre_gain_db'] / 20.0)
post_gain = 10 ** (params['post_gain_db'] / 20.0)
pre_gain = pre_gain.unsqueeze(-1).unsqueeze(-1)
post_gain = post_gain.unsqueeze(-1).unsqueeze(-1)
dc_bias = params['dc_bias'].unsqueeze(-1).unsqueeze(-1)
x_driven = pre_gain * x + dc_bias
else:
x_driven = x
# Apply distortion
x_distorted = self._apply_distortion(x_driven, params)
# Apply post-processing
if self.shaping_mode:
x_processed = post_gain * x_distorted
x_processed = self.dc_filter(x_processed, None, None)
else:
x_processed = x_distorted
# Apply mix
mix = mix.unsqueeze(-1).unsqueeze(-1)
return (1 - mix) * x + mix * x_processed
[docs]class TanHDist(BaseDistortion):
"""Differentiable implementation of hyperbolic tangent distortion.
This processor implements smooth distortion using the hyperbolic tangent (tanh) function
for waveshaping. It provides analog-style saturation with natural compression characteristics
and gradual onset of distortion.
The transfer function is:
.. math::
y = tanh(x)
where x is the input signal (optionally pre-gained and DC biased in shaping mode).
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable waveshaping controls. Defaults to False.
When True, enables:
- Pre-gain control
- DC bias adjustment
- Post-gain control
- Automatic DC filtering
Parameters Details:
Basic Mode (shaping_mode=False):
mix: Wet/dry mix ratio
- Range: 0.0 to 1.0
- 0.0: Only clean signal
- 1.0: Only distorted signal
Shaping Mode (shaping_mode=True):
pre_gain_db: Input gain before distortion
- Range: -24.0 to 24.0 dB
- Controls amount of drive into saturation
post_gain_db: Output gain after distortion
- Range: -24.0 to 0.0 dB
- Compensates for level changes
dc_bias: DC offset before distortion
- Range: -0.2 to 0.2
- Affects harmonic content
Examples:
Basic DSP Usage:
>>> # Create a tanh distortion
>>> dist = TanHDist(sample_rate=44100)
>>> # Process with basic settings
>>> output = dist(input_audio, dsp_params={
... 'mix': 0.7 # 70% wet signal
... })
Advanced Usage (shaping_mode=True):
>>> # Create with waveshaping controls
>>> dist = TanHDist(sample_rate=44100, shaping_mode=True)
>>> # Process with detailed control
>>> output = dist(input_audio, dsp_params={
... 'pre_gain_db': 12.0, # Drive into distortion
... 'post_gain_db': -6.0, # Compensate output
... 'dc_bias': 0.1, # Add asymmetry
... 'mix': 0.8 # 80% wet
... })
"""
[docs] def _apply_distortion(self, x, params):
"""Apply hyperbolic tangent distortion to the input signal.
Args:
x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples)
params (dict): Processing parameters (not used in this implementation)
Returns:
torch.Tensor: Distorted audio tensor of same shape as input
"""
return torch.tanh(x)
[docs]class SoftDist(BaseDistortion):
"""Differentiable implementation of soft-clipping distortion.
This processor implements a soft-clipping distortion using a piecewise polynomial transfer function.
It provides smooth transitions between clean and distorted signals, creating a warm overdrive
characteristic similar to analog tube saturation.
The transfer function is piecewise:
.. math::
y = \\begin{cases}
1.0 & x ≥ 1 \\\\
1.5(x - x^3/3) & -1 < x < 1 \\\\
-1.0 & x ≤ -1
\\end{cases}
where x is the input signal (optionally pre-gained and DC biased in shaping mode).
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable waveshaping controls. Defaults to False.
When True, enables:
- Pre-gain control
- DC bias adjustment
- Post-gain control
- Automatic DC filtering
Parameters Details:
Basic Mode (shaping_mode=False):
mix: Wet/dry mix ratio
- Range: 0.0 to 1.0
- 0.0: Only clean signal
- 1.0: Only distorted signal
Shaping Mode (shaping_mode=True):
pre_gain_db: Input gain before distortion
- Range: -24.0 to 24.0 dB
- Controls amount of drive into clipping
post_gain_db: Output gain after distortion
- Range: -24.0 to 0.0 dB
- Compensates for level changes
dc_bias: DC offset before distortion
- Range: -0.2 to 0.2
- Affects asymmetric clipping
Examples:
Basic DSP Usage:
>>> # Create a soft clipper
>>> dist = SoftDist(sample_rate=44100)
>>> # Process with basic settings
>>> output = dist(input_audio, dsp_params={
... 'mix': 0.6 # 60% wet signal
... })
Advanced Usage (shaping_mode=True):
>>> # Create with waveshaping controls
>>> dist = SoftDist(sample_rate=44100, shaping_mode=True)
>>> # Process with detailed control
>>> output = dist(input_audio, dsp_params={
... 'pre_gain_db': 18.0, # Drive into clipping
... 'post_gain_db': -9.0, # Compensate output
... 'dc_bias': 0.05, # Slight asymmetry
... 'mix': 0.7 # 70% wet
... })
"""
[docs] def _apply_distortion(self, x, params):
"""Apply soft-clipping distortion to the input signal.
Args:
x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples)
params (dict): Processing parameters (not used in this implementation)
Returns:
torch.Tensor: Distorted audio tensor of same shape as input
Note:
Uses a piecewise function:
- Linear clipping for :math:`|x| \geq 1`
- Cubic polynomial for :math:`|x| < 1`
"""
y = torch.zeros_like(x)
mask_high = x >= 1
y[mask_high] = 1.0
mask_mid = (x > -1) & (x < 1)
x_mid = x[mask_mid]
y[mask_mid] = 1.5 * (x_mid - x_mid**3/3)
mask_low = x <= -1
y[mask_low] = -1.0
return y
[docs]class HardDist(BaseDistortion):
"""Differentiable implementation of hard-clipping distortion.
This processor implements a hard-clipping distortion that abruptly limits signals above a
specified threshold. It creates aggressive distortion with rich harmonic content, similar
to extreme transistor or diode clipping circuits.
The transfer function is:
.. math::
y = \\begin{cases}
threshold & x > threshold \\\\
x & -threshold ≤ x ≤ threshold \\\\
-threshold & x < -threshold
\\end{cases}
where x is the input signal (optionally pre-gained and DC biased in shaping mode).
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable waveshaping controls. Defaults to False.
When True, enables:
- Pre-gain control
- DC bias adjustment
- Post-gain control
- Automatic DC filtering
Parameters Details:
Basic Mode (shaping_mode=False):
threshold: Clipping threshold level
- Range: 0.1 to 1.0
- Lower values create more aggressive clipping
- Higher values preserve more dynamics
mix: Wet/dry mix ratio
- Range: 0.0 to 1.0
- 0.0: Only clean signal
- 1.0: Only distorted signal
Shaping Mode (shaping_mode=True):
pre_gain_db: Input gain before distortion
- Range: -24.0 to 24.0 dB
- Controls amount of drive into clipping
post_gain_db: Output gain after distortion
- Range: -24.0 to 0.0 dB
- Compensates for level changes
dc_bias: DC offset before distortion
- Range: -0.2 to 0.2
- Affects asymmetric clipping
Examples:
Basic DSP Usage:
>>> # Create a hard clipper
>>> dist = HardDist(sample_rate=44100)
>>> # Process with basic settings
>>> output = dist(input_audio, dsp_params={
... 'threshold': 0.3, # Aggressive clipping
... 'mix': 0.8 # 80% wet signal
... })
Advanced Usage (shaping_mode=True):
>>> # Create with waveshaping controls
>>> dist = HardDist(sample_rate=44100, shaping_mode=True)
>>> # Process with detailed control
>>> output = dist(input_audio, dsp_params={
... 'threshold': 0.5, # Moderate clipping
... 'pre_gain_db': 15.0, # Drive into clipping
... 'post_gain_db': -12.0, # Compensate output
... 'dc_bias': 0.1, # Add asymmetry
... 'mix': 0.7 # 70% wet
... })
"""
[docs] def _add_specific_parameters(self):
"""Register additional parameters specific to hard clipping.
Adds:
threshold: Clipping threshold level (0.1 to 1.0)
"""
self.params['threshold'] = EffectParam(min_val=0.1, max_val=1.0)
[docs] def _apply_distortion(self, x, params):
"""Apply hard-clipping distortion to the input signal.
Args:
x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples)
params (dict): Must contain:
- threshold: Clipping threshold level
Returns:
torch.Tensor: Distorted audio tensor of same shape as input
"""
threshold = params['threshold'].unsqueeze(-1).unsqueeze(-1)
return torch.clamp(x, min=-threshold, max=threshold)
[docs]class DoubleSoftDist(BaseDistortion):
"""Differentiable implementation of double soft-clipping distortion with asymmetric controls.
Implementation is based on:
.. [4] https://jatinchowdhury18.medium.com/complex-nonlinearities-episode-1-double-soft-clipper-5ce826fa82d6
This processor implements a sophisticated dual-stage soft-clipping distortion with independent
control over positive and negative waveform shaping. It provides precise control over clipping
characteristics, allowing creation of asymmetric distortion with variable slopes and limits.
The transfer function is piecewise and applies separately to positive and negative regions:
Positive region (x > 0):
.. math::
y = \\begin{cases}
upper\\_lim & x ≥ \\frac{1}{slope} \\\\
\\frac{3}{2}upper\\_lim(slope⋅x - \\frac{(slope⋅x)^3}{3}) + \\frac{upper\\_lim}{2} & -\\frac{1}{slope} < x < \\frac{1}{slope} \\\\
0 & x ≤ -\\frac{1}{slope}
\\end{cases}
Negative region (x ≤ 0):
.. math::
y = \\begin{cases}
0 & x ≥ \\frac{1}{slope} \\\\
\\frac{3}{2}lower\\_lim(slope⋅x - \\frac{(slope⋅x)^3}{3}) + \\frac{lower\\_lim}{2} & -\\frac{1}{slope} < x < \\frac{1}{slope} \\\\
lower\\_lim & x ≤ -\\frac{1}{slope}
\\end{cases}
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable waveshaping controls. Defaults to False.
When True, enables:
- Pre-gain control
- DC bias adjustment
- Post-gain control
- Automatic DC filtering
Parameters Details:
Basic Mode (shaping_mode=False):
upper_lim: Positive clipping limit
- Range: 0.1 to 1.0
- Controls maximum positive output
lower_lim: Negative clipping limit
- Range: -1.0 to -0.1
- Controls maximum negative output
slope: Transfer function steepness
- Range: 1.0 to 10.0
- Higher values create sharper transitions
x_off_factor: Offset control
- Range: 0.0 to 1.0
- Affects symmetry of clipping curve
upper_skew: Positive region shaping
- Range: 0.1 to 2.0
- Controls shape of positive overdrive
lower_skew: Negative region shaping
- Range: 0.1 to 2.0
- Controls shape of negative overdrive
mix: Wet/dry mix ratio
- Range: 0.0 to 1.0
- 0.0: Only clean signal
- 1.0: Only distorted signal
Shaping Mode (shaping_mode=True):
pre_gain_db: Input gain before distortion
- Range: -24.0 to 24.0 dB
post_gain_db: Output gain after distortion
- Range: -24.0 to 0.0 dB
dc_bias: DC offset before distortion
- Range: -0.2 to 0.2
Examples:
Basic Usage:
>>> # Create a double soft clipper
>>> dist = DoubleSoftDist(sample_rate=44100)
>>> # Process with asymmetric settings
>>> output = dist(input_audio, dsp_params={
... 'upper_lim': 0.8,
... 'lower_lim': -0.6,
... 'slope': 3.0,
... 'x_off_factor': 0.2,
... 'upper_skew': 1.5,
... 'lower_skew': 1.2,
... 'mix': 0.7
... })
Advanced Usage (shaping_mode=True):
>>> # Create with waveshaping controls
>>> dist = DoubleSoftDist(sample_rate=44100, shaping_mode=True)
>>> output = dist(input_audio, dsp_params={
... 'upper_lim': 0.8,
... 'lower_lim': -0.6,
... 'slope': 3.0,
... 'x_off_factor': 0.2,
... 'upper_skew': 1.5,
... 'lower_skew': 1.2,
... 'pre_gain_db': 12.0,
... 'post_gain_db': -6.0,
... 'dc_bias': 0.1,
... 'mix': 0.7
... })
"""
[docs] def _add_specific_parameters(self):
"""Register additional parameters specific to double soft clipping.
Adds:
upper_lim: Positive clipping limit (0.1 to 1.0)
lower_lim: Negative clipping limit (-1.0 to -0.1)
slope: Transfer function steepness (1.0 to 10.0)
x_off_factor: Offset control (0.0 to 1.0)
upper_skew: Positive region shaping (0.1 to 2.0)
lower_skew: Negative region shaping (0.1 to 2.0)
"""
self.params.update({
'upper_lim': EffectParam(min_val=0.1, max_val=1.0),
'lower_lim': EffectParam(min_val=-1.0, max_val=-0.1),
'slope': EffectParam(min_val=1.0, max_val=10.0),
'x_off_factor': EffectParam(min_val=0.0, max_val=1.0),
'upper_skew': EffectParam(min_val=0.1, max_val=2.0),
'lower_skew': EffectParam(min_val=0.1, max_val=2.0),
})
[docs] def _apply_distortion(self, x, params):
"""Apply double soft-clipping distortion to the input signal.
Args:
x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples)
params (dict): Must contain:
- upper_lim: Positive clipping limit
- lower_lim: Negative clipping limit
- slope: Transfer function steepness
- x_off_factor: Offset control
- upper_skew: Positive region shaping
- lower_skew: Negative region shaping
Returns:
torch.Tensor: Distorted audio tensor of same shape as input
Note:
Processes positive and negative regions independently using
different shaping parameters for each region.
"""
# Get parameters and reshape for broadcasting
# For shape [batch_size, 1, 1]
upper_lim = params['upper_lim'].view(-1, 1, 1)
lower_lim = params['lower_lim'].view(-1, 1, 1)
slope = params['slope'].view(-1, 1, 1)
x_off_factor = params['x_off_factor'].view(-1, 1, 1)
upper_skew = params['upper_skew'].view(-1, 1, 1)
lower_skew = params['lower_skew'].view(-1, 1, 1)
# Calculate offset based on slope and x_off_factor
x_off = (1/slope) * slope**x_off_factor
# Initialize output tensor
y = torch.zeros_like(x)
# Create masks for positive and negative regions
pos_mask = x > 0
neg_mask = x <= 0
# Process positive values
if torch.any(pos_mask):
# Apply offset and skew while preserving batch dimensions
x_pos = (x * pos_mask - x_off) * upper_skew
# Create masks for different regions
pos_high = x_pos >= 1/slope
pos_low = x_pos <= -1/slope
pos_mid = ~pos_high & ~pos_low & pos_mask
# Apply transfer function for each region
y = torch.where(pos_high & pos_mask, upper_lim, y)
y = torch.where(pos_low & pos_mask, torch.zeros_like(x), y)
# Process middle region
x_mid = x_pos * pos_mid
mid_out = (3/2) * upper_lim * (slope*x_mid - (slope*x_mid)**3 / 3) / 2 + (upper_lim/2) * pos_mid
y = torch.where(pos_mid, mid_out, y)
# Process negative values
if torch.any(neg_mask):
# Apply offset and skew while preserving batch dimensions
x_neg = (x * neg_mask + x_off) * lower_skew
# Create masks for different regions
neg_high = x_neg >= 1/slope
neg_low = x_neg <= -1/slope
neg_mid = ~neg_high & ~neg_low & neg_mask
# Apply transfer function for each region
y = torch.where(neg_high & neg_mask, torch.zeros_like(x), y)
y = torch.where(neg_low & neg_mask, lower_lim, y)
# Process middle region
x_mid = x_neg * neg_mid
mid_out = (3/2) * -lower_lim * (slope*x_mid - (slope*x_mid)**3 / 3) / 2 + (lower_lim/2) * neg_mid
y = torch.where(neg_mid, mid_out, y)
return y
[docs]class CubicDist(BaseDistortion):
"""Differentiable implementation of cubic distortion.
This processor implements distortion using a cubic polynomial transfer function,
creating asymmetric clipping characteristics by adding a scaled cubic term to the
input signal. This approach generates both even and odd harmonics, providing a
rich timbral modification.
The transfer function is:
.. math::
y = x + drive * x^3
where:
- x is the input signal
- drive is the intensity control (derived from drive_db)
- drive = 10^{drive\\_db/20}
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable waveshaping controls. Defaults to False.
When True, enables:
- Pre-gain control
- DC bias adjustment
- Post-gain control
- Automatic DC filtering
Parameters Details:
Basic Mode (shaping_mode=False):
drive_db: Distortion intensity
- Range: -24.0 to 24.0 dB
- Controls amplitude of cubic term
- Higher values create more distortion
- Negative values reduce distortion
mix: Wet/dry mix ratio
- Range: 0.0 to 1.0
- 0.0: Only clean signal
- 1.0: Only distorted signal
Shaping Mode (shaping_mode=True):
pre_gain_db: Input gain before distortion
- Range: -24.0 to 24.0 dB
- Controls overall drive level
post_gain_db: Output gain after distortion
- Range: -24.0 to 0.0 dB
- Compensates for level changes
dc_bias: DC offset before distortion
- Range: -0.2 to 0.2
- Affects harmonic balance
Examples:
Basic DSP Usage:
>>> # Create a cubic distortion
>>> dist = CubicDist(sample_rate=44100)
>>> # Process with moderate drive
>>> output = dist(input_audio, dsp_params={
... 'drive_db': 12.0, # 12dB of drive
... 'mix': 0.7 # 70% wet signal
... })
Advanced Usage (shaping_mode=True):
>>> # Create with waveshaping controls
>>> dist = CubicDist(sample_rate=44100, shaping_mode=True)
>>> # Process with detailed control
>>> output = dist(input_audio, dsp_params={
... 'drive_db': 18.0, # Heavy distortion
... 'pre_gain_db': 6.0, # Additional input drive
... 'post_gain_db': -12.0, # Output compensation
... 'dc_bias': 0.05, # Slight asymmetry
... 'mix': 0.8 # 80% wet
... })
"""
[docs] def _add_specific_parameters(self):
"""Register additional parameters specific to cubic distortion.
Adds:
drive_db: Distortion intensity (-24.0 to 24.0 dB)
"""
self.params['drive_db'] = EffectParam(min_val=-24.0, max_val=24.0)
[docs] def _apply_distortion(self, x, params):
"""Apply cubic distortion to the input signal.
Args:
x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples)
params (dict): Must contain:
- drive_db: Distortion intensity in dB
Returns:
torch.Tensor: Distorted audio tensor of same shape as input
Note:
Drive is converted from dB to linear scaling before application
"""
drive = params['drive_db'].view(-1, 1, 1)
drive = 10 ** (drive / 20.0)
x_dist = x + drive * x**3
return x_dist
[docs]class RectifierDist(BaseDistortion):
"""Differentiable implementation of rectifier distortion.
This processor implements half-wave and full-wave rectification with variable threshold,
allowing smooth interpolation between rectification modes. The effect is similar to diode
clipping circuits, creating characteristic asymmetric distortion with rich harmonic content.
The transfer function interpolates between:
Half-wave (mode = 0):
.. math::
y = \\begin{cases}
x & x > threshold \\\\
0 & |x| ≤ threshold \\\\
0 & x < -threshold
\\end{cases}
Full-wave (mode = 1):
.. math::
y = \\begin{cases}
|x| & |x| > threshold \\\\
0 & |x| ≤ threshold
\\end{cases}
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable waveshaping controls. Defaults to False.
When True, enables:
- Pre-gain control
- DC bias adjustment
- Post-gain control
- Automatic DC filtering
Parameters Details:
Basic Mode (shaping_mode=False):
mode: Rectification type
- Range: 0.0 to 1.0
- 0.0: Half-wave rectification
- 1.0: Full-wave rectification
- Intermediate values blend between modes
threshold: Signal threshold for rectification
- Range: 0.0 to 1.0
- Signals below threshold are set to zero
- Higher values create gating effects
mix: Wet/dry mix ratio
- Range: 0.0 to 1.0
- 0.0: Only clean signal
- 1.0: Only rectified signal
Shaping Mode (shaping_mode=True):
pre_gain_db: Input gain before rectification
- Range: -24.0 to 24.0 dB
- Controls amount of signal above threshold
post_gain_db: Output gain after rectification
- Range: -24.0 to 0.0 dB
- Compensates for level changes
dc_bias: DC offset before rectification
- Range: -0.2 to 0.2
- Affects rectification symmetry
Note:
- Half-wave creates strong asymmetric distortion
- Full-wave doubles frequency content
- Threshold creates gating effects
- Useful for extreme timbral modification
- Can generate octave-up effects (full-wave)
Examples:
Basic DSP Usage:
>>> # Create a rectifier distortion
>>> dist = RectifierDist(sample_rate=44100)
>>> # Process with half-wave rectification
>>> output = dist(input_audio, dsp_params={
... 'mode': 0.0, # Half-wave mode
... 'threshold': 0.2, # Low threshold
... 'mix': 0.6 # 60% wet signal
... })
Advanced Usage (shaping_mode=True):
>>> # Create with waveshaping controls
>>> dist = RectifierDist(sample_rate=44100, shaping_mode=True)
>>> # Process with detailed control
>>> output = dist(input_audio, dsp_params={
... 'mode': 0.7, # Blend of half/full wave
... 'threshold': 0.3, # Moderate threshold
... 'pre_gain_db': 12.0, # Drive into rectification
... 'post_gain_db': -6.0, # Compensate output
... 'dc_bias': 0.1, # Add asymmetry
... 'mix': 0.8 # 80% wet
... })
"""
[docs] def _add_specific_parameters(self):
"""Register additional parameters specific to rectifier distortion.
Adds:
mode: Rectification type (0.0 to 1.0)
threshold: Signal threshold (0.0 to 1.0)
"""
self.params.update({
'mode': EffectParam(min_val=0.0, max_val=1.0), # 0: half-wave, 1: full-wave
'threshold': EffectParam(min_val=0.0, max_val=1.0) # threshold for rectification
})
[docs] def _apply_distortion(self, x, params):
"""Apply rectification distortion to the input signal.
Args:
x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples)
params (dict): Must contain:
- mode: Rectification type (0: half-wave, 1: full-wave)
- threshold: Signal threshold for rectification
Returns:
torch.Tensor: Rectified audio tensor of same shape as input
Note:
Interpolates smoothly between half-wave and full-wave rectification
using the mode parameter.
"""
# Get parameters
mode = params['mode'].view(-1, 1, 1)
threshold = params['threshold'].view(-1, 1, 1)
# Apply threshold
x = torch.where(torch.abs(x) < threshold, torch.zeros_like(x), x)
# Interpolate between half and full wave rectification
half_wave = torch.relu(x) # half-wave rectification
full_wave = torch.abs(x) # full-wave rectification
return torch.lerp(half_wave, full_wave, mode)
[docs]class ArcTanDist(BaseDistortion):
"""Differentiable implementation of arctangent distortion.
This processor implements smooth distortion using the arctangent function for waveshaping.
Similar to tanh distortion but with slightly different harmonic characteristics, providing
musical saturation with natural compression and rich overtones.
The transfer function is:
.. math::
y = \\frac{2}{\\pi} \\arctan(x * drive)
where:
- x is the input signal
- drive controls distortion intensity
- 2/π factor normalizes output to [-1, 1] range
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable waveshaping controls. Defaults to False.
When True, enables:
- Pre-gain control
- DC bias adjustment
- Post-gain control
- Automatic DC filtering
Parameters Details:
Basic Mode (shaping_mode=False):
drive: Distortion intensity
- Range: 0.1 to 10.0
- Controls slope of arctangent curve
- Higher values create more saturation
- Lower values provide subtle warming
mix: Wet/dry mix ratio
- Range: 0.0 to 1.0
- 0.0: Only clean signal
- 1.0: Only distorted signal
Shaping Mode (shaping_mode=True):
pre_gain_db: Input gain before distortion
- Range: -24.0 to 24.0 dB
- Controls signal level into arctangent
post_gain_db: Output gain after distortion
- Range: -24.0 to 0.0 dB
- Compensates for level changes
dc_bias: DC offset before distortion
- Range: -0.2 to 0.2
- Affects harmonic content
Examples:
Basic DSP Usage:
>>> # Create an arctangent distortion
>>> dist = ArcTanDist(sample_rate=44100)
>>> # Process with moderate drive
>>> output = dist(input_audio, dsp_params={
... 'drive': 4.0, # Moderate saturation
... 'mix': 0.7 # 70% wet signal
... })
Advanced Usage (shaping_mode=True):
>>> # Create with waveshaping controls
>>> dist = ArcTanDist(sample_rate=44100, shaping_mode=True)
>>> # Process with detailed control
>>> output = dist(input_audio, dsp_params={
... 'drive': 6.0, # Strong saturation
... 'pre_gain_db': 6.0, # Additional drive
... 'post_gain_db': -3.0, # Slight attenuation
... 'dc_bias': 0.05, # Slight asymmetry
... 'mix': 0.8 # 80% wet
... })
"""
[docs] def _add_specific_parameters(self):
"""Register additional parameters specific to arctangent distortion.
Adds:
drive: Distortion intensity (0.1 to 10.0)
"""
self.params.update({
'drive': EffectParam(min_val=0.1, max_val=10.0) # Drive amount for atan curve
})
[docs] def _apply_distortion(self, x, params):
"""Apply arctangent distortion to the input signal.
Args:
x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples)
params (dict): Must contain:
- drive: Distortion intensity
Returns:
torch.Tensor: Distorted audio tensor of same shape as input
Note:
Output is automatically normalized to [-1, 1] range using
the (2/π) scaling factor.
"""
# Get drive parameter
drive = params['drive'].view(-1, 1, 1)
# Apply arctangent with scaling
# (2/π) factor normalizes output to [-1, 1] range
return (2/np.pi) * torch.atan(x * drive)
[docs]class ExponentialDist(BaseDistortion):
"""Differentiable implementation of exponential distortion with asymmetry control.
This processor implements distortion using exponential curves, featuring independent control
over positive and negative regions. It creates dynamic saturation characteristics with
natural compression and adjustable asymmetry for rich harmonic content.
The transfer function is:
.. math::
y = sign(x) * (1 - e^{-|x| * drive * A(x)})
where:
- x is the input signal
- drive controls overall distortion intensity
- A(x) is the asymmetry function:
- A(x) = 1 for x ≥ 0
- A(x) = asymmetry for x < 0
Args:
sample_rate (int): Audio sample rate in Hz
shaping_mode (bool): Whether to enable waveshaping controls. Defaults to False.
- Pre-gain control
- DC bias adjustment
- Post-gain control
- Automatic DC filtering
Parameters Details:
Basic Mode (shaping_mode=False):
drive: Distortion intensity
- Range: 0.1 to 10.0
- Controls steepness of exponential curve
- Higher values create more saturation
- Affects both positive and negative regions
asymmetry: Positive/negative balance
- Range: 0.1 to 2.0
- 1.0: Symmetric distortion
- <1.0: More negative distortion
- >1.0: More positive distortion
mix: Wet/dry mix ratio
- Range: 0.0 to 1.0
- 0.0: Only clean signal
- 1.0: Only distorted signal
Shaping Mode (shaping_mode=True):
pre_gain_db: Input gain before distortion
- Range: -24.0 to 24.0 dB
- Controls signal level into exponential curve
post_gain_db: Output gain after distortion
- Range: -24.0 to 0.0 dB
- Compensates for level changes
dc_bias: DC offset before distortion
- Range: -0.2 to 0.2
- Affects harmonic balance
Examples:
Basic DSP Usage:
>>> # Create an exponential distortion
>>> dist = ExponentialDist(sample_rate=44100)
>>> # Process with asymmetric settings
>>> output = dist(input_audio, dsp_params={
... 'drive': 3.0, # Moderate drive
... 'asymmetry': 1.5, # More positive distortion
... 'mix': 0.7 # 70% wet signal
... })
Advanced Usage (shaping_mode=True):
>>> # Create with waveshaping controls
>>> dist = ExponentialDist(sample_rate=44100, shaping_mode=True)
>>> # Process with detailed control
>>> output = dist(input_audio, dsp_params={
... 'drive': 5.0, # Strong drive
... 'asymmetry': 0.8, # More negative distortion
... 'pre_gain_db': 6.0, # Additional input drive
... 'post_gain_db': -6.0, # Output attenuation
... 'dc_bias': 0.1, # Slight positive offset
... 'mix': 0.8 # 80% wet
... })
"""
[docs] def _add_specific_parameters(self):
"""Register additional parameters specific to exponential distortion.
Adds:
drive: Distortion intensity (0.1 to 10.0)
asymmetry: Positive/negative balance (0.1 to 2.0)
"""
self.params.update({
'drive': EffectParam(min_val=0.1, max_val=10.0), # Drive amount for exp curve
'asymmetry': EffectParam(min_val=0.1, max_val=2.0) # Controls positive/negative asymmetry
})
[docs] def _apply_distortion(self, x, params):
"""Apply exponential distortion to the input signal.
Args:
x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples)
params (dict): Must contain:
- drive: Distortion intensity
- asymmetry: Positive/negative balance
Returns:
torch.Tensor: Distorted audio tensor of same shape as input
Note:
Processes positive and negative regions independently using
the asymmetry parameter to control their relative intensity.
"""
# Get parameters
drive = params['drive'].view(-1, 1, 1)
asymmetry = params['asymmetry'].view(-1, 1, 1)
# Split processing for positive and negative values
# Use sign(x) to maintain the sign while applying different curves
return torch.sign(x) * (1 - torch.exp(-torch.abs(x) * drive * torch.where(x >= 0, torch.ones_like(x), asymmetry)))