# dither.fm / strudel

Open [`../../index.html`](../../index.html), now the homepage for **dither.fm**.
This page develops the original a.sh homepage’s Bayer-dithered noise field with
music-driven colour and geometry. **Bring your own Strudel file**: there are no
bundled tracks, example picker, or default track downloads. Load or drop a local
`.js`, `.strudel` or `.txt` file, then press **play**; it never autoplays.
Volume defaults to **100%** and visual response to its maximum **2×**.
The original grey dither field appears before playback and while stopped.
Play crossfades into the reactive visuals over one second; Stop fades back to
grey. Quiet passages during playback still go black. Reduced motion skips the
crossfade and keeps the field still.

Controls live in a centred desktop window styled after `website/example.html`.
The top menu keeps **settings** centred and a second **Full screen** button at the
right, available even while the window is minimised. Both fullscreen buttons
stay in sync. **Minimise**, beside Visual settings and
Full screen, or the title-bar minus shrinks the window into that menu button;
click **settings** to restore it. Playback continues while minimised. The
420 ms window animation is skipped for reduced motion. Drag the title bar on
desktop; double-click it or use its right-hand reset button to centre it again.
The grey dithered rhombus favicon is pixel art in `favicon.svg`, with PNG and
native ICO sizes. All formats have transparent backgrounds; the menu brand
shares the menu bar's background.
`og.png` is the 1200×630 sharing image using the same diamond and Jersey 10 type;
Open Graph and Twitter card metadata point to `https://dither.fm/og.png`.

## Run locally or upload

From the project root:

```powershell
.venv\Scripts\python.exe -m http.server 8787 --bind 127.0.0.1 --directory website
```

Visit **http://localhost:8787/** on that computer. Localhost is
enough for testing; a remote domain needs **HTTPS** for audio worklets. Opening
the HTML directly as `file://` is not supported.

For deployment, upload the `website/` directory to a static HTTPS host. There is
no build step, backend, API key or Cloudflare-specific setup. The required new
files are `index.html`, `assets/visualizer/`, `og.png` and `favicon.svg`, `favicon.png`,
`favicon.ico`; retain `assets/fonts/`. No tracks folder is needed. When updating an
existing deployment, remove any old `tracks/` copies from the server as well.
The older a.sh player integration remains in `assets/music/`; the dither.fm
homepage uses `assets/visualizer/` directly and defaults to 2× response.

The pinned Strudel engine loads from UNPKG. Default sample libraries and external
samples need internet access and permissive CORS. Tracks with embedded samples
do not need the default sample-library downloads. A restrictive hosting
CSP must permit the Strudel runtime's JavaScript evaluation, its blob worklets,
and its sample/CDN fetches.

The page includes a descriptive search title and description, canonical URL,
Open Graph/Twitter image metadata, and `WebSite` JSON-LD naming dither.fm.
Upload `robots.txt` and `sitemap.xml` alongside it. After deployment, check the
public HTTPS page, image and sitemap, then submit the sitemap in Google Search
Console. HTTP and any configured www hostname should redirect to `https://dither.fm/`.
Local metadata checks do not establish live crawlability or search indexing.

## Use

- Load a `.js`, `.strudel` or `.txt` file, or drop it anywhere on the page, then
  press play. The page reads files locally; it does not upload them. Strudel is
  executable JavaScript, so use tracks you trust.
- Stop cancels pending startup, stops the clock and silences effect tails.
  Play starts the track again from the beginning. Switching files also stops it.
- Automatic visuals blend **clouds → ripples → folds → tunnel**, changing over
  sixteen-cycle stages. Pick a fixed pattern or palette to hold a particular look.
- **Response** changes visual sensitivity. **Pixel size** changes the dither grid.
  **Motion** freezes the visual frame without interrupting the music. Reduced
  motion is respected by default; the motion button can explicitly enable it.
- **Volume** controls the final audio output, including already-playing tails.
  The visual signal is tapped before this trim, so muting does not stop the show.
- **H** minimises/restores controls, **F** toggles full screen, **Space** plays/stops.
  Buttons and editable controls retain their normal keyboard behavior.

## How it follows the music

`player.js` evaluates the full source with Strudel's real transpiler and scheduler.
It taps the merged stereo output into an unconnected Web Audio analyser, including
reverb, delay, filtering and ducking. There is no microphone, screen capture,
duplicate audio path, or fake beat detector.

Low-frequency energy expands/distorts broad shapes; mids deform the field;
high frequencies add fine contour detail; attack energy accents the field.
The spectral centroid influences hue. Overall level controls how far the field
emerges from pure black. Quiet passages during playback fade back to black;
the grey idle field only returns when playback stops. Envelopes are smoothed, and
the shader uses spatial colour/dither instead of full-screen white flashes.
The actual scheduler cycle controls the scene transitions. BPM display assumes
the four-beats-per-cycle convention used by this project's tracks.

Each source file seeds its initial visual character. The four patterns blend
continuously, while the automatic palette slowly travels with the musical clock.
This is signal analysis, not semantic recognition of isolated instruments or key.

## Compatibility and boundaries

The pinned engine is `@strudel/web@1.3.0`, matching the existing website player.
Use ordinary final patterns, `stack(...)`, `arrange(...)` or Strudel labels.
Embedded WAVs, pitched sample maps, `samples(...)`, and the site's slider default
values work. Custom libraries, editor-only visual helpers, Hydra and soundfonts
are not automatically supplied: include the dependencies your track requires.
Sample references are checked across the first 256 cycles; the first sixteen
are predecoded. Later samples still download on first use as in normal Strudel.

WebGL 2 is needed for the animated field. The page shows an explicit error and a
black background if unavailable. Rendering uses a deliberately low-resolution
canvas at up to 30 fps and pauses while the page is hidden. Audio keeps playing.

Tested on 2026-10-16 in Chromium at desktop and a 390×844 mobile viewport:
Track 12 playback with real nonzero audio-band data, local Track 13 v3 upload,
external-sample playback, all main transport/settings controls, fullscreen,
volume zero, malformed-source recovery, and stop during a delayed evaluation.
Mobile viewport testing is not a Safari/iOS hardware test. No audio export.
The OS window was also checked at 1440×900 and 390×844: centred initial position,
desktop drag/reset, both minimise buttons, restore from the top menu, rapid
animation reversal, uninterrupted playback while minimised, reduced motion,
and all three action buttons sharing a row on mobile without horizontal overflow.
Rhombus SVG/PNG/ICO and the current JS/CSS return HTTP 200; no browser runtime or
console errors were recorded in that check.
The upload-only revision was checked with an empty initial source, Play disabled,
no example picker or track requests, then a local Track 12 file loaded and played
with nonzero audio analysis. Menu fullscreen remained available while minimised,
both fullscreen labels stayed in sync, and Stop worked after restoring the window.

## Source and credits

- [Page](../../index.html), [controller](app.js), [audio adapter](player.js), [shared analysis](analysis.js),
  [shader](dither.js), [styles](visualizer.css).
- Original a.sh homepage: existing Ashima/Stefan Gustavson noise,
  Bayer matrix and dither field; their license notice is retained in `dither.js`.
- [Strudel embedding documentation](https://strudel.cc/technical-manual/project-start/)
  and [Strudel source](https://codeberg.org/uzu/strudel).

This integration is AGPL-3.0-or-later. The original noise functions retain their
included permissive notice; music remains credited in each track's source.
Serve the readable JS/CSS/source files alongside the page when publishing.

For debugging, `window.ditherVisualizer.state` exposes a read-only snapshot of
the measured bands, playback clock, tempo and visual settings.
