# Doppler Siren — `doppler-siren@1.0.0`

A siren moving through the dark, its sound rings bunching up and turning blue ahead of it and stretching out red behind. Loops perfectly.

The Doppler effect made visible. A siren moves along a closed path and sends out a ring of sound at regular moments; every ring keeps its own centre, where the siren was when it left, so the rings crowd together and turn blue ahead of the siren and stretch apart and turn red behind it. Make the siren faster than the sound and the rings pile up into a shock cone. Choose the siren's speed against the sound, the number of rings, the path (circle, sweep or figure eight), the colours, the line thickness, the brightness and the size of the path.

- Kind: background; status: published; digest sha256:dd591cfa52462ecd14eccc48b1d5f74b78995b588f4df43ba1fef43830781b74
- Role: Shader background (three.js): a physics showpiece for science, sound and motion content.
- Implementation: three; entry `src/doppler-siren.js`
- Files: https://aipixelwizard.com/registry/doppler-siren/1.0.0/<path> (`LICENSE`, `asset.json`, `demo.js`, `index.html`, `src/doppler-siren.js`)
- Manifest JSON: https://aipixelwizard.com/api/v1/visuals/doppler-siren/versions/1.0.0
- Runnable demo: https://aipixelwizard.com/registry/doppler-siren/1.0.0/index.html (open over http; it imports sibling packages by relative path)
- Dependencies (exact): `three@0.185.1` (source) — Renderer and a full-screen shader material.; `apw-runtime@1.0.0` (demo) — Demo harness.

## Exports
- `createDopplerSiren({ mach?, waves?, path?, palette?, thickness?, brightness?, radius? }) => { scene, camera, setSize(width, height), update(time, loopSeconds), dispose() }` — The rings as one full-screen shader; render its scene with its camera, ideally through createBloomPass.
- `createBloomPass(renderer, { width, height, glow?, exposure?, grain? }) => { render(scene, camera, output?), dispose() }` — A small bloom with tone mapping and a fixed grain.
- `PALETTES: { classic, neon, gold, mono }` — Colours for the stretched and compressed rings and the siren's lamp.

## Parameters
- `mach` (number 0.2–1.8, default 0.6) — Speed vs sound: The siren's speed over the speed of the waves; above 1 the rings pile into a shock cone.
- `waves` (integer 12–48, default 32) — Rings per loop
- `path` (select one of circle | sweep | eight, default "circle") — Path
- `palette` (select one of classic | neon | gold | mono, default "classic") — Colours
- `thickness` (number 0.006–0.04, default 0.012) — Line thickness
- `brightness` (number 0.5–2, default 1) — Brightness
- `radius` (number 0.2–0.8, default 0.45) — Path size

## Composition
- Inputs: Time within the loop; the speed, ring count, path, colours, thickness, brightness and path size.
- Outputs: A full-frame shader image through a bloom pass.
- Coordinates: Screen-centred, ±1 vertically; the siren moves on a path of the chosen radius around the origin; a ring's radius is the wave speed times its age.
- Lighting: Self-lit lines; the bloom provides the glow.
- Timing: update(time, loopSeconds) sets the loop phase; frames render in any order.
- Works with: kinetic-title, number-reveal
- Up to 44 rings are evaluated per pixel; each is tinted by how much the pixel lies in the direction the siren was moving when the ring was born.

## Output and loop
- 1080×1080, 30 fps, 10 s, procedural
- Seamless loop: yes — The siren's path repeats every loop and each ring's centre, radius and fade are functions of its age since it left, with rings born at fixed moments of the loop, so the last frame meets the first.
- Verified 2026-10-06 in Chrome 154.0.8037.97 headless on Linux 7.0.0-38-generic (x64), Node 22.14.0 (WebGL 2 via SwiftShader (software)); deterministic: yes; loop check: Frame 0 vs end state (progress 1): mean |Δ| 0.0000, max |Δ| 0 of 255.

## Use it
New project: Create a WebGLRenderer, createDopplerSiren() and createBloomPass(renderer, { width, height }); call siren.setSize(w, h) and each frame siren.update(time, loopSeconds) then bloom.render(siren.scene, siren.camera).

Existing project: Copy src/doppler-siren.js and render siren.scene with siren.camera into your own target.

Reuse ideas:
- Physics explainer: Circling siren.
- Sonic boom: Shock cone, sweep.
- Nightclub backdrop: Neon figure eight.

## Limitations
- Rings are drawn as lines, not as a physical pressure field; intensity does not fall off with distance as it would in air.
- Above Mach 1 the shock cone appears only as the envelope of the rings.

## License
MIT. Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.

Provenance: Adapted from the owner's threejs-sketches/physics-forces/doppler-siren (MIT) and rebuilt for AI Pixel Wizard in October 2026 as an original shader: rings born at fixed moments along a periodic siren path, each with its own centre, tinted by the direction of travel, with an exact loop.

