P1a: material import, sixteen faces, sample engine and quarry

This commit is contained in:
Emil
2026-11-01 00:56:51 +03:00
parent db4368efae
commit ebdeca3b92
28 changed files with 2799 additions and 114 deletions
+47 -37
View File
@@ -1,25 +1,26 @@
# ZHILA
Sound is terrain. A sampling instrument where the material is cut in a quarry, laid as stones in a nave, and played by a hand or an agent through the same verbs.
Sound is terrain. A sampling instrument where material is cut in a quarry, laid as stones in a nave, and played by a hand or an agent through the same verbs.
This repository is **P0**: the engine, the wire, and the first descent surface. The visual demo lives in [`reference/demo`](reference/demo) as a design reference — not as the codebase. The roadmap is in [`docs/PLAN.md`](docs/PLAN.md).
This repository is **P1a**: the engine, the wire, the descent, and the material room. A found sound can be imported, decoded and cut into sixteen faces, struck on its own, or laid on a vein and heard in the loop. The visual demo in [`reference/demo`](reference/demo) remains a design reference, not the codebase. The roadmap is in [`docs/PLAN.md`](docs/PLAN.md).
![P0 surface](preview-p0-playing.png)
![The quarry](preview-p1-quarry.png)
## What is real in P0
## What is real
- **Rust engine.** `cpal` output stream, 48 kHz, five veins, a sixteen-step phrase, a fixed voice pool, per-vein gains and levels, a bar clock. No Web Audio.
- **Protocol v1.** JSON control frames and a 31-byte binary meter frame over a local WebSocket.
- **Session + journal.** Validation, revisions, a journal for project edits (tempo, gains), performance events that are not history.
- **Daemon.** Loopback-only, token in the hello, origin guard, snapshot on connect, results and events, binary meters at 30 Hz.
- **Surface.** A Svelte 5 descent whose terrain, eye, city, road, stones and playhead react to the five vein levels and the master; a real wire log; engine facts; cilia; a master signal trace; vein gain sliders; transport; keyboard control.
- **Verification.** Rust unit tests, a wire smoke test against live audio, a browser smoke test with screenshots, `svelte-check`, a production build.
- **Rust engine.** `cpal` output at 48 kHz, five veins, a sixteen-step phrase, a fixed voice pool, per-vein levels and gains, a bar clock. Procedural voices for empty pattern rows; real sample voices for rows with a face. No Web Audio.
- **Material.** Import through the local wire; `symphonia` decodes WAV/FLAC/MP3/OGG; onset analysis finds seams and cuts sixteen faces with RMS, rank and waveform peaks; the archive keeps the mono f32 samples. The sample rate is preserved and the engine handles the playback ratio.
- **Quarry.** Import, core strip, zoomed waveform with face boundaries, sixteen-face wall, face audition, seal and stats; assign a face to a pattern row; edit the sixteen steps; strike a face; all against the live session.
- **Protocol v1.** JSON control frames, a 31-byte binary meter frame, and a binary import path (an import header followed by the audio bytes). Snapshot, results, events, token and origin guard.
- **Session + journal.** Validation, revisions, a journal for project edits (tempo, gains, pattern faces and steps); play, stop and strikes are performance, not history.
- **Surface.** A Svelte 5 descent whose terrain, eye, city, road, stones and playhead react to the five levels and the master; a real wire log; engine facts; cilia; a master signal trace; vein gains; transport; keyboard.
- **Verification.** Rust unit tests, a live wire test, a material import test, a browser quarry test and the P0 regression smoke, plus `svelte-check` and a production build.
The Tauri 2 shell is scaffolded in [`app`](app), but **it has not been built on this machine**: the system is missing the GTK/WebKit/dbus development packages and `sudo` requires a password. The exact blocker and the install command are in [`docs/P0.md`](docs/P0.md). The browser surface is the working P0 path.
The Tauri 2 shell is scaffolded in [`app`](app), but **it has not been built on this machine**: the system is missing the GTK/WebKit/dbus development packages and `sudo` requires a password. The exact blocker and the install command are in [`docs/P0.md`](docs/P0.md). The browser surface is the working path.
## Quick start
Requirements: Rust (tested on 1.99), Node 22, ALSA (present on this machine). The daemon uses the default output device; if there is none it reports offline and the surface still connects.
Requirements: Rust (tested on 1.99), Node 22, ALSA. The daemon uses the default output device; without one it reports offline and the surface still connects.
```bash
./scripts/dev.sh
@@ -27,51 +28,60 @@ Requirements: Rust (tested on 1.99), Node 22, ALSA (present on this machine). Th
Open **http://127.0.0.1:5173**. The daemon listens on `ws://127.0.0.1:4317` with the development token `dev`.
Or run the two halves separately:
Or the two halves separately:
```bash
cargo run -p zhila-daemon -- --port 4317
npm --prefix ui run dev
```
Controls: `space` play/stop · `s` strike the selected vein · `1`–`5` select a vein · click a lane label to select; double-click to strike · tempo `−` / `+`.
### Try the material loop
The daemon prints the audio device, rate, buffer, sample format and session id. Use `--no-audio` for UI/protocol tests, `--port` to change the port, and `ZHILA_TOKEN` or `--token` to change the dev token. The surface reads `VITE_ZHILA_WS` and `VITE_ZHILA_TOKEN` at build time; both default to the values above.
1. Open **quarry** in the top rail.
2. `take a core` and choose a WAV (a short field recording works; the fixture `fixtures/clicks-01.wav` is one).
3. The file is decoded, the seams are found and sixteen faces are cut. Click a face, then `lay on bass`.
4. `space` plays the loop; the bass row now triggers the sample while the other veins keep their procedural voices. Edit steps in the pattern; each edit goes to the engine and the journal.
5. Go back to **nave** to see the pattern on the road and the levels in the room.
Controls: `space` play/stop · `s` strike the selected face in the quarry or the selected vein in the nave · `1`–`5` select a vein (nave) or a pattern row (quarry) · click a face to select, double-click to strike · tempo `−` / `+`.
The daemon prints the audio device, rate, buffer and session id. Use `--no-audio` for UI/protocol tests, `--port` to change the port, and `ZHILA_TOKEN` or `--token` to change the dev token. The surface reads `VITE_ZHILA_WS` and `VITE_ZHILA_TOKEN` at build time.
## Layout
```
crates/zhila-protocol wire types, binary meter frame, tests
crates/zhila-core session, journal, validation, engine trait
crates/zhila-engine cpal stream, voices, phrase, levels
crates/zhila-daemon WebSocket daemon, auth, broadcast
ui Svelte 5 surface (browser-first)
ui/src/lib/descent.ts the reactive canvas
app Tauri 2 shell (scaffolded, system deps missing)
docs plan, protocol, P0 notes
crates/zhila-protocol wire types, binary meter frame, cores/faces, tests
crates/zhila-core session, journal, material snapshot, engine trait
crates/zhila-engine cpal stream, procedural + sample voices, levels
crates/zhila-material decode, onset analysis, sixteen faces, peaks
crates/zhila-daemon WebSocket daemon, auth, binary import
ui Svelte 5 surface: descent + quarry
fixtures a small click track for the smoke tests
app Tauri 2 shell (scaffolded; system deps missing)
docs plan, protocol, P0/P1 notes
reference/demo the earlier design demo
```
## Tests
```bash
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
npm --prefix ui run check
npm --prefix ui run build
node scripts/wire-smoke.mjs # daemon running
npm --prefix ui run smoke # daemon + vite dev running
./scripts/check.sh # fmt, clippy, cargo tests, svelte-check, build
node scripts/wire-smoke.mjs # daemon running
node scripts/material-smoke.mjs # daemon running: import, face, play
npm --prefix ui run smoke # daemon + Vite: P0 regression, screenshots
npm --prefix ui run quarry-smoke # daemon + Vite: import, assign, play, screenshots
```
`npm --prefix ui run smoke` writes `preview-p0.png` and `preview-p0-playing.png` and asserts the wire, the pattern, the cilia and the live levels.
`npm --prefix ui run smoke` writes `preview-p0*.png`; `quarry-smoke` writes `preview-p1-*.png`.
## P0 limits (next, not hidden)
## P1a limits (next, not hidden)
- One fixed phrase; patterns are not editable in the surface yet.
- Five veins are fixed; the engine has per-vein gain, no sends or automation.
- The surface is read-only except transport, strike, tempo and gains.
- No session persistence, project file or archive yet.
- No agent and no MCP in this phase; the command bus and the journal are the ground for them.
- The archive is in memory. There is no save/load yet; a daemon restart loses imported cores and pattern edits. Persistence is the next slice.
- No recording, no waveform trimming, no manual cut/loop points; the sixteen faces are automatic seams.
- No undo/redo for pattern edits yet; the journal records them, the surface does not expose history.
- One fixed two-bar phrase and five veins; pattern rows are veins, one face per row, no per-step face or pitch.
- Formats depend on symphonia; WAV is the tested path. AIFF, multi-channel files and very long files are not specifically handled.
- No agent/MCP in this phase.
- The Tauri shell is not verified; see the blocker.
P1 is the core session and the material room: import/record, analysis, cores and faces, a zoomable waveform and a real pattern model. The plan is in [`docs/PLAN.md`](docs/PLAN.md).
The next slice is P1b: a project file and SQLite journal, an asset archive, autosave and reload, undo for pattern edits, recording, and waveform trimming. Then the arrangement and the agent. The plan is in [`docs/PLAN.md`](docs/PLAN.md); the P1a notes are in [`docs/P1.md`](docs/P1.md).