# odeon
P2P concert hall โ walk on stage and play as a band: grand piano, drum pads,
theremin and bass, all synthesized in the browser. Live at
**[game6.ardegazu.ro](https://game6.ardegazu.ro)**, repo published at
**[git.ardegazu.ro/game6](https://git.ardegazu.ro/game6)**.
Up to 8 musicians on stage (everyone else gets the best seat in the house โ
spectators hear the full mix and applaud). Live notes travel peer-to-peer the
moment you play them; **recorded loops are the band's glue** โ record a 1/2/4
bar riff and it's broadcast as note data, quantized to 16ths, and played by
every peer locked to the shared bar grid, so the groove stays tight no matter
the network latency. Installable PWA, safe-area aware, touch + keyboard.
## Architecture
- **No server, no audio streaming.** Browsers are [js-libp2p](https://libp2p.io)
peers: they meet through a circuit-relay-v2 node (gossipsub peer discovery),
upgrade to direct WebRTC, and speak a room-scoped protocol under
[`client/src/game6/net/`](client/src/game6/net/), vendored from
[neon-grid](https://git.ardegazu.ro/game1/) and game-agnostic except for the
protocol id. Rooms are keyed by a secret in the URL fragment โ membership is
proved pairwise with a sealed hello, and every frame is end-to-end encrypted
by libp2p's noise layer. What crosses the wire is *notes, not sound*: tiny
JSON frames (a note-on is ~40 bytes), synthesized identically by every peer's
WebAudio engine
([`client/src/game6/game/audio.cljs`](client/src/game6/game/audio.cljs)).
- **One peer conducts.** The host owns the transport (BPM + the bar-zero
instant); every peer estimates its offset to the host's clock via ping/pong
and maps host time onto its local AudioContext for sample-accurate loop
scheduling ([`clock.cljs`](client/src/game6/game/clock.cljs),
[`looper.cljs`](client/src/game6/game/looper.cljs); the arithmetic and every
referee rule sit in a pure [`sim.cljs`](client/src/game6/game/sim.cljs) the
golden vectors drive). Host loss promotes the lowest
open-channel id, which re-anchors the clock with the groove's phase intact.
Colliding host claims resolve by seniority (an established band's clock
outranks a fresh boot), near-ties by lowest id. Late joiners get a snapshot
(transport + all loops) and are in the pocket within a second.
- **Hosting without an origin server.** `client/dist/` is pinned to IPFS,
named by IPNS and served straight from the gateway on the app's own domain
(DNSLink). The published repo works the same way (`deploy/publish-repo.sh`
+ the shared `git.ardegazu.ro` source root).
## Instruments
| | wire | synthesis |
|---|---|---|
| ๐น grand | note on/off, velocity | layered detuned triangles + harmonics, mellow lowpass |
| ๐ฅ pads | one-shot, 16 voices | pitched-drop sines, shaped noise, metallic squares |
| ๐ theremin | ~15 Hz control stream | portamento sine, vibrato LFO, tanh warmth |
| ๐ธ bass | note on/off, velocity | saw + sub sine, plucky filter envelope |
Keyboard: piano/bass on `A W S E D F T G Y H U J K โฆ` (`Z`/`X` shift octave),
pads on `1234 / QWER / ASDF / ZXCV`, `B` records, `M` metronome. Touch: keys
and frets glissando under your finger, the theremin is an XY surface (pentatonic
snap toggle), pads are pads.
## Develop
Needs node โฅ 22, a JVM โฅ 17 and the `clojure` CLI (shadow-cljs).
```bash
cd client
npm install
npm run dev # watch build โ http://localhost:5173
```
Open two tabs on the same `#room` URL. The room secret never leaves the
browser; a fresh visit mints one. While the shadow server runs, port 4173
serves the last release build from `client/dist` (the relay's origin
allowlist covers 4173/5173 only). `npm test` compiles the pure game/net
namespaces and runs the golden vectors extracted from the TS
implementation while it was canon.
## Build & deploy
```bash
cd client && npm run build # โ client/dist
ird ipfs add client/dist --json --yes # โ CID
ird ipfs ipns publish game6.ardegazu.ro <cid>
```
## Publish the repo
```bash
deploy/publish-repo.sh # builds deploy/.site (landing page + bare mirror)
# then, from the shared source-root repo: ./assemble.sh game6
```
The script enforces the anonymity gate: single anonymous author, no identity
strings in any object, no nested site builds.
MIT โ see [LICENSE](LICENSE).