P1a: material import, sixteen faces, sample engine and quarry
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
# P1a · material and the quarry
|
||||
|
||||
P0 proved the spine: Rust audio, a session, a protocol, a surface. P1a puts material into it: a file becomes a core, a core becomes sixteen faces, a face becomes a voice in the pattern.
|
||||
|
||||
## The path of a sound
|
||||
|
||||
```
|
||||
file input (browser)
|
||||
→ ArrayBuffer
|
||||
→ ws: import_core { id, name, seal } + binary audio
|
||||
→ zhila-material: symphonia decode → mono f32 at the source rate
|
||||
→ onset analysis: 10 ms energy hops, positive flux, local peaks
|
||||
→ sixteen faces (sample ranges + RMS + rank)
|
||||
→ waveform peaks (1400 bins, normalised)
|
||||
→ zhila-core: Core { summary, Arc<[f32]>, faces }
|
||||
→ Session::import_core: archive + journal + sync_material
|
||||
→ engine: Arc<MaterialSnapshot> through the control queue
|
||||
→ snapshot broadcast → the quarry
|
||||
```
|
||||
|
||||
The pattern keeps its procedural voices until a row is given a face. `set_pattern_face` writes the reference and syncs a new material snapshot; the audio thread triggers a sample voice for that row's steps and leaves the other rows procedural. `set_pattern_step` edits the rhythm; both are journaled. `strike_face` auditions a face without touching history.
|
||||
|
||||
## Pieces
|
||||
|
||||
| Piece | What it does |
|
||||
|---|---|
|
||||
| `zhila-material` | decode (symphonia), downmix to mono, onset analysis, sixteen faces, ranks, waveform peaks, content-hash id |
|
||||
| `zhila-core` | `Core`, `AudioCore`, `AudioFace`, `MaterialSnapshot`; archive; pattern face/step commands; journal; engine sync |
|
||||
| `zhila-engine` | sample voices alongside procedural voices; a step plan that resolves a row's face to a core/face; rate-correct playback; 4 ms fades |
|
||||
| `zhila-daemon` | binary import after a text header, decoded in `spawn_blocking`; broadcast snapshot; new commands flow through the session |
|
||||
| `zhila-protocol` | core/face summaries, face references, import messages, pattern commands/events |
|
||||
| `ui` | quarry: import, core strip, waveform, face wall, audition, assign, pattern grid; the descent remains the nave |
|
||||
|
||||
## Verification
|
||||
|
||||
- `cargo test --workspace`: 10 core tests, 2 material tests, 5 protocol tests.
|
||||
- `cargo clippy --workspace --all-targets -- -D warnings` and `cargo fmt --check`.
|
||||
- `node scripts/material-smoke.mjs`: a live import of `fixtures/clicks-01.wav`, sixteen faces, a face laid on row 1, playback, non-zero meters, `face_struck`.
|
||||
- `npm --prefix ui run quarry-smoke`: the browser imports the fixture, selects a face, assigns it, plays, screenshots the quarry and the nave, checks 80 steps and 16 faces.
|
||||
- `npm --prefix ui run smoke`: the P0 regression still passes.
|
||||
|
||||
The screenshots are `preview-p1-quarry.png`, `preview-p1-nave.png` and the empty state `preview-p1-quarry-empty.png`.
|
||||
|
||||
## Limits
|
||||
|
||||
- In-memory archive and pattern; no project file, no save/load, no asset persistence. A daemon restart loses the material and edits.
|
||||
- No recording, no waveform trim, no manual cut/loop points. Seams are automatic.
|
||||
- No undo/redo for pattern edits; the journal is written but not exposed.
|
||||
- One face per pattern row; no per-step face, pitch or probability yet.
|
||||
- Formats are whatever symphonia decodes; WAV is the tested path.
|
||||
- The Tauri shell is still blocked on the system development packages (see `P0.md`).
|
||||
|
||||
## Next
|
||||
|
||||
P1b: a project folder with SQLite, content-addressed assets, autosave and reload, pattern-edit undo, recording, and waveform trim. Then the arrangement and the agent.
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# ZHILA — план проекта
|
||||
|
||||
Черновик v1.1 · 1 ноября 2026
|
||||
Статус: **P0 выполнен** (см. `P0.md`). Демо `reference/demo/` — референс, не кодовая база.
|
||||
Статус: **P0 и P1a выполнены** (см. `P0.md`, `P1.md`). Демо `reference/demo/` — референс, не кодовая база.
|
||||
|
||||
**Зафиксировано 1 ноября:** descent — основной вид; камень = паттерн; климат — на жилу (концепт погоды остаётся); платформа v1 — Linux; UI — Svelte 5. Журнал — §9.
|
||||
|
||||
|
||||
@@ -91,3 +91,58 @@ The browser sets `binaryType = "arraybuffer"` and reads a `DataView`. The frame
|
||||
- Project/asset commands.
|
||||
- Agent tools and MCP.
|
||||
- Per-client cursors or collaboration.
|
||||
|
||||
|
||||
## P1a additions
|
||||
|
||||
The protocol remains version 1; the additions are backward-compatible for the P0 surface (unknown fields are ignored).
|
||||
|
||||
### Cores
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "import_core",
|
||||
"id": 100,
|
||||
"name": "clicks-01.wav",
|
||||
"seal": "found"
|
||||
}
|
||||
```
|
||||
|
||||
The next binary frame from the client is the encoded audio. The daemon answers with:
|
||||
|
||||
```json
|
||||
{"type":"imported","id":100,"core":{
|
||||
"id":"core-f298c4956d","name":"clicks-01","seal":"found",
|
||||
"sample_rate":48000,"duration_sec":2.6,
|
||||
"faces":[{"index":0,"rank":9,"start_sec":0.0,"end_sec":0.14,"rms":0.14}],
|
||||
"peaks":[0.0,0.32,...]
|
||||
}}
|
||||
```
|
||||
|
||||
A snapshot follows and is broadcast to every client. `state.cores` carries the same summaries.
|
||||
|
||||
### Pattern rows
|
||||
|
||||
```json
|
||||
{"vein":"bass","face":{"core":"core-f298c4956d","face":10},"velocities":[112,0,...]}
|
||||
```
|
||||
|
||||
`face: null` means the procedural voice for that vein.
|
||||
|
||||
### Commands
|
||||
|
||||
| command | args | journal |
|
||||
|---|---|---|
|
||||
| `strike_face` | `core`, `face`, `velocity` 0..1 | no |
|
||||
| `set_pattern_step` | `row` 0..4, `step` 0..15, `velocity` 0..127 | yes |
|
||||
| `set_pattern_face` | `row`, `core`, `face` | yes |
|
||||
|
||||
Events: `pattern_step`, `pattern_face`, `face_struck`.
|
||||
|
||||
### Import semantics
|
||||
|
||||
- The import header must arrive immediately before the binary payload on the same connection.
|
||||
- The daemon decodes in a blocking task; the meter stream and other clients continue.
|
||||
- The archive id is a content hash prefix (`core-` + 10 hex); the same bytes get the same id.
|
||||
- The import is journaled on the session as `import_core` (journal-only; clients cannot send it as a command).
|
||||
- If the file cannot be decoded or is too short, the requester receives `error` with the import id.
|
||||
|
||||
Reference in New Issue
Block a user