Differentiable Distortion Residual Level (DRL) metric for audio processing
Project description
distortion-residual
A differentiable Distortion Residual Level (DRL) metric for PyTorch.
DRL measures the nonlinear distortion introduced by audio processors (limiters, compressors, saturators, etc.) using the nulling method:
- Level-match the reference to the processed signal via least-squares projection, cancelling any linear gain difference.
- Subtract the matched reference from the processed signal to isolate the distortion residual.
- Measure the power ratio of the residual to the signal:
$$ \text{DRL} = 10 \log_{10} \frac{\lVert d \rVert^2}{\lVert \hat{g},x \rVert^2}, \qquad \hat{g} = \frac{\langle x, y \rangle}{\langle x, x \rangle}, \qquad d = y - \hat{g},x $$
Every operation is differentiable, so DRL can be used directly as a loss function for gradient-based optimisation of audio processing parameters.
Installation
pip install distortion-residual
Or from source:
git clone https://github.com/agrathwohl/distortion-residual.git
cd distortion-residual
pip install -e .
Optional: audio file I/O
pip install "distortion-residual[audio]"
Quick start
import torch
from distortion_residual import DRL
drl = DRL(sample_rate=44100)
reference = torch.randn(44100) # 1 s of audio
processed = torch.clamp(reference, -0.5, 0.5) # hard-clip at -6 dBFS
result = drl(reference, processed)
print(result["total_drl_db"]) # e.g. tensor(-18.42)
print(result["total_drl_percent"]) # e.g. tensor(12.0)
As a loss function
gain = torch.tensor(1.0, requires_grad=True)
processed = torch.tanh(reference * gain)
result = drl(reference, processed)
loss = result["total_drl_db"]
loss.backward()
print(gain.grad) # gradient flows through
Band-wise analysis
By default, DRL is decomposed into three frequency bands (20-200 Hz, 200-2000 Hz, 2000-20000 Hz). You can customise or disable this:
# Custom bands
drl = DRL(sample_rate=44100, frequency_bands=[(100, 1000), (1000, 10000)])
# Broadband only (faster, no FIR filtering)
drl = DRL(sample_rate=44100, frequency_bands=None)
Output dictionary
DRL.forward() returns a dict with:
| Key | Type | Description |
|---|---|---|
total_drl_db |
Tensor (scalar) |
Broadband DRL in dB |
total_drl_percent |
Tensor (scalar) |
DRL as a percentage |
band_drl_db |
dict[str, Tensor] |
Per-band DRL in dB |
band_drl_percent |
dict[str, Tensor] |
Per-band DRL as percentage |
residual |
Tensor |
The distortion residual signal |
residual_rms |
Tensor (scalar) |
RMS of the residual |
signal_rms |
Tensor (scalar) |
RMS of the level-matched reference |
How it works
The level-matching step (g_hat = <x,y>/<x,x>) projects out any linear gain component, so DRL is invariant to makeup gain. Only the nonlinear distortion component remains in the residual.
This makes DRL ideal for optimising dynamics processors: the loss function measures what the processor does to the waveform shape, not how loud it makes the output.
Gradient properties
- Through the residual: linear subtraction, gradients pass directly.
- Through level matching: quotient rule on the inner-product ratio.
- Through band filters: FIR convolution is a linear operation.
All paths are fully differentiable. No straight-through estimators or surrogate gradients required.
Development
git clone https://github.com/agrathwohl/distortion-residual.git
cd distortion-residual
uv sync --extra dev
uv run pytest
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file distortion_residual-0.1.0.tar.gz.
File metadata
- Download URL: distortion_residual-0.1.0.tar.gz
- Upload date:
- Size: 76.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e043f1b4ccc514130949c4c3329e295d5e70a2c520935b2f309b2e4390ee3913
|
|
| MD5 |
e94de06fc556ef599e50cd74993e6cbe
|
|
| BLAKE2b-256 |
b763017b69ee80ab9087e76dba1dfcb084d773408ab34142a2491bf8cb2f1573
|
File details
Details for the file distortion_residual-0.1.0-py3-none-any.whl.
File metadata
- Download URL: distortion_residual-0.1.0-py3-none-any.whl
- Upload date:
- Size: 7.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e0f3124d30daeffd1bbff8dff66f8736a2855609634059caea8ebef9b6f9e9a
|
|
| MD5 |
522a31d07ed42bb2b0bd32cca937819d
|
|
| BLAKE2b-256 |
bf809eedde035766b46d7c88584145465b0430ebb6a6e841e3ac826e90f08de2
|