A generative audiovisual instrument disguised as a recovered-transmission terminal.
Procedural Web Audio synthesis · real-time Canvas rendering · diegetic interface fiction — in a single static page, no build step, no dependencies to install.
- Overview
- Why this project exists
- Live demo & screenshots
- The experience in 60 seconds
- Feature matrix
- Quick start
- Architecture at a glance
- The signal chain
- Parameter model
- Controls & shortcuts
- Project structure
- Tech stack & rationale
- Documentation
- Quality: testing, performance, accessibility
- Deployment
- Roadmap
- Contributing
- Design language
- Credits & license
Interference Archive is an interactive browser artwork that treats the web page as a speculative instrument. It presents itself as a late-1990s archival console used to recover unstable transmissions from abandoned research sites. Every control simultaneously reshapes three coupled systems:
- Sound — an evolving drone generated live with the Web Audio API.
- Image — a real-time oscilloscope, spectrogram, and particle field drawn on a Canvas.
- Fiction — the implied state of the archive, expressed through telemetry, logs, classifications, and generated case files.
It is deliberately not a music player or a game. There are no audio files, no levels, and no win state. The signal is synthesized from first principles in the browser, and the "horror" is systemic: the machine looks orderly while its own readouts imply that something it is measuring cannot be contained by the interface built to observe it.
All locations, events, transmissions, and reports are fictional. All audiovisual output is generated procedurally at runtime.
This repository is a portfolio piece for creative-technology work, and it is intentionally scoped to demonstrate breadth across disciplines that rarely appear together in one codebase:
| Discipline | What it demonstrates here |
|---|---|
| Creative coding | A hand-written requestAnimationFrame render loop driving procedural particles, oscilloscope traces, spectrogram bars, grid, vignette, and scanlines on a raw Canvas 2D context. |
| DSP / browser audio | A fully wired Web Audio graph — oscillators, a looped noise buffer, a biquad filter, a delay/feedback network, an analyser, and a master bus — modulated in real time. |
| Interaction design | DAW-style parameter sliders, node selection, mute/power state machines, keyboard shortcuts, and live telemetry that reads back into the visuals. |
| Systems & narrative | Site-specific fiction, anomaly logs, randomized incident reports, and generated, downloadable "case files." |
| Diegetic interface design | The UI is the story: status indicators, typography, and failure states carry the worldbuilding, not a separate narrative layer. |
| Zero-dependency engineering | Ships as three static files. No bundler, no framework, no server, no node_modules. Loads instantly and is trivial to host. |
If you are evaluating this repo as a recruiter or collaborator, the fastest tour is: read this README, skim docs/ARCHITECTURE.md, then open assets/js/app.js alongside the API reference.
- Live: https://zazieproductions.github.io/interference-archive/
- The console runs entirely client-side. Audio only begins after a user gesture (a browser autoplay requirement), so the experience opens on a "CLICK TO ACCESS" gate.
- Initialize the archive to unlock the audio engine (satisfies the browser autoplay gesture requirement).
- Move between recovered nodes — each has a distinct fictional identity, accent color, base frequency, and log vocabulary.
- Adjust transmission parameters to reshape the coupled audio + visual system in real time.
- Randomize the console to jump to an unstable new state.
- Capture a transmission to mint a unique archive ID, freeze the current frame, and auto-write a classification and incident report.
- Download the case file as a paired
.txtreport and.pngsnapshot.
- Procedural Web Audio signal chain (oscillator bank + noise + filter + delay/feedback)
- Dynamically generated white-noise buffer (
AudioBuffer) - Parameter-driven oscillator, filter, delay, and gain behavior with smoothed transitions
- Real-time Canvas 2D animation via
requestAnimationFrame AnalyserNode-driven oscilloscope waveform and spectrogram bars- Particle field whose motion responds to live audio energy
- Five site "personalities" with per-site synthesis and narrative parameters
- Randomized narrative event generation (logs, anomalies, incident reports)
- Canvas snapshot export via
toDataURL() - Client-side text-file generation via
Blob+ object URLs - Keyboard shortcuts for capture and randomization
- CRT/terminal aesthetic (scanlines, glow, glitch) in pure CSS
- Responsive single-page layout, zero external state or backend
The project is fully static. The fastest path:
git clone https://github.com/zazieproductions/interference-archive.git
cd interference-archiveThen either open index.html directly, or (recommended, for consistent browser behavior) serve it locally:
# Python 3
python3 -m http.server 8000
# …or Node
npx serve .Visit http://localhost:8000 and click to initialize. Audio starts only after that first gesture — this is intentional and matches how modern browsers gate autoplaying sound.
No installation, build, or environment variables are required.
package.jsoncarries metadata and keywords only; there is no dependency graph to resolve.
See docs/SETUP.md for browser support notes and troubleshooting.
Three files, three responsibilities, one coupled system:
┌──────────────────────────────────────────┐
User gesture ─────────▶ index.html (shell) │
(click / key) │ markup · Tailwind CDN · VT323 font │
└───────────────┬──────────────────────────┘
│ loads
┌───────────────▼──────────────────────────┐
│ assets/js/app.js │
│ ┌───────────┐ ┌──────────┐ ┌────────┐ │
│ │ Audio │◀▶│ State │◀▶│ Render │ │
│ │ engine │ │ (params, │ │ loop │ │
│ │(Web Audio)│ │ site, │ │(Canvas │ │
│ └─────┬─────┘ │ power) │ │ 2D) │ │
│ │ └────┬─────┘ └───┬────┘ │
│ AnalyserNode ───────┴────────────┘ │
└────────────────────────┬──────────────────┘
│ styles
┌─────────────────────────▼─────────────────┐
│ assets/css/styles.css │
│ CRT scanlines · neon glow · glitch · font │
└────────────────────────────────────────────┘
The AnalyserNode is the bridge: it taps the live audio bus and feeds time-domain and frequency-domain data back into the render loop, so the picture is literally a view of the sound. Full detail — module boundaries, data flow, and lifecycle — lives in docs/ARCHITECTURE.md.
The audio graph is built once in initAudio() and modulated continuously thereafter:
osc0 (sine 85Hz) ─▶ gain ┐
osc1 (sine 128Hz)─▶ gain ┤
osc2 (sine 210Hz)─▶ gain ├─▶ droneMix ┐
osc3 (saw 340Hz) ─▶ gain ┘ │
├─▶ biquad LOWPASS ─▶ delay ─▶ analyser ─▶ master ─▶ 🔈
noiseBuffer ─▶ noiseSource ─▶ noiseGain ┘ ▲ │
│ └─▶ feedbackGain ─┐
└───────────────(feedback)────┘
- Oscillator bank — three sine partials and one sawtooth "voice," each with its own gain, summed into a drone mix.
- Noise layer — a 4-second looped white-noise
AudioBufferprovides the "contamination" texture. - Biquad lowpass filter — the tonal gate; its cutoff is the most audible parameter target.
- Delay + feedback — a recirculating delay (
delay → feedbackGain → delay, plus a tap back into the filter) creates the haunted, smeared tail. - AnalyserNode — non-destructive tap that drives the visuals.
- Master gain — final level, ramped for power/mute transitions.
A complete node-by-node breakdown with default values is in docs/AUDIO_ENGINE.md.
Six normalized parameters (0–100) are mapped to audio targets with smoothed setTargetAtTime transitions so nothing clicks or zippers:
| Parameter | UI range | Primary audio target |
|---|---|---|
| Signal Cohesion | fragile ↔ stable | Oscillator frequency spread + filter cutoff |
| Memory Decay | slow ↔ rapid | Delay time + feedback amount |
| Observer Contamination | clean ↔ polluted | Noise-layer gain |
| Spatial Residue | flat ↔ diffuse | Reserved for spatialization (drives presets/state today) |
| Voice Reconstruction | silence ↔ clarity | Filter cutoff bias + "voice" oscillator modulation depth |
| Archive Integrity | decaying ↔ stable | Oscillator gain + master level |
The exact transfer functions (e.g. cutoff = 400 + cohesion·18 + voice·8, clamped to 4200 Hz) are documented in docs/AUDIO_ENGINE.md.
| Control | Action |
|---|---|
| Initialize overlay | Unlocks the audiovisual engine (required user gesture) |
| Recovered Nodes | Switches the active fictional site and its synthesis profile |
| Parameter sliders | Reshapes the live signal and system behavior |
| Randomize Parameters | Generates a new unstable parameter state |
| Audio toggle | Mutes / restores output (ramped, never hard-cut) |
| Power | Transitions the archive between CONNECTED and OFFLINE |
| Capture | Snapshots the Canvas and generates a fictional case file |
| Download .CASE | Saves a paired .txt + .png artifact |
| Cmd/Ctrl + K | Randomize parameters |
| / | Capture a transmission |
interference-archive/
├── index.html # Single-page shell: markup, meta, CDN + asset links
├── assets/
│ ├── css/
│ │ └── styles.css # CRT scanlines, neon glow, glitch, VT323 font
│ ├── js/
│ │ └── app.js # Audio engine, render loop, state, capture/export
│ └── interference-archive-preview.png
├── docs/ # Deep technical documentation (see below)
├── .github/ # Issue/PR templates (CI snippet in docs/TESTING.md)
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
├── SECURITY.md
├── CHANGELOG.md
├── LICENSE
├── package.json # Metadata + keywords only (no dependencies)
└── README.md
| Choice | Why |
|---|---|
| Vanilla JS (no framework) | The app is one render loop plus one audio graph. A framework's diffing model adds overhead and abstraction with no payoff for imperative Canvas/audio work. Zero dependencies = zero supply-chain surface and instant loads. |
| Web Audio API | Sample-accurate scheduling and a real DSP node graph, natively, with no audio assets to ship. |
| Canvas 2D (not WebGL) | The visuals are line/particle/text work at 720×420; Canvas 2D is the right complexity-to-fidelity tradeoff. WebGL is on the roadmap for shader-based post-processing. |
| Tailwind via CDN | Lets the shell stay a single hand-authored HTML file while keeping layout declarative. Production hardening (pinning/self-hosting) is tracked on the roadmap. |
| VT323 (Google Fonts) | A period-accurate bitmap terminal face that sells the diegetic 1990s console. |
| Static hosting (GitHub Pages) | No backend means no server state to secure, scale, or pay for. |
The longer-form reasoning, including trade-offs we explicitly rejected, is recorded as decision records in docs/DESIGN_DECISIONS.md.
| Document | What's inside |
|---|---|
docs/ARCHITECTURE.md |
Module map, data flow, lifecycle, and coupling model |
docs/AUDIO_ENGINE.md |
The full Web Audio graph and parameter transfer functions |
docs/RENDERING.md |
The Canvas render loop, layers, and analyser bridge |
docs/API_REFERENCE.md |
Every function, its signature, and side effects |
docs/COMPONENTS.md |
The UI regions and the DOM contract they rely on |
docs/SETUP.md |
Local dev, browser support, troubleshooting |
docs/TESTING.md |
Test strategy and manual QA checklist |
docs/PERFORMANCE.md |
Budgets, hot paths, and profiling notes |
docs/ACCESSIBILITY.md |
Current state, known gaps, and the a11y plan |
docs/DEPLOYMENT.md |
Publishing to GitHub Pages and alternatives |
docs/DESIGN_DECISIONS.md |
Architecture decision records (ADRs) |
docs/ROADMAP.md |
Where this is going |
- Testing — This is a runtime-driven visual/audio app, so the strategy is a documented manual QA matrix plus static checks (HTML validation, link checking) suitable for CI. A ready-to-use workflow is in
docs/TESTING.md. - Performance — The render loop targets 60 fps at 720×420. Per-frame work is bounded (fixed particle count, fixed analyser bins, no per-frame allocations in the hot path). Budgets and profiling guidance are in
docs/PERFORMANCE.md. - Accessibility — The piece is inherently visual/auditory, but the interface can still be made far more inclusive. Current status, honest known gaps (keyboard focus order, ARIA on the custom controls,
prefers-reduced-motion), and the remediation plan are indocs/ACCESSIBILITY.md.
Deployed as a static site on GitHub Pages — push to the default branch and serve the repository root. Because there is no build step, "deploy" is "publish these three files." Full instructions and alternatives (Netlify, Cloudflare Pages, any static host) are in docs/DEPLOYMENT.md.
Highlights (full list in docs/ROADMAP.md):
- FFT-driven visuals wired directly from
AnalyserNodefrequency bins - Microphone / line-input analysis mode
- Seeded, reproducible generation
- Persistent archive history via IndexedDB
- Exportable audio recordings (
MediaRecorder) - WebGL shader post-processing and an installation/kiosk mode
Contributions, experiments, and forks are welcome. Start with CONTRIBUTING.md for workflow, coding conventions, and the areas where help is most useful, and please follow the CODE_OF_CONDUCT.md. Security reports go through SECURITY.md.
The interface draws from CRT terminals, scientific instrumentation, analog surveillance, archival media, numbers stations, abandoned institutional technology, and the administrative aesthetics of classified documentation. The horror is primarily systemic: the machine appears orderly while its measurements and generated reports imply that something cannot be contained by the interface built to observe it.
Built by Zazie Productions. Released under the MIT License.
Third-party runtime dependencies loaded via CDN: Tailwind CSS and the VT323 typeface (Google Fonts). All audio and imagery is generated in the browser.