Magnetic Tape Degradation — User Guide
A compositional tape-degradation model that repeatedly applies memory smoothing, symmetric print-through ghosts, progressive high-frequency loss, and wow/flutter time displacement.
What this does
Magnetic Tape Degradation makes a zero-based processing copy of the selected Sound, optionally appends a silent tail, and passes the complete signal through the same four-stage degradation chain for a user-selected number of generations.
- Hysteresis-like memory smoothing
- Symmetric print-through ghosting
- Progressive high-frequency loss
- Wow/flutter transport displacement
The output retains the input channel count. Every DSP formula is applied independently to each channel, while the visualization converts multichannel source/result copies to mono only for display.
What is actually simulated?
| Label | Implemented mechanism | Important distinction |
|---|---|---|
| Memory / hysteresis-like | Normalized one-pole sample memory | Not a nonlinear magnetic hysteresis loop and not saturation. |
| Print-through | Symmetric pre/post delayed ghosts from a frozen snapshot | One fixed delay in milliseconds; ghost amount decays across generations. |
| HF loss | Blend between the signal and a three-sample low-pass smoother | Simple progressive spectral softening, not a measured tape/head frequency response. |
| Wow / flutter | Sinusoidal time displacement with independent random phase each generation | True time-position modulation; not amplitude modulation. |
The older bias modulation control is no longer part of the current script.
Quick start
- Select exactly one Sound object.
- Run
Magnetic_Tape_Degradation.praat. - Choose Custom, Subtle Tape, Medium Tape, Heavy Tape, or Extreme Tape.
- For Custom, set Generations and Tail_duration_s.
- Enable Show_advanced_settings to edit memory, print-through, HF-loss, wow/flutter and output controls.
- Run the script. The result is named
<source>_tape_<preset>.
What “generations” means
A generation is one complete pass through all four processing stages. The output of one generation becomes the input to the next:
Generation 1: memory → print-through → HF loss → wow/flutter Generation 2: memory → print-through → HF loss → wow/flutter ... Generation N: memory → print-through → HF loss → wow/flutter
This is why the degradation accumulates. Memory and HF filtering are repeated, fresh wow/flutter phases are drawn for every pass, and the print-through coefficient is reduced after every generation.
Generations is limited to 1–50.
Memory / hysteresis-like smoothing
The two user coefficients are first normalized:
sum = Hysteresis_current + Hysteresis_previous current = Hysteresis_current / sum previous = Hysteresis_previous / sum
The generation then applies a one-pole memory relation:
y[n] = current × x[n] + previous × y[n-1]
Because the normalized coefficients sum to 1, the stage has unity DC gain. Increasing the normalized previous term increases short-term temporal memory and softens rapid sample-to-sample changes.
The first sample is left unchanged. This stage is intentionally described as hysteresis-like memory smoothing; it is not a nonlinear magnetic hysteresis model.
Print-through ghosting
Each generation first freezes the signal after the memory stage. The current sample and both delayed directions are then read from that same frozen snapshot:
p[g] =
Print_through_initial
× Print_through_decay^(g-1)
y(t) =
[x(t)
+ 0.5 p[g] x(t - D)
+ 0.5 p[g] x(t + D)]
/ [1 + p[g]]
D = Print_through_delay_ms / 1000
This creates a post-ghost and a pre-ghost at the same delay. The division by 1+p keeps the nominal DC gain at unity away from finite-signal boundaries.
The delay is specified directly in milliseconds and does not depend on source duration or sample count.
Progressive high-frequency loss
The HF-loss stage blends the current generation with a symmetric three-sample smoother:
smooth[n] =
0.25 x[n-1]
+ 0.50 x[n]
+ 0.25 x[n+1]
y[n] =
(1-a) x[n] + a smooth[n]
a = HF_loss_per_generation
Both branches have unity gain at DC. At Nyquist, the three-sample smoother has zero response, so one pass retains exactly:
Nyquist-edge retention per pass = 1 - a after G generations: edgeRetention = (1 - a)^G
This exact quantity is what the visualization's HF-loss curve plots. The filter is deliberately simple: repeated generations progressively reduce the highest-frequency edge without the gain instability of the earlier model.
Wow & flutter
Wow and flutter are implemented as time displacement. At every generation the script freezes the pre-stage signal and reads it at:
tRead =
t
+ wowDepth × sin(2π wowRate t + randomWowPhase)
+ flutterDepth × sin(2π flutterRate t + randomFlutterPhase)
The read time is clamped to the valid 0…output-duration range and Praat interpolates positional reads. The same displacement is used for all channels in a given generation, so their relative spatial timing remains linked.
- Wow uses the lower-frequency rate and larger millisecond depth.
- Flutter uses the faster rate and smaller depth.
- Each generation receives new random phases.
There is no exposed random seed, so otherwise identical runs can differ when wow/flutter is active.
Presets
| Preset | Gen. | Tail | Memory C/P | Print initial / decay / delay | HF loss/pass | Wow | Flutter | Peak | Fade |
|---|---|---|---|---|---|---|---|---|---|
| Subtle Tape | 3 | 1.5 s | 0.78 / 0.22 | 0.10 / 0.85 / 100 ms | 0.055 | 0.45 Hz / 0.55 ms | 6.2 Hz / 0.05 ms | 0.90 | 0.8 s |
| Medium Tape | 6 | 2.0 s | 0.70 / 0.30 | 0.22 / 0.80 / 120 ms | 0.10 | 0.55 Hz / 1.20 ms | 6.0 Hz / 0.12 ms | 0.87 | 1.0 s |
| Heavy Tape | 10 | 2.8 s | 0.62 / 0.38 | 0.32 / 0.76 / 160 ms | 0.14 | 0.65 Hz / 2.10 ms | 5.5 Hz / 0.20 ms | 0.85 | 1.4 s |
| Extreme Tape | 15 | 4.0 s | 0.55 / 0.45 | 0.42 / 0.70 / 220 ms | 0.18 | 0.72 Hz / 3.80 ms | 5.0 Hz / 0.35 ms | 0.82 | 1.8 s |
Custom starts with the same internal values as Medium Tape, but keeps the form's user-entered Generations and Tail_duration_s and any advanced values entered by the user.
Named presets do not overwrite Draw_visualization or Play_result.
Parameters
Main form
| Parameter | Default | Meaning |
|---|---|---|
| Preset | Custom | Custom plus four named degradation profiles. |
| Generations | 6 | Complete degradation passes; 1–50. |
| Tail_duration_s | 2.0 | Silence appended before processing; may be 0. |
| Draw_visualization | On | Draw waveforms, degradation graph, spectra and summary. |
| Play_result | On | Play the result after processing. |
| Show_advanced_settings | Off | Open the second dialog containing DSP controls. |
Advanced settings — Custom defaults
| Parameter | Default | Validation / behavior |
|---|---|---|
| Hysteresis current | 0.70 | Must be > 0; normalized together with previous. |
| Hysteresis previous | 0.30 | Must be ≥ 0; normalized together with current. |
| Print through initial | 0.22 | 0–1. |
| Print through decay | 0.80 | 0–1 multiplicative generation-to-generation decay. |
| Print through delay ms | 120 | Must be > 0 ms. |
| HF loss per generation | 0.10 | 0 ≤ value < 1. |
| Wow rate Hz | 0.55 | Must be ≥ 0. |
| Wow depth ms | 1.20 | Must be ≥ 0. |
| Flutter rate Hz | 6.0 | Must be ≥ 0. |
| Flutter depth ms | 0.12 | Must be ≥ 0. |
| Scale peak ceiling | 0.87 | 0 < value ≤ 1; downward safety ceiling only. |
| Fadeout duration s | 1.0 | Must be ≥ 0; effective fade cannot exceed output duration. |
Tail & fadeout
The script first shifts the processing copy to start at 0. If Tail_duration_s is positive, a silent Sound with the same channel count and sample rate is concatenated to the source before any degradation pass.
processing duration =
original duration + tail duration
Because the tail is present during every generation, print-through and time displacement can move source-related energy into that extra region. The output duration remains exactly the source duration plus the requested tail.
After peak safety handling, the final Fadeout_duration_s applies a half-cosine fade over the end of the complete result:
gain(x) =
0.5 + 0.5 cos(
π × (x - fadeStart) / effectiveFade
)
A fade of 0 disables it. There is no second peak adjustment after the fade.
Visualization
The v0.4 Picture output is designed to show the degradation process directly:
- Original waveform — neutral gray.
- Tape waveform — processed result in blue.
- Degradation across generations — the central process graph.
- Memory / transport strip — parameters that cannot honestly share the graph's 0–1 axis.
- Original spectrum / Tape spectrum — spectral comparison up to min(8 kHz, Nyquist).
- Summary — preset, generations, HF loss, print-through, memory, tail, ceiling and output duration.
Reading the degradation graph
The horizontal axis is tape generation; the vertical axis is a shared 0–1 relative amount. Two curves are shown because both quantities have a meaningful dimensionless scale:
| Curve | Exact plotted value | Interpretation |
|---|---|---|
| HF-loss stage | (1 - HF_loss_per_generation)^generation | Exact response of the script's HF-loss stage at the Nyquist edge after repeated passes. It falls cumulatively from 1. |
| Print ghost | initial × decay^(generation-1) | The actual print-through coefficient used for that generation. |
The graph labels the final HF-edge percentage directly and shows the print-through coefficient from its initial value to its last-generation value.
Why memory and wow/flutter are not extra curves
The memory term is a normalized coefficient, while wow/flutter use milliseconds and hertz. Plotting all of them on the same 0–1 axis would imply a comparison that has no physical or computational meaning. They are therefore shown underneath as explicit text:
Memory smear: percentage taken from the previous sample Transport drift: wow depth ms @ wow rate Hz + flutter depth ms @ flutter rate Hz
For waveform comparison, Source and Tape share the same amplitude scale. Multichannel audio is folded to mono only in these display copies; the rendered Sound remains multichannel.
Output behavior
- Name:
<source>_tape_<presetName>. - Channel count: preserved.
- Sample rate: preserved.
- Time domain: zero-based.
- Duration: source duration + Tail_duration_s.
Peak safety
Before the final fade, the script measures the Sinc70 absolute peak:
if peak > Scale_peak:
output *= Scale_peak / peak
else:
leave level unchanged
This is an attenuate-only safety ceiling, not normalization. Quiet output is not boosted.
The original Sound is left unchanged and temporary processing/visualization objects are removed.