README.md

Tapes #

One endless tape for the whole world. Once a day, anyone can add ten seconds of their voice to the end of it, in the sound of AM radio.

Originator: Nighthaven (@moja.blue)

Live at https://tapes.soiree.blue

日本語

How it works #

  • Opening the page plays the tape from one minute before the end. When playback reaches the end, it waits for the next recording.
  • Skip back and forward by 10, 30 or 60 seconds.
  • The orange button records ten seconds from your microphone. The browser turns it into AM radio sound (mono, 100 Hz to 5 kHz, heavy compression), encodes it as Ogg Opus (16 kHz, 24 kbps, about 30 KB), and appends it to the tape.
  • No names are shown. Only you know where your own recordings are. The memo under the player lists them, and you can pass a position URL (/?at=<seconds>) to whoever you like.
  • Listening needs no account. Recording needs a Bluesky account (ATProto OAuth). Each account can record once a day; the day changes at midnight Japan time.
  • /?demo opens a sample tape that runs entirely in the browser and sends nothing.

How it is built #

  • worker/ is a Cloudflare Worker. The sound goes to R2 (slot/<n>.ogg), and the order goes to D1 (schema.sql). Nothing is written to the PDS, because PDS records are public and would show who recorded where.
  • The Worker keeps the DID of each recording so that reported recordings can be removed. The DID is never shown on the page or returned by the API.
  • To record, the browser asks the user's PDS for a service-auth JWT (com.atproto.server.getServiceAuth, lxm blue.moja.tapes.me), and the Worker verifies it against the key in the DID document.
  • worker/d1-budget.js caps the rows written to D1 per day.
  • R2 is billed past its free tier, so the Worker caps each recording at 40 KB, all recordings at 3,000 a day and the whole tape at 225,000 slots (about 9 GB), and keeps sound in Cloudflare's edge cache for 5 minutes.
  • src/ is the page (TPS-L2 style player, recorder, AM filter, Opus encoder and Ogg muxer). build.sh bundles it into dist/ with esbuild.

Run it locally #

npm ci
./build.sh
npx wrangler d1 execute tapes --local --file schema.sql
npx wrangler dev --ip 127.0.0.1 --port 8807 --local-upstream 127.0.0.1:8807 --var DEV_AUTH:1

Open http://127.0.0.1:8807/?devdid=did:plc:alice to record as a test user without signing in. DEV_AUTH only works on 127.0.0.1 and must never be set in production. Opening http://127.0.0.1:8807/ without devdid uses a real loopback OAuth sign-in.

Sample voices #

The voices on the sample tape (public/demo/) are Bluesky-chan, made with ZEN-TTS (https://zenryokukikai.com) and the voice model from kojira/zen-tts-models. The model's terms ask projects that use it to publish their source under an OSI license, which is why this repository is public. ZEN-TTS and the model are not included here. bsky/make-bsky.sh regenerates the voices; set ZEN_TTS_DIR to a folder that holds tts/, voices/ and radio.js.

License #

The code is under the MIT License. The sample voices follow the terms of the voice model.