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.

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

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.

  1. Hysteresis-like memory smoothing
  2. Symmetric print-through ghosting
  3. Progressive high-frequency loss
  4. 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.

This is a compositional model, not a calibrated tape-machine emulator. The controls reproduce tape-associated transformation types in stable, direct DSP forms. They do not model a particular tape formulation, bias calibration, head geometry, saturation curve, transport mechanism, or measured machine response.

What is actually simulated?

LabelImplemented mechanismImportant distinction
Memory / hysteresis-likeNormalized one-pole sample memoryNot a nonlinear magnetic hysteresis loop and not saturation.
Print-throughSymmetric pre/post delayed ghosts from a frozen snapshotOne fixed delay in milliseconds; ghost amount decays across generations.
HF lossBlend between the signal and a three-sample low-pass smootherSimple progressive spectral softening, not a measured tape/head frequency response.
Wow / flutterSinusoidal time displacement with independent random phase each generationTrue time-position modulation; not amplitude modulation.

The older bias modulation control is no longer part of the current script.

Quick start

  1. Select exactly one Sound object.
  2. Run Magnetic_Tape_Degradation.praat.
  3. Choose Custom, Subtle Tape, Medium Tape, Heavy Tape, or Extreme Tape.
  4. For Custom, set Generations and Tail_duration_s.
  5. Enable Show_advanced_settings to edit memory, print-through, HF-loss, wow/flutter and output controls.
  6. Run the script. The result is named <source>_tape_<preset>.
Preset/advanced interaction: the Advanced Settings dialog appears before preset values are applied. A named preset therefore overwrites the advanced synthesis values after the dialog closes. Use Custom when you want the advanced values to remain exactly as entered.

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.

There is no exposed random seed, so otherwise identical runs can differ when wow/flutter is active.

Presets

PresetGen.TailMemory C/PPrint initial / decay / delayHF loss/passWowFlutterPeakFade
Subtle Tape31.5 s0.78 / 0.220.10 / 0.85 / 100 ms0.0550.45 Hz / 0.55 ms6.2 Hz / 0.05 ms0.900.8 s
Medium Tape62.0 s0.70 / 0.300.22 / 0.80 / 120 ms0.100.55 Hz / 1.20 ms6.0 Hz / 0.12 ms0.871.0 s
Heavy Tape102.8 s0.62 / 0.380.32 / 0.76 / 160 ms0.140.65 Hz / 2.10 ms5.5 Hz / 0.20 ms0.851.4 s
Extreme Tape154.0 s0.55 / 0.450.42 / 0.70 / 220 ms0.180.72 Hz / 3.80 ms5.0 Hz / 0.35 ms0.821.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

ParameterDefaultMeaning
PresetCustomCustom plus four named degradation profiles.
Generations6Complete degradation passes; 1–50.
Tail_duration_s2.0Silence appended before processing; may be 0.
Draw_visualizationOnDraw waveforms, degradation graph, spectra and summary.
Play_resultOnPlay the result after processing.
Show_advanced_settingsOffOpen the second dialog containing DSP controls.

Advanced settings — Custom defaults

ParameterDefaultValidation / behavior
Hysteresis current0.70Must be > 0; normalized together with previous.
Hysteresis previous0.30Must be ≥ 0; normalized together with current.
Print through initial0.220–1.
Print through decay0.800–1 multiplicative generation-to-generation decay.
Print through delay ms120Must be > 0 ms.
HF loss per generation0.100 ≤ value < 1.
Wow rate Hz0.55Must be ≥ 0.
Wow depth ms1.20Must be ≥ 0.
Flutter rate Hz6.0Must be ≥ 0.
Flutter depth ms0.12Must be ≥ 0.
Scale peak ceiling0.870 < value ≤ 1; downward safety ceiling only.
Fadeout duration s1.0Must 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:

  1. Original waveform — neutral gray.
  2. Tape waveform — processed result in blue.
  3. Degradation across generations — the central process graph.
  4. Memory / transport strip — parameters that cannot honestly share the graph's 0–1 axis.
  5. Original spectrum / Tape spectrum — spectral comparison up to min(8 kHz, Nyquist).
  6. 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:

CurveExact plotted valueInterpretation
HF-loss stage(1 - HF_loss_per_generation)^generationExact response of the script's HF-loss stage at the Nyquist edge after repeated passes. It falls cumulatively from 1.
Print ghostinitial × 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

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.