Files
zhila/README.md
T

109 lines
7.5 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 **P1b**: the engine, the wire, the descent, the material room, and a project that survives a restart. A found sound can be imported, cut into sixteen faces, laid on a vein and heard in the loop; tempo, gains and pattern 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)
## 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.
- **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. 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.
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 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`.
## P1b 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.
- No recording, no waveform trimming, no manual cut/loop points; the sixteen faces are automatic seams.
- 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 P1c: recording, waveform trimming, reconstructed undo across reloads, and embedding the engine 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).