3.0 KiB
P2 · arrangement, render, agent
P2 turns the loop into a thing that can be played, rendered and performed with an agent. It is delivered in three slices.
P2a · the arrangement
Four 2-bar sections (A–D) form an 8-bar loop. Each section is a full Pattern (5 rows × 16 steps, with per-row faces). The engine walks 64 steps, picks the section and the pattern step, and triggers the section's rows; the bar clock runs 1–8. The state carries arrangement: [Pattern; 4], active_section, and pattern = the active section for the editors.
Commands: select_section { index }, clear_section { index }, copy_section { from, to }, event arrangement { sections, active }. Pattern edits apply to the active section. Select is persisted but not undoable; clear/copy push history. Sections, the active index and their history are stored in SQLite (arrangement, active_section columns, migrated with ensure_column) and restored on load.
Surface: the quarry gets a section strip (A–D with note counts, copy →, clear); the pattern grid edits the active section. The descent draws the 64-step road, bar labels 01–08, section seams and letters A–D, with the active section emphasized; the playhead follows the 8-bar clock.
Verification:
cargo test -p zhila-core arrangement: sections independent, select/copy/clear, undo, material carries four sections, loop_bars = 8.node scripts/persistence-smoke.mjs: imports, edits section B, copies A→C, cuts, restarts, verifies the arrangement, active B, B's step 0 = 0, C = A, and undo/redo.npm --prefix ui run arrangement-smoke: four sections, select B, edit a step, switch to the nave, play, screenshotpreview-p2-arrangement.png.
Limits: exactly four sections of two bars; no insert/reorder/length; select is not undoable; no per-section mute.
P2b · render
zhila_engine::render_arrangement(RenderRequest) drives the same AudioState offline: no cpal device, the same voices, sample faces, gains, master and the 8-bar sections, plus a 1.5 s tail. Command::render is intercepted by the host, rendered in spawn_blocking, written with hound as 48 kHz stereo 32-bit float to <project>/renders/<project>-<ms>.wav, and announced with Event::rendered { path, duration_sec }; the command result is the unchanged state. Render is not a project edit and does not push history.
Surface: render in the top rail; the wire logs the send and the rendered path; the button's title keeps the last path.
Verification: cargo test -p zhila-engine (non-silent 16.5 s buffer, correct frame count); node scripts/render-smoke.mjs (status 16.5 s, peak 0.442, 6.3 MB, float 48 kHz stereo, event duration matches); npm --prefix ui run render-ui-smoke (button → wire path, screenshot preview-p2-render.png).
Limits: WAV only, stereo float, fixed 48 kHz and 1.5 s tail; no stems, normalisation or section ranges.
P2c · agent (next)
An in-app LLM agent on the same verbs, with state/sound context and approval for project edits.