# Loudspeaker ## Microsim <iframe src="https://editor.p5js.org/sciencenibber/full/MOYGtBLpj" width="100%" height="620" frameborder="0" sandbox="allow-scripts allow-same-origin"></iframe> <img src="../SPINTRONICS Images/Loudspeaker.png" alt="Loudspeaker microsim"> *Live sketch: [open in the p5.js editor](https://editor.p5js.org/sciencenibber/sketches/MOYGtBLpj). The poster image above is a placeholder pending an attended or server-side canvas capture.* ### p5.js source ```js // ===================================================================== // Loudspeaker - Wikitube MicroSim (SPINTRONICS hub, branch T - Transducers) // Slug/ARTICLE: "Loudspeaker" -> en.wikitube.io/wiki/Loudspeaker // --------------------------------------------------------------------- // CONCEPT // A loudspeaker is an electric-to-acoustic transducer - the exact DUAL // of the Microphone. Its heart is a CONE driven by an electrodynamic // MOTOR: a voice coil of length L in a radial field B feels the force // F = B*L*i = Bl*i (Bl = force factor / motor constant) // and a moving coil also makes a back-EMF Bl*v that damps it. // The cone is a DRIVEN, DAMPED HARMONIC OSCILLATOR: // Mms*x'' + Rms*x' + (1/Cms)*x = Bl*i // Two derived constants summarise the resonance: // fs = (1/2pi)*sqrt(1/(Mms*Cms)) free-air cone resonance // Qts = Qms*Qes/(Qms+Qes) total (Thiele-Small) Q // with the motor setting the electrical damping Qes = (Re/Bl^2)*sqrt(Mms/Cms). // // With r = f/fs and the shared resonance denominator // D(r) = 1/sqrt( (1-r^2)^2 + (r/Qts)^2 ), // the two physical read-outs are: // Excursion (displacement x): X(r) = D(r) low-pass (flat below fs) // Radiated SPL (acceleration): S(r) = r^2 * D(r) high-pass (flat ABOVE fs) // Far-field on-axis pressure ~ cone (volume) ACCELERATION, hence the r^2: // that single factor flips the microphone's low-pass into a loudspeaker // HIGH-PASS whose flat "piston band" above fs is the usable range, while // the cone's biggest MOTION sits below fs where it radiates least. // Phases mirror the mic, reflected: excursion lags (0 -> -90 -> -180 deg), // radiated SPL leads (+180 -> +90 -> 0 deg). // // A-V PATTERN: composition on a driven damped oscillator + a motor - // * LEFT : Bode magnitude 20*log10(M) vs log f (chart/H) + live dot // + a faint comparison curve for the OTHER output. // * R-TOP : moving-coil driver cross-section (B/G) - magnet + pole gap, // voice coil carrying i, spider + surround, an animated cone; // a motor-force arrow F=Bl*i and a radiated wavefront whose // strength tracks the cone ACCELERATION. // * R-BOT : scope (H) of drive voltage e(t) vs the selected output. // // GOLDEN-RULES COMPLIANCE // * createCanvas(720,520) + pixelDensity(2); layout from width/height. // * Controls use real symbols + meaningful ranges; Reset restores ALL state. // * HUD watermark drawn last: title | URL | hints | live equation footer. // * ASCII only in every string/text(); Unicode (pi, theta, ->) only in comments. // * Frame-rate independent: dt = min(deltaTime/1000, 0.05). The model is // closed-form steady-state (not an energy-conserving ODE) -> Verlet N/A. // * Default noLoop()+redraw() (input-driven); Run toggles a LIGHT loop(). // Per-frame work = a few hundred polyline vertices; static scaffolding // baked once into an offscreen buffer; setup() cheap. No per-pixel / // per-atom inner loops -> does not trip the editor loop-protect. // * Avoided p5 reserved names (incl. methods: mag/map/scale/split/noise): // helpers are magOut/dbOut/DofR/Hri/thetaOut/xFromF/yFromDB/l10; mode // var is outMode. // * p5.disableFriendlyErrors = true; no allocation inside draw(). // ===================================================================== p5.disableFriendlyErrors = true; // quiet the FES (perf + clean console) const ARTICLE = "Loudspeaker"; // single source of truth (HUD/URL/save) const WIKI = "en.wikitube.io/wiki/Loudspeaker"; // ---- frequency + dB axes for the response plot (kept fixed; no rescaling) --- const FMIN = 20, FMAX = 20000; // log frequency axis: 20 Hz .. 20 kHz const DBMIN = -40, DBMAX = 24; // magnitude axis in dB (each output ref 0 dB at its asymptote) // ---- scope full-scales (fixed) ---------------------------------------------- const EAXIS = 22; // drive-voltage axis: e in [-22, +22] V const PAXIS = 3.0; // SPL-mode output axis: normalized pressure [-3, +3] const XAXIS_MM = 10; // excursion-mode output axis: x in [-10, +10] mm const XMAX_MM = 8; // illustrative Xmax (cone bottoms out beyond this) // ---- drive + display scaling ------------------------------------------------ const EREF = 2.83; // reference drive (2.83 Vrms = 1 W into 8 ohm) const SENS = 88; // nominal passband sensitivity (dB SPL @ 1 m, 2.83 V) const XREF_MM = 1.6; // illustrative cone excursion amplitude at EREF, DC (mm) const DISP_PX = 26; // px of drawn cone travel per unit displacement const DISP_MAX = 26; // clamp drawn cone travel to the panel // ---- layout (all derived from the 720x520 canvas; no magic coords in draw) -- const PTOP = 84; // panels top const FOOTY = 326; // y where the live-equation footer sits // LEFT panel: frequency response (Bode magnitude) const LX0 = 52, LX1 = 348, LY0 = PTOP, LY1 = 322; // RIGHT column shared x; split into cross-section (top) + scope (bottom) const RX0 = 396, RX1 = 704; const RT0 = PTOP, RT1 = 198; // cross-section sub-panel y-range const RB0 = 208, RB1 = 322; // scope sub-panel y-range // ---- control defaults (also used by Reset) ---------------------------------- // f and fs sliders hold log10(Hz); outMode: 0=SPL (accel), 1=excursion (displ). const DEF = { f: l10c(800), // 2.903 -> 800 Hz (well into the piston band of a woofer) fs: l10c(60), // 1.778 -> 60 Hz (typical woofer free-air resonance) Q: 0.707, // maximally flat (Butterworth) total Q - no peak E: EREF, // 2.83 V reference drive mode: 0 // SPL (acceleration) }; // ---- DOM controls ----------------------------------------------------------- let sldF, sldFs, sldQ, sldE; // four sliders let btnMode, btnRun, btnReset; // three buttons // ---- animation + mode state ------------------------------------------------- let running = false; // paused by default (noLoop) let tsec = 0; // elapsed model time (s), advanced by dt let outMode = 0; // 0 = SPL (accel), 1 = excursion (displ) // ---- baked static scenery --------------------------------------------------- let scene; // p5.Graphics drawn once in setup() // ASCII-safe base-10 log usable at top level (Math.*) AND in draw ------------- function l10c(x) { return Math.log(x) / Math.LN10; } // for const init (top level) function l10(x) { return Math.log(x) / Math.LN10; } // alias used in helpers // ===================================================================== // model (pure functions - no p5 state; mirrored by the node math harness) // ===================================================================== function DofR(r, Q) { // shared resonance magnitude (excursion) const a = 1 - r * r; return 1 / Math.sqrt(a * a + (r / Q) * (r / Q)); } function magOut(r, Q, mode) { // response magnitude for the selected output const d = DofR(r, Q); return (mode === 0) ? r * r * d : d; // SPL ~ acceleration (r^2*D); excursion ~ x (D) } function dbOut(r, Q, mode) { // response in dB (each ref 0 dB at its asymptote) return 20 * l10(Math.max(magOut(r, Q, mode), 1e-6)); } // complex transfer function H = num/denom so the phase is exact. // denom = (1 - r^2) + j(r/Q) // SPL (acceleration): num = -r^2 (2nd-order high-pass) // excursion (displ.): num = 1 (2nd-order low-pass) function Hri(r, Q, mode) { const dre = 1 - r * r, dim = r / Q; const den = dre * dre + dim * dim || 1e-12; const nre = (mode === 0) ? -r * r : 1; // (nre + j*0) * conj(denom) / |denom|^2 return { re: (nre * dre) / den, im: (-nre * dim) / den }; } function thetaOut(r, Q, mode) { // output phase relative to drive voltage (rad) const h = Hri(r, Q, mode); return Math.atan2(h.im, h.re); } // ===================================================================== // coordinate maps (use p5 map(); these names do NOT shadow p5) // ===================================================================== function xFromF(fHz) { return map(l10(fHz), l10(FMIN), l10(FMAX), LX0 + 34, LX1 - 8); } function yFromDB(db) { return map(db, DBMIN, DBMAX, LY1 - 8, LY0 + 18); } function scopeY(top, bot, v, vmax) { return map(v, -vmax, vmax, bot - 6, top + 16); } function scopeT(t, t0, win) { return map(t, t0, t0 + win, RX0 + 8, RX1 - 6); } // ===================================================================== // setup // ===================================================================== function setup() { createCanvas(720, 520); pixelDensity(2); textFont("Helvetica"); // -- sliders: (min, max, value, step) -------------------------------------- const w = 132; // log10(Hz) sliders for f and fs (log axis is the natural one for audio) sldF = createSlider(l10c(FMIN), l10c(FMAX), DEF.f, 0.01); sldF.position(120, 378); sldF.size(w); sldFs = createSlider(l10c(20), l10c(500), DEF.fs, 0.01); sldFs.position(120, 416); sldFs.size(w); sldQ = createSlider(0.2, 2.0, DEF.Q, 0.01); sldQ.position(476, 378); sldQ.size(w); sldE = createSlider(1.0, 20.0, DEF.E, 0.01); sldE.position(476, 416); sldE.size(w); // redraw on any slider change so the PAUSED view updates live while dragging for (const s of [sldF, sldFs, sldQ, sldE]) s.input(redraw); // -- buttons ---------------------------------------------------------------- btnMode = createButton("Output: SPL (~accel)"); btnMode.position(120, 456); btnMode.size(170, 22); btnMode.mousePressed(toggleMode); btnRun = createButton("Run"); btnRun.position(320, 456); btnRun.size(80, 22); btnRun.mousePressed(toggleRun); btnReset = createButton("Reset"); btnReset.position(476, 456); btnReset.size(80, 22); btnReset.mousePressed(resetAll); buildScene(); // bake panels, axes, gridlines, magnet/housing ONCE noLoop(); // input-driven by default; Run switches to loop() } // ===================================================================== // buildScene - bake everything static into the offscreen buffer once // ===================================================================== function buildScene() { scene = createGraphics(720, 520); const g = scene; g.pixelDensity(2); g.background("#0f1420"); g.textFont("Helvetica"); // ---- top caption ---------------------------------------------------------- g.noStroke(); g.fill("#7f8aa3"); g.textSize(9); g.textAlign(CENTER, TOP); g.text("drive voltage e(t) -> voice-coil force F=Bl*i -> cone motion -> radiated sound", 360, 26); // ---- LEFT panel: frequency-response (Bode magnitude) frame + grid ---------- panelFrame(g, LX0, LY0, LX1, LY1, "frequency response |H| (dB) vs log f"); // horizontal dB gridlines + labels g.textSize(8); g.textAlign(RIGHT, CENTER); for (let db = DBMIN; db <= DBMAX; db += 8) { const yy = map(db, DBMIN, DBMAX, LY1 - 8, LY0 + 18); g.stroke(db === 0 ? "#55708f" : "#202b41"); // 0 dB line emphasised g.strokeWeight(db === 0 ? 1.3 : 1); g.line(LX0 + 34, yy, LX1 - 6, yy); g.noStroke(); g.fill(db === 0 ? "#8fa6c8" : "#66789c"); g.text(db, LX0 + 31, yy); } // vertical decade gridlines + labels (log f) const decades = [20, 100, 1000, 10000, 20000]; const dlabels = ["20", "100", "1k", "10k", "20k"]; g.textAlign(CENTER, TOP); for (let i = 0; i < decades.length; i++) { const xx = map(l10(decades[i]), l10(FMIN), l10(FMAX), LX0 + 34, LX1 - 8); g.stroke("#202b41"); g.strokeWeight(1); g.line(xx, LY0 + 18, xx, LY1 - 8); g.noStroke(); g.fill("#66789c"); g.text(dlabels[i], xx, LY1 - 6); } // axis titles g.fill("#9aa6c2"); g.textSize(9); g.textAlign(LEFT, TOP); g.text("dB", LX0 + 6, LY0 + 16); g.textAlign(RIGHT, BOTTOM); g.text("f (Hz, log)", LX1 - 6, LY1 - 18); // ---- RIGHT-TOP: driver cross-section frame + fixed magnet/housing ---------- panelFrame(g, RX0, RT0, RX1, RT1, "moving-coil driver F = Bl*i"); // magnet + pole assembly on the RIGHT (back of the driver); gap faces the coil const mxx = RX1 - 54; // magnet block left edge const cyy = (RT0 + RT1) / 2 + 4; // driver axis (vertical centre) g.noStroke(); g.fill("#2b2b3a"); g.rect(mxx, cyy - 34, 44, 68, 4); // magnet body g.fill("#3a3340"); g.rect(mxx - 16, cyy - 12, 18, 24, 2); // front pole piece g.stroke("#5b6b88"); g.strokeWeight(1); g.noFill(); g.rect(mxx - 16, cyy - 30, 60, 60); // top/bottom plates frame g.noStroke(); g.fill("#caa15a"); g.textSize(9); g.textAlign(CENTER, CENTER); g.text("N", mxx + 22, cyy - 22); g.text("S", mxx + 22, cyy + 22); // the magnetic GAP where the coil rides (shaded slot) g.fill(120, 170, 255, 26); g.rect(mxx - 16, cyy - 9, 16, 18); g.noStroke(); g.fill("#7f8aa3"); g.textSize(8); g.textAlign(RIGHT, TOP); g.text("magnet + gap", RX1 - 6, RT0 + 18); // "sound out" label at the open (left/front) side g.fill("#7f8aa3"); g.textSize(9); g.textAlign(LEFT, CENTER); g.text("sound out", RX0 + 8, RT0 + 18); // ---- RIGHT-BOT: scope frame + zero line ------------------------------------ panelFrame(g, RX0, RB0, RX1, RB1, "scope e(t) [cyan] vs output [amber]"); g.stroke("#2b3550"); g.strokeWeight(1); g.line(RX0 + 8, (RB0 + RB1) / 2 + 6, RX1 - 6, (RB0 + RB1) / 2 + 6); // visual mid g.noStroke(); g.fill("#9aa6c2"); g.textSize(9); g.textAlign(RIGHT, BOTTOM); g.text("t (window = 3 periods, scrolling)", RX1 - 4, RB1 - 3); } // small helper: panel frame + caption (baked use only) function panelFrame(g, x0, y0, x1, y1, label) { g.noFill(); g.stroke("#33405f"); g.strokeWeight(1.2); g.rect(x0, y0, x1 - x0, y1 - y0, 4); g.noStroke(); g.fill("#aebbd9"); g.textSize(10); g.textAlign(LEFT, TOP); g.text(label, x0 + 6, y0 + 3); } // ===================================================================== // draw // ===================================================================== function draw() { // -- read every control ONCE into named locals ----------------------------- const fHz = pow(10, sldF.value()); // Hz drive (signal) frequency const fs = pow(10, sldFs.value()); // Hz driver free-air resonance const Q = sldQ.value(); // total quality factor Qts const Edrv = sldE.value(); // V drive amplitude const r = fHz / fs; // frequency ratio f/fs // -- derived response quantities (read sliders ONCE; all else derived) ------ const M = magOut(r, Q, outMode); // selected-output magnitude |H| const dbv = dbOut(r, Q, outMode); // = 20*log10(M), clamped at the floor const th = thetaOut(r, Q, outMode); // selected-output phase vs drive (rad) const Dx = DofR(r, Q); // cone displacement magnitude (low-pass) const thx = thetaOut(r, Q, 1); // displacement phase (drives the animation) const Sa = r * r * Dx; // acceleration magnitude (high-pass; SPL) // -- advance model time only while running (frame-rate independent) --------- if (running) { const dt = min(deltaTime / 1000, 0.05); // clamp big frame gaps tsec += dt; if (tsec > 1e6) tsec = 0; // guard unbounded growth } image(scene, 0, 0); // blit baked panels/axes/grid/magnet drawResponse(fHz, fs, Q, dbv); drawDriver(fHz, Edrv, Dx, thx, Sa); drawScope(fHz, Edrv, M, th); drawHUD(fHz, fs, Q, Edrv, r, M, dbv, th, Dx, Sa); } // --------------------------------------------------------------------- // LEFT panel: response curve(s) + resonance marker + live operating dot // --------------------------------------------------------------------- function drawResponse(fHz, fs, Q, dbv) { // resonance marker: vertical line at fs const xr = xFromF(constrain(fs, FMIN, FMAX)); stroke(120, 170, 110, 150); strokeWeight(1.2); drawingContext.setLineDash([4, 4]); line(xr, LY0 + 18, xr, LY1 - 8); drawingContext.setLineDash([]); noStroke(); fill("#9fe6b0"); textSize(9); textAlign(CENTER, TOP); text("fs", xr, LY0 + 18); // faint comparison curve = the OTHER output (dashed) const other = outMode === 0 ? 1 : 0; stroke(150, 165, 200, 90); strokeWeight(1.2); noFill(); drawingContext.setLineDash([3, 4]); responseCurve(fs, Q, other); drawingContext.setLineDash([]); // selected output curve (solid, bright) stroke(outMode === 0 ? "#7fb0ff" : "#ffb347"); strokeWeight(2.5); noFill(); responseCurve(fs, Q, outMode); // live operating point at the drive frequency const px = xFromF(constrain(fHz, FMIN, FMAX)); const py = yFromDB(constrain(dbv, DBMIN, DBMAX)); stroke(150, 165, 200, 120); strokeWeight(1); line(px, LY1 - 8, px, py); // drop line to the f axis noStroke(); fill("#ffffff"); circle(px, py, 8); } // trace one response curve across the log-f axis (light: ~180 vertices) function responseCurve(fs, Q, mode) { const N = 180; beginShape(); for (let i = 0; i <= N; i++) { const fHz = pow(10, map(i, 0, N, l10(FMIN), l10(FMAX))); const db = dbOut(fHz / fs, Q, mode); vertex(xFromF(fHz), yFromDB(constrain(db, DBMIN, DBMAX))); } endShape(); } // --------------------------------------------------------------------- // RIGHT-TOP: animated moving-coil driver (cone + coil + force + wavefront) // --------------------------------------------------------------------- function drawDriver(fHz, Edrv, Dx, thx, Sa) { const cyy = (RT0 + RT1) / 2 + 4; // driver axis const coilX = RX1 - 86; // resting x of the voice coil (in the gap) const ph = TWO_PI * fHz * tsec; // current drive phase // signals: current ~ drive voltage (ignore coil L); motion = displacement ----- const iNow = sin(ph); // drive/current shape (unit) const lev = Edrv / EREF; // drive level vs reference const xUnit = Dx * sin(ph + thx); // cone displacement (signed) const aNow = -Sa * sin(ph + thx); // cone acceleration ~ -x const dx = constrain(DISP_PX * lev * xUnit, -DISP_MAX, DISP_MAX); // drawn travel // --- radiated wavefronts on the FRONT (left); brightness tracks |a| --------- const wf = constrain(120 * abs(aNow), 0, 150); noFill(); strokeWeight(2); for (let k = 0; k < 3; k++) { stroke(95, 200, 255, wf * (1 - k * 0.28)); const rr = 16 + k * 13 + 8 * (aNow >= 0 ? 1 : 0); arc(coilX - 44 + dx, cyy, rr * 2, rr * 1.7, radians(118), radians(242)); } // --- the cone: surround rim (front, left) tapering back to the coil ---------- const rimX = coilX - 60 + dx; // cone mouth (front) const rimH = 30; // half-height of the cone mouth stroke("#cfe0ff"); strokeWeight(3); noFill(); line(rimX, cyy - rimH, coilX + dx, cyy - 7); // upper cone wall line(rimX, cyy + rimH, coilX + dx, cyy + 7); // lower cone wall // surround (compliant roll at the rim) + spider (inner spring) - drawn as springs stroke("#6f7da0"); strokeWeight(1.5); drawSpring(rimX, cyy - rimH, rimX, cyy - rimH - 12, 3); drawSpring(rimX, cyy + rimH, rimX, cyy + rimH + 12, 3); // --- voice coil (former + windings) riding in the magnet gap ----------------- stroke("#e0a85a"); strokeWeight(2); noFill(); for (let k = -1; k <= 1; k++) ellipse(coilX + dx, cyy + k * 6, 14, 11); noStroke(); fill("#9aa6c2"); textSize(8); textAlign(CENTER, TOP); text("voice coil", coilX + dx, cyy + 14); // --- current arrow through the coil (length pulses with i(t)) ---------------- const aLen = constrain(22 * iNow, -20, 20); stroke("#5fd0ff"); strokeWeight(2); line(coilX + dx, cyy - 20, coilX + dx + aLen, cyy - 20); noStroke(); fill("#5fd0ff"); const dir = aLen >= 0 ? 1 : -1; triangle(coilX + dx + aLen, cyy - 20, coilX + dx + aLen - 6 * dir, cyy - 23, coilX + dx + aLen - 6 * dir, cyy - 17); textSize(8); textAlign(CENTER, BOTTOM); fill("#7fbfe0"); text("i(t)", coilX + dx, cyy - 24); // --- motor force arrow F = Bl*i on the cone (points the way the cone is pushed) const F = constrain(30 * iNow, -28, 28); stroke("#9fe6b0"); strokeWeight(3); line(coilX + dx, cyy + 22, coilX + dx - F, cyy + 22); noStroke(); fill("#9fe6b0"); const fdir = (-F) >= 0 ? 1 : -1; triangle(coilX + dx - F, cyy + 22, coilX + dx - F + 6 * fdir, cyy + 19, coilX + dx - F + 6 * fdir, cyy + 25); // rest-position guide for the cone mouth + excursion label -------------------- stroke(150, 165, 200, 90); strokeWeight(1); drawingContext.setLineDash([2, 3]); line(coilX - 60, cyy - rimH - 16, coilX - 60, cyy + rimH + 16); drawingContext.setLineDash([]); noStroke(); fill("#cfe0ff"); textSize(9); textAlign(CENTER, BOTTOM); text("cone", rimX, cyy - rimH - 14); // numeric peak-to-peak excursion (mm), with an Xmax warning ------------------- const xpp = 2 * XREF_MM * lev * Dx; fill(xpp > 2 * XMAX_MM ? "#ff6b6b" : "#9fe6b0"); textSize(9); textAlign(LEFT, TOP); text("x_pp = " + nf(xpp, 0, 2) + " mm" + (xpp > 2 * XMAX_MM ? " (> Xmax!)" : ""), RX0 + 8, RT0 + 32); } // a little zig-zag spring between two points (for surround + spider hints) function drawSpring(x0, y0, x1, y1, turns) { beginShape(); noFill(); for (let i = 0; i <= turns * 2; i++) { const t = i / (turns * 2); const sx = lerp(x0, x1, t) + ((i % 2) ? 4 : -4); const sy = lerp(y0, y1, t); vertex(sx, sy); } endShape(); } // --------------------------------------------------------------------- // RIGHT-BOT: scope - drive voltage e(t) [cyan] vs selected output [amber] // --------------------------------------------------------------------- function drawScope(fHz, Edrv, M, th) { const Twin = 3 / fHz; // 3 periods regardless of f const t0 = tsec - Twin; const N = 240; // light sample count const lev = Edrv / EREF; // output axis + amplitude depend on the mode (mm for excursion; norm for SPL) const outMax = (outMode === 0) ? PAXIS : XAXIS_MM; const outAmp = (outMode === 0) ? (lev * M) // normalized pressure (passband=1 at EREF) : (XREF_MM * lev * M); // excursion in mm // input drive voltage e(t) (cyan), fixed Volt axis stroke("#5fd0ff"); strokeWeight(2); noFill(); beginShape(); for (let i = 0; i <= N; i++) { const t = t0 + (i / N) * Twin; const e = Edrv * sin(TWO_PI * fHz * t); vertex(scopeT(t, t0, Twin), scopeY(RB0, RB1, e, EAXIS)); } endShape(); // selected output (amber): outAmp*sin(2pi f t + theta), fixed mode axis stroke("#ffb347"); strokeWeight(2); noFill(); beginShape(); for (let i = 0; i <= N; i++) { const t = t0 + (i / N) * Twin; const v = outAmp * sin(TWO_PI * fHz * t + th); vertex(scopeT(t, t0, Twin), scopeY(RB0, RB1, constrain(v, -outMax, outMax), outMax)); } endShape(); // dashed clip guides if the output would run off its axis (resonance / Xmax) if (outAmp > outMax) { stroke(255, 107, 107, 130); strokeWeight(1); drawingContext.setLineDash([4, 4]); line(RX0 + 8, scopeY(RB0, RB1, outMax, outMax), RX1 - 6, scopeY(RB0, RB1, outMax, outMax)); line(RX0 + 8, scopeY(RB0, RB1, -outMax, outMax), RX1 - 6, scopeY(RB0, RB1, -outMax, outMax)); drawingContext.setLineDash([]); } // "now" markers at the right edge (newest sample) const eEnd = Edrv * sin(TWO_PI * fHz * tsec); const vEnd = constrain(outAmp * sin(TWO_PI * fHz * tsec + th), -outMax, outMax); noStroke(); fill("#5fd0ff"); circle(RX1 - 6, scopeY(RB0, RB1, constrain(eEnd, -EAXIS, EAXIS), EAXIS), 6); fill("#ffb347"); circle(RX1 - 6, scopeY(RB0, RB1, vEnd, outMax), 6); // output-axis caption (units change with the mode) fill("#9aa6c2"); textSize(8); textAlign(LEFT, TOP); text(outMode === 0 ? "amber: p(t) ~ accel (norm)" : "amber: x(t) cone excursion (mm)", RX0 + 8, RB0 + 16); } // --------------------------------------------------------------------- // HUD watermark (drawn LAST): title | URL | hints | live equation footer // + the control labels/values + a live region flag. // --------------------------------------------------------------------- function drawHUD(fHz, fs, Q, Edrv, r, M, dbv, th, Dx, Sa) { // ---- control labels + live values (next to each slider) ---- noStroke(); textSize(11); fill("#cfe0ff"); textAlign(LEFT, CENTER); text("f", 14, 389); text("fs", 14, 427); text("Qts", 366, 389); text("E", 372, 427); fill("#9fe6b0"); textAlign(LEFT, CENTER); text(fmtHz(fHz), 258, 389); text(fmtHz(fs), 258, 427); text(nf(Q, 0, 2), 614, 389); text(nf(Edrv, 0, 2) + " V (" + nf(Edrv * Edrv / 8, 0, 1) + " W/8ohm)", 614, 427); // ---- HUD part 1: title (top-left) ---- fill("#ffffff"); textSize(13); textAlign(LEFT, TOP); text("Loudspeaker - the cone is a driven resonator, run in reverse", 12, 8); // ---- HUD part 2: URL (top-right) ---- fill("#8fa0c8"); textSize(10); textAlign(RIGHT, TOP); text(WIKI, 708, 10); // ---- HUD part 3: control hints ---- fill("#7f8aa3"); textSize(9); textAlign(LEFT, CENTER); text("drag sliders | Output toggles SPL/Excursion | Run/Pause animates | Reset", 120, 496); // ---- live region flag (where on the response we are) ---- textAlign(RIGHT, CENTER); textSize(10); let flag, col; if (abs(r - 1) < 0.12) { flag = "AT RESONANCE fs (peak ~Qts)"; col = "#ffd27f"; } else if (r < 1) { flag = (outMode === 0) ? "below fs: SPL rolls off (+12 dB/oct)" : "below fs: max cone excursion (flat)"; col = "#7fb0ff"; } else { flag = (outMode === 0) ? "above fs: flat PISTON BAND (usable)" : "above fs: excursion falls (mass-controlled)"; col = "#ffb347"; } fill(col); text(flag, 708, 496); // ---- approximate absolute SPL + excursion read-outs (illustrative) ---- const spl = SENS + 20 * l10(Edrv / EREF) + 20 * l10(Math.max(Sa, 1e-6)); fill("#9aa6c2"); textSize(9); textAlign(LEFT, TOP); text("approx SPL ~ " + nf(spl, 0, 1) + " dB @ 1 m", LX0 + 6, LY0 - 14); // ---- HUD part 4: live equation footer (above the control band) ---- const mEq = (outMode === 0) ? "S=r^2*D(r) (SPL ~ acceleration, high-pass)" : "X=D(r) (excursion ~ displacement, low-pass)"; noStroke(); fill("#0b0f18"); rect(0, FOOTY, width, 28); fill("#aab6d6"); textSize(11); textAlign(LEFT, CENTER); text("D(r)=1/sqrt((1-r^2)^2+(r/Qts)^2) " + mEq, 10, FOOTY + 14); textAlign(RIGHT, CENTER); fill("#cfe0ff"); text("r=" + nf(r, 0, 3) + " |H|=" + nf(dbv, 0, 1) + " dB theta=" + nf(degrees(th), 0, 0) + " deg", 712, FOOTY + 14); } // format a frequency as Hz or kHz (ASCII only) function fmtHz(fHz) { return (fHz >= 1000) ? (nf(fHz / 1000, 0, 2) + " kHz") : (nf(fHz, 0, 0) + " Hz"); } // ===================================================================== // controls // ===================================================================== function toggleRun() { running = !running; btnRun.html(running ? "Pause" : "Run"); if (running) loop(); else { noLoop(); redraw(); } } function toggleMode() { outMode = (outMode === 0) ? 1 : 0; btnMode.html(outMode === 0 ? "Output: SPL (~accel)" : "Output: Excursion (~displ)"); redraw(); } function resetAll() { sldF.value(DEF.f); sldFs.value(DEF.fs); sldQ.value(DEF.Q); sldE.value(DEF.E); outMode = DEF.mode; btnMode.html("Output: SPL (~accel)"); tsec = 0; running = false; btnRun.html("Run"); noLoop(); redraw(); } ``` <!-- REAL-GENERATIVE-MEDIA:START --> ## The model this MicroSim animates **The cone is a driven, damped harmonic oscillator with an electrodynamic motor.** Driving the coil with a tone `e(t) = E*sin(2*pi*f*t)`, the motor force is `F ~ Bl*i`, and in steady state the cone obeys ``` Mms*x'' + Rms*x' + (1/Cms)*x = F = Bl*i ``` Two derived constants summarise the cone resonance - the **free-air resonant frequency** and the **total quality factor** (which combines the mechanical and the motor/electrical damping): ``` fs = (1/2pi)*sqrt( 1/(Mms*Cms) ) free-air cone resonance Qms = (1/Rms)*sqrt(Mms/Cms) mechanical Q Qes = (Re/(Bl*Bl))*sqrt(Mms/Cms) electrical Q (set by the motor Bl) Qts = Qms*Qes/(Qms + Qes) total Q at resonance ``` `Re` is the voice-coil DC resistance and `Qts` is the famous **Thiele-Small** total-Q parameter. A strong motor (large `Bl`) makes `Qes` small and so *tightens* `Qts` - the motor brakes the cone electrically through its own back-EMF. Writing the frequency ratio `r = f/fs`, the steady-state response splits into the two physical quantities the toggle selects. Define the shared resonance denominator ``` D(r) = 1 / sqrt( (1 - r^2)^2 + (r/Qts)^2 ) ``` then ``` Excursion (cone displacement x): X(r) = D(r) (low-pass; ref: DC value = 0 dB) Radiated SPL (cone acceleration a): S(r) = r^2 * D(r) (high-pass; ref: passband = 0 dB) ``` The far-field, on-axis sound pressure of a piston radiator is proportional to its **volume acceleration**, so the SPL response carries the extra factor `r^2`. That factor is everything: - **Excursion `X(r)`** is a **low-pass**: well below `fs` it is **flat and maximal** (the suspension compliance controls the motion - this is why a woofer's cone visibly heaves at low frequencies and why `Xmax` is the limit there), it **peaks ~ Qts** at `r = 1`, then rolls off above `fs` as the mass takes over. - **SPL `S(r)`** is a **2nd-order high-pass**: it rises at **+12 dB/octave** below `fs`, **peaks** at `r = 1` if `Qts > 0.707`, and is **flat** above `fs` - the **piston band**, the loudspeaker's usable range. `Qts = 1/sqrt(2) ~ 0.707` is the **maximally flat (Butterworth)** alignment with no peak. The phases mirror the microphone's exactly, but reflected: cone **excursion lags** the drive (`0 -> -90 deg -> -180 deg` as `f` sweeps up), while radiated **SPL leads** it (`+180 -> +90 -> 0 deg`). At resonance the excursion lags by `90 deg` and the radiated pressure leads by `90 deg`. The left panel plots `20*log10` of the selected response against a logarithmic frequency axis (the standard Bode magnitude view); the chosen output is solid and the other is shown faintly for comparison, with a live operating dot at the drive frequency `f`. The upper-right panel is a **cross-section** of the moving-coil driver - magnet and pole gap, the voice coil carrying the current, the spider and surround, and the cone that visibly moves; an arrow shows the motor force `F = Bl*i` and a pulsing wavefront shows the radiated sound whose strength tracks the cone **acceleration**. The lower-right panel is a **scope** of the drive [[Voltage|voltage]] `e(t)` against the selected output, so the resonant amplification and the phase are read directly off the waveforms. The **"aha"**: a loudspeaker is the microphone run backwards. The same one-diaphragm resonance that gave the microphone its response gives the loudspeaker its response - but because sound radiates from **acceleration**, the curve flips into a high-pass whose flat **piston band** above `fs` is the speaker's working range, while the cone's largest **motion** lives down below `fs`, exactly where it makes the *least* sound. ## Links (Wikipedia order) <!-- injected from _registry/childlinks/Loudspeaker.json (2026-07-30T02:09:12Z) --> `1939_New_York_World's_Fair` · `Ableton_Live` · `Absorption_(acoustics)` · `Acoustic_Research` · `Acoustic_impedance` · `Acoustic_lobing` · `Acoustic_resonance` · `Acoustic_suspension` · `Acoustic_transmission_line` · `Air_Motion_Transformer` · `Alexander_Graham_Bell` · `Aliasing` · `Alnico` · `Altec_Lansing` · `Altec_Lansing_Duplex` · `Amplifier` · `Analog_recording` · `Anechoic_chamber` · `Audio_(magazine)` · `Audio_Engineering_Society` · `Audio_crossover` · `Audio_engineer` · `Audio_equalization` · `Audio_power` · `Audio_power_amplifier` · `Audio_signal` · `Audiophile` · `Background_music` · `Bakelite` · `Bamboo` · `Bandwidth_extension` · `Bass_amplifier` · `Bass_reflex` · `Bell_Labs` · `Bessel_function` · `Bi-wiring` · `Binaural_recording` · `Binding_post` · `Bit` · `Cabasse_(company)` · `Capacitance` · `Capacitor` · `Carbon_nanotube` · `Cassette_deck` · `Charles_Algernon_Parsons` · `Chiptune` · `Circuit_bending` · `Compact_disc` · `Comparison_of_analog_and_digital_recording` · `Compression_driver` · `Computer-aided_design` · `Computer_speakers` · `Concert` · [[Copper]] · `Damping_factor` · `Degaussing` · `Diaphragm_(acoustics)` · `Diffraction` · `Diffusion_(acoustics)` · `Digital_Audio_Tape` · `Digital_audio` · `Digital_audio_workstation` · `Digital_recording` · [[Digital_signal_processing]] · `Digital_speaker` · `Dipole_speaker` · `Directional_sound` · `Douglas_Shearer` · `Drum_machine` · `Dust_cap` · `Dynamic_range_compression` · `Edgar_Villchur` · `Edward_W._Kellogg` · `Effects_unit` · `Electric_field` · `Electric_generator` · `Electrical_characteristics_of_dynamic_loudspeakers` · `Electrical_impedance` · `Electrical_polarity` · `Electrical_reactance` · `Electrodynamic_speaker_driver` · `Electromagnet` · `Electromagnetic_coil` · `Electronic_music` · `Electronic_musical_instrument` · [[Electronics]] · `Electrostatic_loudspeaker` · `Experimental_musical_instrument` · `Faraday's_law_of_induction` · `FeONIC` · `Ferrofluid` · `Foam` · [[Frequency_response]] · `Full-range_speaker` · `GarageBand` · `Genelec` · `Geometric_terms_of_location` · `Glass_wool` · `Goji_Electronics` · `Guitar_amplifier` · `Guitar_speaker` · `Guitar_tech` · `Hard_disk_recorder` · `Harmonic_oscillator` · `Headphones` · [[Helium]] · `Hemp` · `Henry_Kloss` · `High-end_audio` · `High_fidelity` · `Home_audio` · `Home_cinema` · `Horn_(acoustic)` · `Horn_loudspeaker` · `Hugh_Chisholm` · `IRCAM` · `Impedance_matching` · `Inductance` · `Infrasound` · `Ingeniøren` · `Insertion_loss` · `Instrument_amplifier` · `Isobaric_loudspeaker` · `James_Bullough_Lansing` · `Jay_Pritzker_Pavilion` · `Johann_Philipp_Reis` · `John_Kenneth_Hilliard` · `John_M._Eargle` · `KEF` · `KLH_(company)` · `Kevlar` · `Keyboard_amplifier` · `Kyocera` · `LARES` · `Lejaren_Hiller` · `Lincoln_Walsh` · `Linear_motor` · `List_of_loudspeaker_manufacturers` · `Logic_Pro` · `Loudness` · `Loudspeaker_(disambiguation)` · `Loudspeaker_acoustics` · `Loudspeaker_enclosure` · `Loudspeaker_measurement` · `MIDI` · `MIDI_controller` · `MP3` · `Machine_press` · `Magnavox` · `Magnet` · `Magnetic_field` · `Magnetic_tape` · `Magnetostatic_loudspeaker` · `Magnetostriction` · `Mass` · [[Materials_science]] · `Max_Mathews` · `Media_control_symbols` · `Megaphone` · `Microphone` · `Microphone_preamplifier` · `Mid-range_speaker` · `MiniDisc` · `Mixing_console` · `Mixing_engineer` · `Moving_iron_speaker` · `Multitrack_recording` · `Music_sequencer` · `Music_store` · `Music_technology` · `Music_technology_(electric)` · `Music_technology_(electronic_and_digital)` · `Music_technology_(mechanical)` · `Music_workstation` · `Musical_Electronics_Library` · `NOx` · `Napa,_California` · [[Neodymium]] · `New_Interfaces_for_Musical_Expression` · `OLED` · `Oliver_Lodge` · `Opus_(audio_format)` · `Oskar_Heil` · `Outboard_gear` · `Ozone` · `Parabolic_loudspeaker` · `Pascal_(unit)` · `Passband` · `Pathé` · `Paul_Wilbur_Klipsch` · `Personal_computer` · `Peter_L._Jensen` · `Phase_plug` · `Phonograph` · `Phonograph_record` · `Piezoelectric_speaker` · `Pioneer_Corporation` · `Planephones` · [[Plasma_(physics)]] · `Plasma_speaker` · `Plasmatronics` · `Player_piano` · `Point_source` · `Popular_Electronics` · `Portable_audio_player` · `Power_supply` · `Powered_speakers` · `Professional_Lighting_and_Sound_Association` · `Professional_audio_store` · `Public_address_system` · `Radio_receiver` · `Rare-earth_magnet` · `Re-recording_mixer` · `Record_producer` · `Reel-to-reel_audio_tape_recording` · `Resistor` · `Resonance` · `Reverb_effect` · `Robert_Moog` · `Roll-off` · `Room_acoustics` · `Rotary_woofer` · `Rudy_Bozak` · `Rule_of_thumb` · `STEIM` · `Sampler_(musical_instrument)` · `Scorewriter` · [[Silver]] · `Society_of_Motion_Picture_and_Television_Engineers` · `Software` · `Software_effect_processor` · `Software_synthesizer` · `Solenoid` · `Sonar` · `Sound` · `Sound_baffle` · `Sound_follower` · `Sound_from_ultrasound` · `Sound_module` · `Sound_power` · `Sound_pressure` · `Sound_recording_and_reproduction` · `Sound_reinforcement_system` · `Soundbar` · `Speaker_stands` · `Speaker_terminal` · `Speaker_wire` · `Speakerphone` · `Speech` · `Standing_wave` · `Stereophile` · `Studio_monitor` · `Subwoofer` · `Super_tweeter` · `Surround_sound` · [[Synergy]] · `Synthesizer` · `THX` · `Tannoy` · `Tape_op` · `Tape_recorder` · `Telephone` · `Television` · `Theremin` · `Thermophone` · `Thiele/Small_parameters` · `Thomas_Edison` · `Timbre` · `Timeline_of_audio_formats` · `Trade-off` · [[Transducer]] · `Transmission_line` · `Transmission_line_loudspeaker` · `Tweeter` · `Vehicle_audio` · `Victor_Talking_Machine_Company` · `Voice_coil` · `Watch` · `Waveguide` · [[Wayback_Machine]] · `Wikimedia_Commons` · `Wireless_speaker` · `Woofer` ## From the Real GENERATIVE library ![Loudspeaker](https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Electrodynamic-loudspeaker.png/170px-Electrodynamic-loudspeaker.png) *Loudspeaker — placed from the Real G.E.N.E.R.A.T.I.V.E. course library (Telecommunications room). Source: Wikimedia Commons (via Wikipedia article media). [Details & license](https://commons.wikimedia.org/wiki/File:Electrodynamic-loudspeaker.png).* ![Animated: Loudspeaker](https://upload.wikimedia.org/wikipedia/commons/8/85/Es_spk.gif) *Animated: Loudspeaker — placed from the Real G.E.N.E.R.A.T.I.V.E. course library (Telecommunications room). Source: Wikimedia Commons (via Wikipedia article media). [Details & license](https://commons.wikimedia.org/wiki/File:Es_spk.gif).* > A loudspeaker (commonly referred to as a speaker or, more fully, a speaker system) is a combination of one or more speaker drivers, an enclosure, and electrical connections (possibly including a crossover network). The speaker driver is an electroacoustic transducer[1]: 597 that converts an electrical audio signal into a corresponding sound.[2] ([Wikipedia](https://en.wikipedia.org/wiki/Loudspeaker)) <!-- REAL-GENERATIVE-MEDIA:END --> <!-- SIGN-SYSTEMS:START --> **Semiotic universals** (the notations and alphabet letters this article speaks — each opens its canonical card): frequency · resonance · phase · damping · potential. Index: the glyph gallery · SEMIOTICS PORTAL. <!-- SIGN-SYSTEMS:END --> ## Media (PD/CC) <!-- MEDIA-DEPLOY:Loudspeaker/Es_spk.gif --> !Gif Library/Speaker (audio equipment)/Es spk.gif *Es_spk.gif · Original uploader was Rohitbd at en.wikipedia · CC BY-SA 3.0 · [source](https://commons.wikimedia.org/wiki/File:Es_spk.gif)* <!-- /MEDIA-DEPLOY --> ## Overview A **loudspeaker** is a **[[Transducer|transducer]]** that converts an electrical [[Signal|signal]] back into **sound** - a travelling pressure variation in air. It is the canonical *output* transducer of the audio chain and the exact dual of the Microphone: where the microphone's diaphragm *hears*, the loudspeaker's cone *speaks*. The overwhelmingly common type is the **electrodynamic (moving-coil) driver**, invented in its modern form by Rice and Kellogg (1925), which works by the same electromagnetic [[Coupling|coupling]] as a dynamic microphone, run in reverse. The mechanism has two halves wired together: - A **motor**. A light cylindrical **voice coil** of wire (total length `L` in the gap) sits in the strong radial field `B` of a permanent magnet. When the amplifier pushes a current `i` through the coil, the coil feels a [[Force|force]] given by the **Lorentz / motor law**: `F = B*L*i`. The product `B*L` - the **force factor** (or motor constant) `Bl`, in tesla-metres or newtons per amp - is the single most important motor parameter. A moving coil also generates a **back-EMF** `B*L*v` proportional to its [[Velocity|velocity]] `v`, which feeds back into the circuit and (through the amplifier's low output impedance) electrically *damps* the cone. - A **radiator**. The coil is glued to a stiff **cone** (the diaphragm) held centred by two springs - a **spider** at the coil and a **surround** at the rim. Cone, coil and trapped air together have a moving **mass** `Mms`, the suspension has a mechanical **compliance** `Cms` (stiffness `k = 1/Cms`), and the losses give a mechanical **resistance** `Rms`. So the cone is a **mass on a spring with [[Damping|damping]]** - a driven, damped harmonic oscillator - pushed by the motor force `F = B*L*i`. This is the same oscillator the microphone MicroSim animates, but driven from the *electrical* side and read out as *radiated sound*. That single change - from reading the motion to radiating it - flips the frequency response from a low-pass into a **high-pass**, and that flip is what this MicroSim makes visible. ## Parameter table (each control -> real symbol + range) | Control | Symbol | Physical meaning | Range | Default | |---|---|---|---|---| | Signal frequency | `f` | frequency of the drive tone into the coil (log-swept slider) | 20 - 20000 Hz | 800 Hz | | Driver resonance | `fs` | free-air cone resonance `fs = (1/2pi)*sqrt(1/(Mms*Cms))` (log slider) | 20 - 500 Hz | 60 Hz | | Total Q | `Qts` | Thiele-Small total quality factor `Qts = Qms*Qes/(Qms+Qes)`; `zeta = 1/(2Qts)` | 0.2 - 2.0 | 0.707 | | Drive voltage | `E` | drive amplitude (`2.83 V = 1 W into 8 ohm`, the standard reference) | 1 - 20 V | 2.83 V | | Output | mode | **SPL** (V proportional to acceleration, high-pass) vs **Excursion** (proportional to displacement, low-pass) | toggle | SPL | Derived and shown live in the HUD: frequency ratio `r = f/fs`, response magnitude `|H| = 20*log10(M)` in dB, output phase `theta` in degrees, an approximate on-axis SPL in dB (referenced to a nominal sensitivity of `~88 dB @ 1 m, 2.83 V` in the passband), and the cone peak excursion in millimetres (illustrative scaling). The standard reference uses `2.83 Vrms = 1 W into 8 ohm`. ## Learning objective Explain why a moving-coil loudspeaker's on-axis frequency response is a **2nd-order high-pass** set by the cone's mechanical resonance `fs` and total damping `Qts` - radiated **SPL follows cone acceleration** (flat in the piston band above `fs`, +12 dB/octave roll-off below `fs`, a resonance peak when `Qts > 0.707`) while cone **excursion follows displacement** (largest and constant well below `fs`) - and predict how moving `fs`, changing `Qts`, and raising the drive level reshape the **passband, resonance peak, low-frequency roll-off, phase, and cone excursion**. Recognise the loudspeaker as the radiating dual of the microphone: the same diaphragm resonance, read out as acceleration instead of displacement. ## A-V pattern A composition built on a **driven, damped harmonic oscillator with an electrodynamic motor** (`F = Bl*i`): an **H/chart** Bode magnitude plot of the selected frequency response (left, log-frequency, with a live operating point and a faint comparison curve for the other output), a **B/G** cross-section schematic of the moving-coil driver - magnet, pole gap, voice coil, spider, surround and an animated cone - with a motor-force arrow and a radiated-wavefront cue tied to the cone acceleration (upper right), and an **H** signals-over-time scope of drive voltage vs the selected output exposing gain and phase (lower right). ## Sources - Loudspeaker - Wikipedia: https://en.wikipedia.org/wiki/Loudspeaker - Moving-coil / dynamic driver; motor force `F = BLi` and back-EMF `BLv`: https://en.wikipedia.org/wiki/Loudspeaker#Moving-coil - Voice coil (force factor Bl, the motor constant): https://en.wikipedia.org/wiki/Voice_coil - Thiele/Small parameters (fs, Qms, Qes, Qts, Cms, Mms, Re): https://en.wikipedia.org/wiki/Thiele/Small_parameters - Driven, damped harmonic oscillator (resonance magnitude D(r), Q, phase): https://en.wikipedia.org/wiki/Harmonic_oscillator#Driven_harmonic_oscillators - Sound radiation proportional to volume acceleration (piston in the piston band): https://en.wikipedia.org/wiki/Loudspeaker#Driver_design <!-- CRAFT-LINK:START g12 --> *Built to the [[WT!P5_js_Microsim_Master_Class|p5.js Master Class]].* <!-- CRAFT-LINK:END --> <!-- ACOUSIM:BEGIN g22 — Acoustics portal microsim (framework build, specs/acoustics/sims/Loudspeaker.json); do not hand-edit inside --> **Microsim — three.js (Wikitube framework):** *Loudspeaker* <div class="wt-sim" data-src="https://wikitube-3d-microsims.netlify.app/acoustics/Loudspeaker.html" data-title="Loudspeaker"></div> *Built from `MICROSIM_GUIDE/specs/acoustics/sims/Loudspeaker.json`; part of the [[PORTAL_Acoustics|Acoustics portal]] spine (section sims and See-also variants).* <!-- ACOUSIM:END --> ## Wikipedia : Wikitube **Strict pair:** [Wikipedia](https://en.wikipedia.org/wiki/Loudspeaker) : [Wikitube](https://en.wikitube.io/wiki/Loudspeaker) ## Previous hub tags Tree parent: [[Control_theory]]. Legacy hubs: none. --- *Sources: 1 legacy note. Minted wave 1, 2026-07-30 (v1.6 order).*