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 remains a design reference, not the codebase. The roadmap is in docs/PLAN.md.

The quarry

The Tauri window

P1b · restored project

P1c · a recorded take

P1d · a cut and trimmed core

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.

./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:

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

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

./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; the phase notes are in docs/P1.md.

S
Description
DAW
Readme
17 MiB
Languages
JavaScript 37.3%
Rust 29.5%
CSS 14.1%
Svelte 8.5%
TypeScript 7.9%
Other 2.7%