Hysteresis Distortion — User Guide

A stateful tanh distortion followed by a one-pole recursive lag. The output depends on the current input and the previous output, producing rate-dependent path separation and controllable smoothing/lag.

Author: Shai Cohen Affiliation: Department of Music, Bar-Ilan University, Israel Version: 0.5.1 (2026) License: MIT License Repo: Praat AudioTools
Contents:

What this does

The script applies a static tanh saturation with bias and then blends each sample with the previous output. This makes the processor stateful: two identical input samples can produce different outputs when they occur after different histories.

f[n] = tanh((x[n] + Bias) × Drive) y[1] = f[1] y[n] = (1 − m) × f[n] + m × y[n−1], n > 1 then: subtract file mean → multiply by Output_Gain → selected output-level stage
Important terminology: this is a hysteresis-like dynamic nonlinear lag, not a physical model of magnetic hysteresis, tape, or a transformer. For a constant input it has one equilibrium, y = tanh((x + Bias) × Drive). It has no modeled coercivity, remanence, domain switching, or multiple stable branches. The visible loop is produced because the recursive lag cannot follow a changing input instantaneously; if the test sweep is made sufficiently slow, the loop collapses toward the single static tanh curve.

The effect is nevertheless musically useful: Drive controls saturation, Bias shifts the tanh operating point, and Memory controls the lag applied to the nonlinear signal.

Quick start

  1. Select exactly one Sound object in Praat.
  2. Run Hysteresis_Distortion_v0.5.1.praat.
  3. Choose a preset or keep Manual.
  4. Choose Memory_mode: legacy per-sample coefficient or sample-rate-independent time constant in milliseconds.
  5. Adjust Drive, Bias and Output_Gain as needed.
  6. Choose the output-level policy. The default remains Normalize to 0.95 for compatibility with earlier renders.
  7. Optionally enable the spectrum panel, then run the script.
For musically interpretable lag times: use Time constant in ms. The legacy coefficient is a per-sample number, so the same coefficient produces a different time constant at different sampling rates.

Algorithm

1. Static nonlinear stage

The current input sample is shifted by Asymmetry_Bias, multiplied by Drive, and passed through tanh():

f(x) = tanh((x + Bias) × Drive)

With Bias = 0, the static tanh is odd-symmetric. A non-zero Bias moves the zero crossing to x = −Bias and makes the static stage asymmetric. Drive may be positive, zero, or negative: zero produces silence at the nonlinear stage; a negative value reverses the polarity of the tanh mapping.

2. Recursive lag

The nonlinear sample is then blended with the previous output. Praat evaluates the Formula from left to right in place, and the script explicitly reads self[row, col − 1], so each channel feeds back only its own previous output.

y[n] = (1 − m) f[n] + m y[n−1]

The first sample is intentionally initialized as y[1] = f[1], rather than assuming an earlier zero state. This avoids a forced startup transient for a file beginning at a steady non-zero value.

3. Mean removal and gain

After the recursion, the script runs Subtract mean, then applies Output_Gain. Mean removal is a file-dependent post-process: it removes the constant component left by bias/asymmetry but is not part of the recursive equation itself.

Memory modes

Per-sample coefficient (legacy)

Hysteresis_Memory is the coefficient m. For 0 < m < 1, its equivalent one-pole time constant is:

τ = −1 / (fs × ln(m)) seconds

This means a coefficient that sounds large numerically can still represent a very short lag. At 44.1 kHz, for example, m = 0.90 is about 0.215 ms and m = 0.99 about 2.26 ms. The report prints the actual time constant for the current file.

Time constant in ms

In this mode, Memory_time_ms is the active memory control and must be above zero. The script computes:

m = exp(−1 / (fs × τ))

Because the coefficient is derived from the file's sampling rate, the requested time constant is consistent across files with different sample rates.

Preset scope: the preset memory numbers are legacy coefficients. They apply only in coefficient mode. In time-constant mode, the preset still changes Drive, Bias and Output_Gain, but Memory_time_ms remains the active manual memory value.

Parameters

ParameterDefaultWhat it controls
PresetManualSets Drive, legacy memory coefficient, Bias and Output_Gain. It does not change Memory_mode, Memory_time_ms, Output_level, spectrum settings or loop-test frequency.
Drive2.0Multiplier inside the tanh stage.
Memory_modePer-sample coefficientSelects legacy coefficient control or a sample-rate-independent time constant.
Hysteresis_Memory0.3Legacy per-sample coefficient.
Memory_time_ms5.0 msTime constant used only in time-constant mode.
Asymmetry_Bias0.0Offsets the input before tanh; non-zero values create an asymmetric static mapping before mean removal.
Output_Gain0.9Constant multiplier after recursion and mean removal. Negative values invert the final polarity before the output-level stage; zero gives silent output.
Output_levelNormalize to 0.95Preserve, conditional global attenuation, or peak normalization.
Show_spectrumOffReplaces the short waveform zoom with a spectrum comparison; adds analysis time.
Spectrum_referenceMatched peakMatched peak removes the global level difference before the spectral comparison; it does not isolate harmonics.
Loop_test_frequency_Hz220 HzRequested sweep rate for the synthetic loop panel. The panel title reports the actual rate obtained from its integer step count.
Draw_visualizationOnDraws the analysis/diagnostic page.
Play_resultOnPlays the processed Sound after completion.

Presets

The names describe musical behavior rather than claiming models of specific analog devices. Numeric values are unchanged from the corresponding legacy presets.

PresetDriveMemory coefficientBiasOutput Gain
Warm Saturation1.50.250.000.95
Dark Lag Saturation2.50.750.000.80
Biased Lag Saturation3.00.400.200.80
Sluggish Fuzz10.00.500.050.50
Hard Saturation20.00.100.000.40
Do not read the coefficient column as milliseconds. At 44.1 kHz, even the 0.75 coefficient in Dark Lag Saturation is only about 0.079 ms. To obtain an explicitly chosen musical lag time, switch to Time constant mode and set Memory_time_ms.

Output & level behavior

The processed object is named <source>_Hyst_<presetName>, using internal names such as WarmSaturation, DarkLagSaturation, BiasedLagSaturation, SluggishFuzz and HardSaturation. The original Sound remains unchanged.

Output levelBehavior
Preserve shaped levelNo final peak scaling. Output_Gain retains its full magnitude effect; peaks above 1.0 are reported as a warning.
Attenuate to 0.95 only if peak > 0.95If the measured peak exceeds 0.95, the whole Sound is scaled down to 0.95. If it is already below 0.95, nothing changes. This is global attenuation, not a sample-by-sample limiter.
Normalize to 0.95For any non-silent output, scales the peak to 0.95. A silent-output guard skips scaling when the peak is zero.
Normalization and Output_Gain: because Output_Gain is a constant scalar, peak normalization removes its magnitude effect. A negative Output_Gain still changes polarity, but 0.4 and 2.0 can produce the same normalized peak magnitude. Use Preserve or conditional attenuation when Output_Gain should remain an audible level control.

The processor preserves the number of input channels. The recursion is independent per channel; the visualization shows both channels for stereo and the first two channels for larger multichannel Sounds.

Visualization

The 8-inch analysis page is diagnostic rather than a claim that one static curve completely describes the rendered audio.

Recursive loop

A synthetic ascending/descending input sweep through the same recurrence. Blue is ascending and red descending. Its width depends on both memory and sweep rate. The title states the actual test frequency. The loop is drawn before mean removal and output-level scaling.

Static tanh stage

The non-recursive tanh((x + Bias) × Drive) curve, including the bias zero-crossing reference. This is also before memory, mean removal and level processing.

Zoom or spectrum

With Show_spectrum off, the first 30 ms (or the whole file if shorter) compares original gray with distorted red, using channel 1 for multichannel display. With spectrum on, original/result spectra are compared up to 8 kHz or Nyquist.

Full output

Displays the actual rendered output after mean removal, Output_Gain and the selected output-level stage.

Spectrum reference

Matched peak does not isolate harmonics. Differences may reflect the tanh nonlinearity, recursive low-pass/lag behavior, bias, mean removal and the source material itself.

How to interpret the sound

For small signals around the tanh operating point, the memory stage behaves approximately like a first-order low-pass filter:

H(e^jω) = (1 − m) / (1 − m e^(−jω))

As Drive increases, tanh contributes nonlinear saturation and harmonics. With Bias = 0 the static waveshaper is odd-symmetric; a non-zero Bias breaks that symmetry before the script removes the final mean, so even-order components can appear for suitable periodic inputs. The recursive lag then changes the magnitude and phase of those components according to frequency.

It is therefore more precise to describe the result as stateful nonlinear saturation plus one-pole lag than as a tape, transformer, or ferromagnetic model.

Limitations & notes

  • Not a physical hysteresis model: there is one static equilibrium for each input; no coercivity, remanence, domain dynamics or rate-independent hysteresis is modeled.
  • Coefficient mode is sample-rate dependent: use the millisecond mode when an absolute lag time matters.
  • Mean removal is global/file-dependent: the final rendered relationship cannot be represented by the pre-mean-removal loop or tanh panel alone.
  • No oversampling: tanh is nonlinear and can generate content above Nyquist; the script does not oversample this waveshaper.
  • Loop panel is a synthetic diagnostic: it traces a controlled sweep, not the source file's actual input/output trajectory.
  • Very high time constants: coefficients approach 1 closely; the recursion remains causal but reacts increasingly slowly.
  • Offline Praat processing: this script renders a new Sound object; it is not presented as a real-time plug-in.

Further Reading