# Lab Report — 5thOrgan: Polyphonic Pipe Organ Additive Synthesis in the Browser ### Design, Mathematics, and As-Built Verification of `src/sources/5thOrgan.html` **Filed:** 2026-05-24 · **Ship commit:** `030c446` (2026-05-24 19:20) · **Path today:** `src/sources/5thOrgan.html`, moved from the repo root by `5e48dd7` **Verified:** 2026-08-12 (§DOC-02ad) — every claim below re-measured against the live file. Original 573 lines. **Status:** ✅ The synthesizer shipped and works. ⛔ **The game feature it was built to become did not** — `play.html` contains zero Web Audio and is silent (Finding 1). --- ## Abstract `src/sources/5thOrgan.html` is a self-contained, single-file polyphonic pipe organ synthesizer running in the browser on the Web Audio API. It uses additive synthesis to reconstruct organ timbre from six harmonic partials per note over a 12-voice pool — a **capacity** of 72 simultaneous sine oscillators. A lookahead sequencer plays the opening motif of Beethoven's Symphony No. 5, Op. 67 as a two-voice canon, the second voice entering 14 sixteenth notes after the first. Ten synthesis parameters are exposed as live controls. This report documents the mathematical foundations, records the as-built implementation against the specification, and reports seven measured findings. The 2026-08-12 verification pass found the transcribable material **exact** — the motif array, the envelope algebra, the voice-stealing expression, the architecture diagram and all ten rows of the parameter table are faithful to the file — and every error in composed passages: a timbre claim off by one power of *n*, a collision proof that proves the wrong proposition, a "Results" section listing outcomes the same document later admits were never heard, and a numerical accuracy bound three orders of magnitude optimistic. --- ## I. Purpose — What This Is For, and What It Would Add to the Game This section is the report's reason to exist and was the thinnest part of the original. It is restated here from the design's own source. **The inspiration.** The concept was locked before a line of HTML existed, in a sibling report — `lab-report-ponies-unicorns-aspirations-future-ideas.md` §VI, *"Polyphonic Pipe Organ Synthesizer — Background Music via Sine Waves."* Its opening sentence is the whole brief: > *"Pure mathematics: sine waves mixed in the proportions that a real pipe organ produces, driven by a sequencer that reads a note file and plays it like a player piano — **in the background, as the game is played**."* **Why an organ.** A pipe organ is the one instrument whose acoustic physics reduce cleanly to the harmonic series. Each pipe is a resonant tube sustaining a fundamental plus integer-multiple overtones; timbre — the "color" of a stop — comes from the *relative amplitudes* of those overtones, not from waveform complexity or non-linear excitation. So the synthesis problem reduces to: sum *N* sines at the right frequencies and amplitudes. By Fourier's theorem, ``` x(t) = Σ_{n=1}^{N} A_n · sin(2π · n · f₀ · t + φ_n) ``` and for an organ the phases `φ_n` are approximately zero — all partials speak together on key-down — leaving only the amplitudes to specify. **Why that matters to `play.html` specifically.** The game is one static HTML file with no build step and no server at play time (prompt.md §0). Every conventional soundtrack option breaks that constraint: `.mp3` assets need files to fetch, a library needs a CDN, a tracker needs a decoder. An additive organ needs **none of it** — the score is a small array of integers and the instrument is arithmetic. It is the only music architecture that fits the game's own single-file invariant, which is why this shape was chosen. **What the feature adds to play.** Three things the game currently has no mechanism for: 1. **Continuous tonal presence under a turn-based loop.** codexofconquest is read-and-click: node text, a Ceremonia roll, a battle round. Nothing sustains between clicks, so pacing lives entirely in the prose. A held organ chord is the cheapest possible continuity — it makes a pause feel like a held breath instead of a stall. 2. **State expressed as timbre rather than as another text strip.** The parameters are already the right dials. Void pressure could open the harmonic falloff (bright, buzzy, wrong); act progression could add upper stops; the doom clock's approach to Day 49 could drop the filter cutoff until the world sounds muffled. That is diegetic feedback costing the player no screen space — and screen space is the scarce resource in a single-column story pane. 3. **Place with an audible identity.** 416 nodes across 111 terrains are distinguished by prose alone today. A short registration per region is a second axis of location, essentially free: one motif array per terrain family. **Why 12 voices,** from the sibling report's §H budget: 1 for a melody, 2 for melody-plus-bass, 3 for a Bach invention, 4 for a chorale, 8–12 for full organ texture with overlapping phrase releases. Twelve covers all practical organ repertoire. *The shipped demo is a two-voice canon and uses two* — the pool is provisioned for the intended repertoire, not for the demonstration standing in for it (Finding 4). > The file that was meant to give the game a voice currently lives in `src/sources/`, which is where this repo keeps its authoring tools — the directory you go to in order to be quiet. --- ## II. Verification Method (2026-08-12) 1. Locate the deliverable. The report names `5thOrgan.html`; `ls` at the root fails; the file is at `src/sources/5thOrgan.html`, relocated by `5e48dd7` (*"reorganize repo root"*). Four commits touch it, none since the reorg. 2. Batch-census every identifier the report names — 55 names in one `grep -c` loop — before reading a line of the source. 3. Re-derive every stated number rather than re-reading it: harmonic amplitudes, MIDI frequencies, note durations, loop arithmetic, filter response, Taylor-series error. 4. Simulate the two behavioural claims that cannot be read off a line — canon collision across the full selectable offset range, and the effect of a mid-play tempo change. 5. Cross-check against the sibling report that specified the design. This located the root cause of Finding 2, which no single-document pass could have found. 6. Measure the game side: does `play.html` consume any of it. **Census result: 42 of 55 named identifiers resolve (76 %).** Every one that does not is framed by the report itself as future work or reference material, with two exceptions — `ROUND_OFFSET` and `LOOKAHEAD`/`SCHEDULE_INTERVAL`, named as if they were constants in the file. They are inline literals; the *values* are correct. --- ## III. As-Built Inventory Every anchor below resolves literally and line-exact in the live file. **Constants and registration.** `src/sources/5thOrgan.html:const N_HARM@142` = 6 and `src/sources/5thOrgan.html:const MAX_VOX@143` = 12, both as specified. The motif at `src/sources/5thOrgan.html:const MOTIF = [@147` is byte-exact to the listing the report reproduces in its future-directions section, and `src/sources/5thOrgan.html:const MOTIF_LEN@157` = 28 sixteenths. Drawbar defaults `src/sources/5thOrgan.html:drawbars: [1.000@161` are 1/*n* across all six, and `src/sources/5thOrgan.html:falloffDB: 6,@162` is 6 dB/oct — both exactly as documented, and together the cause of Finding 2. **Tuning and amplitude.** `src/sources/5thOrgan.html:function midiHz@185` and `src/sources/5thOrgan.html:function harmAmp@188` implement the two published formulas without deviation; the falloff term is visible at `src/sources/5thOrgan.html:P.falloffDB * Math.log2(h)@189`. **Envelope and voice pool.** Attack peaks at `src/sources/5thOrgan.html:vel * 0.065, when + atkS@211`, matching the specified `V_peak = velocity × 0.065`. Release begins at `src/sources/5thOrgan.html:Math.max(when, atkEnd)@246` — the specified `max(t_off, t_on + T_attack)` — and oscillators halt 20 ms later at `src/sources/5thOrgan.html:osc.stop(stopAt + 0.02)@250`. Voice stealing is oldest-first at `src/sources/5thOrgan.html:voices.reduce((a, b)@203`, the expression the original quotes verbatim, and the reuse guard at `src/sources/5thOrgan.html:slot.gen !== myGen@253` behaves exactly as described. `src/sources/5thOrgan.html:function noteOff@263` releases the **newest** matching voice (`src/sources/5thOrgan.html:v.startTime > best.startTime@266`), which matters in §VIII.C. **Filter.** `src/sources/5thOrgan.html:flt.type = 'lowpass'@214` is the native `BiquadFilterNode`, and both live controls update sounding voices as claimed: `src/sources/5thOrgan.html:vv.filter.frequency.value=v@414` and `src/sources/5thOrgan.html:vv.filter.Q.value=v@415`. Drawbars likewise, at `src/sources/5thOrgan.html:harmAmp(hi+1)@384`. **Sequencer.** `src/sources/5thOrgan.html:function buildSeq@277` computes `src/sources/5thOrgan.html:const s16 = (60 / bpm) / 4@278` and is called at exactly one site, `src/sources/5thOrgan.html:seqEvents = buildSeq(@323`. The lookahead horizon is `src/sources/5thOrgan.html:const horizon = now + 0.15@300` and the tick is `src/sources/5thOrgan.html:setTimeout(sched, 50)@317` — the documented 150 ms / 50 ms, shipped as inline literals rather than named constants. Two behaviours the original does not mention: note-offs fire at 92 % of nominal duration (`src/sources/5thOrgan.html:dur * s16 * 0.92@285`, a deliberate articulation gap), and a late event is clamped forward rather than dropped (`src/sources/5thOrgan.html:Math.max(t, now + 0.001)@306`). The loop advance at `src/sources/5thOrgan.html:seqStart += (gRO()@314` is Finding 3. Autoplay policy is handled before scheduling at `src/sources/5thOrgan.html:actx.state === 'suspended'@322`, as claimed. **Output graph and scope.** `src/sources/5thOrgan.html:masterGain.connect(analyser)@180` then `src/sources/5thOrgan.html:analyser.connect(actx.destination)@181` — the published diagram, exactly. `src/sources/5thOrgan.html:analyser.fftSize@179` = 2048 and `src/sources/5thOrgan.html:getFloatTimeDomainData(scBuf)@429` fills a `Float32Array` of that length, so *"length = fftSize"* holds; the trace margin is `src/sources/5thOrgan.html:H * 0.44@440`, the specified 0.44. **Controls.** Vertical drawbars use `src/sources/5thOrgan.html:writing-mode:vertical-lr@40` — and, undocumented in the original's own limitation note, also ship the legacy fallback `src/sources/5thOrgan.html:appearance:slider-vertical@41`. The round-offset input is `src/sources/5thOrgan.html:id="ro" value="14"@67`, clamped by `src/sources/5thOrgan.html:function gRO()@340`. **Scale.** 448 lines total, 307 inside the `