Skip to main content

Playground · research instrument

DSP · Audio

Driver Nonlinearity Lab

A lumped-parameter moving-coil loudspeaker model with nonlinear Bl(x), Kms(x), and Le(x). Explore how force-factor droop and suspension stiffening generate harmonic distortion as excursion grows — educational, not measurement-grade.

Independent research instrument — not claimed as MakerPortal shipped product code. Methods, equations, assumptions, and limitations are disclosed so you can inspect what the page does and does not establish.

Motor cross-sectionCoil in gap
MagnetMagnetBack plate

Drive & Parameters

Adjust drive level and nonlinearity coefficients.

Drive Vrms
Frequency Hz
β Bl(x) asymmetry
γ Kms(x) stiffening
δ Le(x) modulation
Mms Moving mass (g)
Re DC resistance (Ω)
Bl0 Force factor (N/A)
Xmax Linear excursion (mm)
Peak excursion
THD estimate
Bl(x) at peak
Kms at peak
Waveform & Bl(x) curveDisplacement vs time
Approximate spectral viewEducational, not measurement-grade

The motor is linear until you ask it to work

Thiele-Small looks innocent at 1 watt. Push a driver to XmaxX_{max} and three curves betray you: Bl(x)Bl(x) falls when coil leaves the gap, Kms(x)K_{ms}(x) stiffens when spider and surround stretch, Le(x)Le(x) breathes with position. Each one is a distortion generator. This lab lets you dial them and watch the sine turn into something else.

1 — Bl(x): force factor droop

Ideal motor gives constant BlBl. Real gap has finite height. As coil moves out, fewer turns sit in BB-field. Force per amp drops. If coil is centered, droop is symmetric; if offset (common after sag or assembly), one side drops earlier — even-order distortion.

Fmotor=Bl(x)iF_{motor} = Bl(x) \cdot i

Bl(x)=Bl0(1βx/Xmax)Bl(x) = Bl_0 (1 - \beta |x / X_{max}|)

β\beta is asymmetry/shape knob. In Klippel measurements you see a bell curve, not this triangle — peak Bl0Bl_0 at rest, down to 70% at XmaxX_{max} for a good long-throw, 50% for a cheap one. Twice excursion → twice loss → current has to rise → more heat → more compression. That coupling is outside this model, but you feel it in the motor SVG when status flips to "Coil out of gap".

2 — Kms(x): spider gets angry

Suspension compliance is progressive. Small xx, soft. Large xx, spider fibers align, surround folds stretch — stiffness climbs quadratically. This is symmetric by nature, so it makes odd harmonics.

Kms(x)=Kms0(1+γ(x/Xmax)2)K_{ms}(x) = K_{ms0}(1 + \gamma (x/X_{max})^2)

Kms0=Mms(2πfs)2K_{ms0} = M_{ms}(2\pi f_s)^2

Fixed fs=40f_s=40 Hz here gives Kms0K_{ms0}. γ\gamma multiplies the square term. γ=0.2\gamma=0.2 is a soft progressive spider, γ=1\gamma=1 is a harsh bottoming surround. Because force is Kms(x)xK_{ms}(x) \cdot x, you get an x3x^3 term — same math that makes a Duffing oscillator generate 3rd harmonic. Push Vrms and watch waveform flatten on peaks.

3 — Le(x): inductance modulates

Voice-coil inductance depends on how much steel it overlaps. Move out, LeL_e drops ~δ\delta. That sounds harmless until you realize impedance changes with position, so current waveform distorts even with perfect voltage drive. Add flux modulation (current itself modulates BB) and you get Bl(i)Bl(i) — not modeled here but measured by Klippel Le(x,i)L_{e}(x,i).

Le(x)=Le0(1+δx/Xmax)Le(x) = Le_0 (1 + \delta |x/X_{max}|)

i=VinBl(x)vReDC approx, full: + Le⋅di/dti = \frac{V_{in} - Bl(x) v}{R_e} \quad\text{DC approx, full: + Le·di/dt}

The back-EMF Bl(x)vBl(x)v is velocity times force factor — it fights input voltage. When BlBl droops at peak xx, back-EMF collapses, current spikes. That spike hits non-flat KmsK_{ms} and you have intermodulation. In the canvas lower half, watch Bl(x)Bl(x) bell shrink with β\beta and dot trace current position.

4 — Where distortion comes from: Taylor tells all

Any memoryless nonlinearity can be expanded:

y=a1x+a2x2+a3x3+...y = a_1 x + a_2 x^2 + a_3 x^3 + ...

Drive x=X^sinωtx = \hat{X}\sin\omega t:

x2=X^22(1cos2ωt)2nd harmonic + DC shiftx^2 = \frac{\hat{X}^2}{2}(1 - \cos 2\omega t) \rightarrow \text{2nd harmonic + DC shift}

x3=X^34(3sinωtsin3ωt)3rd harmonic + fundamental compressionx^3 = \frac{\hat{X}^3}{4}(3\sin\omega t - \sin 3\omega t) \rightarrow \text{3rd harmonic + fundamental compression}

So asymmetric Bl(x)Bl(x) → even order (2nd), symmetric Kms(x)K_{ms}(x) → odd order (3rd). Real driver does both. THD here computed from displacement spectrum via numerical harmonic extraction:

THD=V22+V32+V42V1THD = \frac{\sqrt{V_2^2+V_3^2+V_4^2}}{V_1}

Klippel's measurement rig does this for real with laser and current sense, plotting Bl(x)Bl(x), Kms(x)K_{ms}(x), Le(x)Le(x) versus xx. Golden rule: keep BlBl above 80% and KmsK_{ms} within 50% over intended XX. Once outside, no motor feedback fixes mechanical bottoming.

5 — Playbook & honesty

How to use

  • Set Vrms=12V_{rms}=12 V, f=60f=60 Hz, β=0.1\beta=0.1, γ=0.2\gamma=0.2 — this is a decent 6" woofer. Waveform looks sinusoidal, THD 2–5%, Bl(x)Bl(x) dot stays near top.
  • Now push Vrms to 30 V or drop XmaxX_{max} to 2 mm. Excursion exceeds gap, coil dot leaves bell, displacement waveform flattens, 3rd harmonic bar pops up in spectrum.
  • Isolate mechanisms: β=0\beta=0, γ=0.8\gamma=0.8 → pure suspension odd distortion. β=0.5\beta=0.5, γ=0\gamma=0 → even. Combine — you get real driver stew.
  • Lower MmsM_{ms} raises acceleration a=F/Ma=F/M, raises fsf_s via Kms0=M(2πfs)2K_{ms0}=M(2\pi f_s)^2 — lighter cone distorts more at high freq? Check.

What this omits

Euler at 8 kHz timestep, no flux modulation Bl(i)Bl(i), no eddy-current hysteresis in pole, no thermal memory, no cone breakup, no port nonlinearity, no equivalent circuit of QmsQ_{ms}, QesQ_{es}. BlBl, KmsK_{ms}, LeLe shapes are simplified polynomials — real curves have asymmetry, plateau, and hysteresis loops. THD is displacement THD via 1024-sample history, not acoustic SPL THD. Use for qualitative cause → effect, not for validating a driver design. For that, need Klippel LSI or laser vibrometer.

Anatomy of the instrument

A real-time ODE solver at 8 kHz, two live canvases, an animated motor SVG, and a harmonic analyzer. Here is what each piece computes and how the render loop ties them together.

The physics engine and motor SVG

  1. 01

    Euler integration at 8 kHz. Three state variables — displacement x, velocity v, current i — updated via forward Euler with dt=1/8000dt = 1/8000 s. Each step: compute Bl(x)Bl(x), Kms(x)K_{ms}(x), Le(x)Le(x) from the current x, then integrate: inew=i+(VinReiBlv)/Ledti_{new} = i + (V_{in} - R_e i - Bl\cdot v)/L_e \cdot dt, a=(BliKmsx)/Mmsa = (Bl\cdot i - K_{ms}\cdot x)/M_{ms}, vnew=v+adtv_{new} = v + a\cdot dt, xnew=x+vnewdtx_{new} = x + v_{new}\cdot dt. A 1024-sample circular buffer stores displacement and voltage history for waveform display and harmonic extraction.

  2. 02

    Run/pause with fixed timestep accumulation. The renderFrame() callback measures elapsed wall-clock time and accumulates physics steps. When running, up to 5000 steps are processed per frame to prevent spiral-of-death if the tab loses focus. The accumulator caps at 0.05 s per frame to prevent bursts. When paused, physics freezes but canvases still redraw — you can study a frozen state.

  3. 03

    Animated motor SVG. The coil-group, cone, and force arrow translate vertically based on the historical peak excursion scaled to ±30 px. The gap indicator dims when |x| exceeds Xmax, and the status label switches from "Coil in gap" to "Coil out of gap" styled in the theme's magenta color. The force arrow length scales with Bl(x)i|Bl(x)\cdot i| — you see the motor push weaken as the coil leaves the gap.

  4. 04

    THD from harmonic extraction. Every frame, the extractHarmonics() function from the DSP library performs numerical Fourier integration over the 1024-sample displacement buffer at the drive frequency and its first three harmonics. THD = √(H₂²+H₃²+H₄²)/H₁ × 100%. This is computed synchronously in the animation loop — no FFT, no windowing overhead.

The two canvases

  1. 05

    Waveform canvas (top). Upper half: displacement (magenta, 2 px) and input voltage (gray, 1 px) over the full 1024-sample history window. Auto-scaled symmetrically so the zero line stays at the midpoint. The y-axis labels show the current voltage and displacement ranges. Lower half: the Bl(x)Bl(x) curve — a tent shape whose width is set by Xmax and whose height is Bl0Bl_0. A magenta dot marks the current operating point on the curve.

  2. 06

    Spectrum canvas (bottom). Up to four harmonic bars positioned by their frequency on a linear x-axis from 0 to 5×f₀. The fundamental (f₀) bar uses the magenta color; harmonics 2–4 use blue. Each bar is labeled with its harmonic order and displacement amplitude in mm. The THD readout in the top-left corner matches the metric card. The maximum amplitude is auto-scaled to 1.3× the largest harmonic.

  3. 07

    Visibility-aware frame skipping. An IntersectionObserver watches the motor card. When the page is scrolled off-screen or the tab is hidden, the render loop skips canvas drawing (but continues physics accumulation). This prevents wasting GPU on invisible canvases while maintaining physics continuity for when the user scrolls back.

  4. 08

    Respects reduced-motion. On page load, the script checks prefers-reduced-motion. If the user has that preference enabled, the simulation starts paused so no animation runs automatically.

The rendering loop

Each frame: if running, accumulate elapsed time into physics steps. Then call syncControls() (update metrics, animate SVG) and renderPlots() (redraw both canvases from the circular buffer). The canvases use setupHiDPI() for retina-quality rendering. On theme change, all canvases redraw automatically via a MutationObserver.

Gear for driver measurement

Measurement stack · 14 picks

Audio interfaces & monitoring14

More gear across every app: the full Gear list →

Two gotchas worth knowing

Forward Euler is conditionally stable at 8 kHz

The solver uses explicit Euler integration — the simplest possible numerical method. At 8 kHz timestep, the mechanical resonance (40 Hz) is well-resolved (~200 samples per cycle) and the electrical time constant Le/Re ≈ 0.08 ms is just barely stable. If you set extreme parameter combinations (very low Re + very high Le via large δ), the current integration can go unstable and blow up. The tool caps Le at reasonable values to prevent this, but it's a reminder that this is an educational model, not a production circuit simulator.

THD from displacement is not THD from SPL

The THD metric analyzes the displacement waveform — how much the cone motion deviates from a pure sine. Acoustic THD depends on additional factors: the cone's radiation impedance, breakup modes that filter displacement into SPL differently at each frequency, air nonlinearity at high SPL, and port distortion in vented boxes. Displacement THD is typically 2–5× lower than measured acoustic THD at bass frequencies and can be higher or lower at mid frequencies depending on breakup. Treat the THD number here as a lower bound on total harmonic distortion.

JavaScript — the nonlinear ODE solver

The core time-stepping loop. Three state variables, three nonlinear curves, one Euler integrator. This is the engine behind the animated SVG and waveform canvases.

Nonlinear driver step — JavaScript

const stepPhysics = (state, dt, t) => {
  const omega = 2 * Math.PI * state.freq;
  const vin = state.vrms * Math.sqrt(2) * Math.sin(omega * t);

  // Nonlinear force factor: drops as coil leaves gap
  const bl = state.bl0 * (1 - state.beta
    * Math.abs(state.x / (state.xmax || 1e-9)));

  // Suspension stiffness: progressive quadratic stiffening
  const kms0 = state.mms * (2 * Math.PI * 40) ** 2;
  const kms = kms0 * (1 + state.gamma
    * (state.x / (state.xmax || 1e-9)) ** 2);

  // Inductance modulation with position
  const le0 = 0.5e-3;
  const le = le0 * (1 + state.delta
    * Math.abs(state.x / (state.xmax || 1e-9)));

  // State update: forward Euler
  const di = (vin - state.re * state.i - bl * state.v) / le * dt;
  state.i += di;
  const a = (bl * state.i - kms * state.x) / state.mms;
  state.v += a * dt;
  state.x += state.v * dt;

  return { x: state.x, v: state.v, i: state.i, bl, kms, le };
};

Frequently asked questions

What exactly do β, γ, and δ control?

β (0–1) controls Bl(x) asymmetry — how much the force factor drops as the coil leaves the magnetic gap. γ (0–1) controls Kms(x) progressive stiffening — spider and surround getting stiffer with excursion. δ (0–1) controls Le(x) modulation — how voice coil inductance changes with position. Set each to 0 to isolate the others; real drivers exhibit all three simultaneously. Typical values for a mid-range 6" woofer: β≈0.1, γ≈0.2, δ≈0.05.

Why does the displacement waveform flatten at high drive?

That's Kms(x) stiffening. As excursion grows, the suspension spring constant increases quadratically (Kms ∝ 1 + γ·(x/Xmax)²), so the restoring force grows faster than displacement. The waveform clips symmetrically — both positive and negative peaks compress. This is a Duffing oscillator and it generates odd harmonics (3rd, 5th). Increase γ to 0.8 and watch the waveform square off.

Is the THD number accurate to real measurements?

No. This THD is computed from the displacement waveform using numerical harmonic extraction over a 1024-sample window — it's displacement THD, not acoustic SPL THD. Acoustic THD includes additional sources: cone breakup, port nonlinearity, air nonlinearity at high SPL, and microphone distortion. The tool is educational: it shows which nonlinearity produces which harmonic pattern. For real driver validation you need a Klippel LSI or laser vibrometer.

What happens when the coil leaves the gap?

The motor SVG shows the coil position relative to the magnet gap. When "Coil out of gap" appears, Bl(x) has dropped significantly — the force per amp is reduced. Current spikes because back-EMF (Bl·v) collapses, and the remaining force is applied through a stiffer suspension. The result is intermodulation distortion: the current waveform interacts with the position-dependent force factor to produce sum and difference frequencies. This is why drivers sound harsh when overdriven.

Why does changing Mms affect fs and distortion?

The tool ties Kms₀ to Mms via the fixed fs = 40 Hz: Kms₀ = Mms·(2π·40)². Heavier cone means higher Kms₀ to maintain the same resonance frequency. But the nonlinear Kms(x) model then applies γ to this higher baseline, so a heavier driver with the same γ is actually stiffer at any given excursion. This is realistic — adding mass without redesigning the suspension increases effective compliance nonlinearity.

Shareable still

The instrument, captured—not illustrated.

This 16:9 frame is rendered from the real browser instrument above. It is the page's canonical preview for image search, link unfurls, and posts that need to show what the tool actually does.

Download 1280 × 720 JPEG
Driver Nonlinearity Lab — live MakerPortal instrument screenshot
Canonical capture · real UI · no generated scientific artwork