Files
zhila/README.md
T

119 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ZHILA
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 **P1d**: the engine, the wire, the descent, the material room, a project that survives a restart, a microphone, and a waveform you can cut. A found or recorded sound is cut into sixteen faces by hand or by analysis, laid on a vein and heard in the loop; tempo, gains, pattern and face edits are autosaved and reversible. 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).
![The quarry](preview-p1-quarry.png)
![The Tauri window](preview-tauri.png)
![P1b · restored project](preview-p1b-restored.png)
![P1c · a recorded take](preview-p1c-record.png)
![P1d · a cut and trimmed core](preview-p1d-faces.png)
## What is real
- **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.
- **Project.** A portable folder: `project.db` (SQLite) plus `assets/`. On daemon start it loads the cores, pattern, tempo, gains and journal; every project edit autosaves. The default is `~/.local/share/zhila/descent-04`; use `--project PATH` for another. The project name is shown in the top rail.
- **Undo/redo.** Tempo, gains and pattern steps/faces push a small history; `Ctrl/⌘+Z` and `Shift+Ctrl/⌘+Z`, or the rail buttons. The journal records every edit. History is per session: after a reload the project is restored, the undo stack starts empty.
- **Recording.** `record` in the quarry opens the microphone, captures mono PCM through an AudioWorklet, encodes a 16-bit WAV in the surface and sends it through the same import path. The take becomes a core with sixteen faces, seal `recorded`, and is autosaved in the project. Stops at 30 s; no monitoring.
- **Quarry editing.** Drag any of the seventeen seams on the waveform: the inner seams recut the sixteen faces, the outer seams trim the playable range. The core recomputes RMS and rank from the samples; the engine hears the new ranges on the next trigger; the project stores the cuts; the edit is undoable.
- **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 **built and running**: a native window loading the same surface, connected to the daemon. It is built with Tauri 2.12.5 / CLI 2.12.4 against WebKitGTK 2.54.2, GTK 3.24.52 and dbus 1.16.2. The binary is `app/src-tauri/target/release/zhila-app` (13 MB); installers are a later step.
## Quick start
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
```
Open **http://127.0.0.1:5173**. The daemon listens on `ws://127.0.0.1:4317` with the development token `dev`.
Or the two halves separately:
```bash
cargo run -p zhila-daemon -- --port 4317
npm --prefix ui run dev
```
### Try the material loop
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. Or press `record`, speak/play into the microphone, press `stop`; the take arrives the same way. 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.
### Tauri
```bash
npm --prefix ui run build
cd app
./node_modules/.bin/tauri build --no-bundle
./src-tauri/target/release/zhila-app
```
The built window embeds the surface and connects to the daemon at `ws://127.0.0.1:4317`; start the daemon first. `tauri dev` uses the Vite server at `127.0.0.1:5173`. The engine is still the separate daemon in this phase. Rebuild the native app after any UI change; the binary embeds `ui/dist` at compile time. The recording and cutting UI are in the shared surface; WebKitGTK microphone permission still needs a manual check on this machine.
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, 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-project SQLite project, assets, load/save, undo persistence
crates/zhila-daemon WebSocket daemon, auth, binary import, autosave
ui Svelte 5 surface: descent + quarry
fixtures a small click track for the smoke tests
app Tauri 2 shell (built; engine remains the daemon)
docs plan, protocol, P0/P1 notes
reference/demo the earlier design demo
```
## Tests
```bash
./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
node scripts/persistence-smoke.mjs # spawned daemon: import/edit/undo/restart
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 project-smoke # daemon + Vite: project, undo, import, screenshots
npm --prefix ui run record-smoke # daemon + Vite: fake microphone take, faces, seal
npm --prefix ui run faces-smoke # daemon + Vite: drag a seam, trim, save, screenshot
npm --prefix ui run restored-smoke # after restarting the daemon on the saved project
```
`npm --prefix ui run smoke` writes `preview-p0*.png`; `quarry-smoke` writes `preview-p1-*.png`.
## P1d limits (next, not hidden)
- The project persists, but undo history does not: after a reload the undo stack starts empty. The journal is stored for audit, not reconstructed into undo.
- Recording is mono, 16-bit, requested 48 kHz, up to 30 s, no monitoring and no input device choice. WebKitGTK/Tauri microphone permission is untested; the browser path is verified with Chromium's fake capture.
- Face editing is seventeen contiguous cuts: no zoom, fades or per-face loop points. The trim is the outer pair of cuts; the source bytes are untouched.
- The Tauri binary still embeds an older surface and starts no engine; it is a viewer in this phase.
- One fixed two-bar phrase and five veins; pattern rows are veins, one face per row, no per-step face or pitch.
- The Tauri binary currently embeds an older surface build; rebuild it after UI changes (`npm --prefix ui run build`, then `tauri build`). The engine remains the separate daemon.
- 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 now builds and runs; the engine is still the separate daemon (embedding is next).
The next slice is P1e: undo history across reloads and the engine host inside the Tauri backend. Then the arrangement and the agent. The plan is in [`docs/PLAN.md`](docs/PLAN.md); the phase notes are in [`docs/P1.md`](docs/P1.md).