From 6a09ad266d8755f699600c5310f83c6f992fefef Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 8 Sep 2026 09:45:08 -0400 Subject: [PATCH] =?UTF-8?q?notespatial:=20the=20composition=20model=20?= =?UTF-8?q?=E2=80=94=20dwelling=20turns=201:41=20into=208=E2=80=9312=20min?= =?UTF-8?q?utes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTESPATIAL-COMPOSITION.md reads the movement subtitles as what they already are — spatial verbs (enter, answer, turn, scatter, converge, rest) — and models the live form: each movement a holdable region looping 2–6 passes with re-seated sound, fermatas at the doors while machines re-seat, the score's own level arc as the macro shape. The piece grows the mechanic: hold captures the current movement and loops it at its far door (space toggles, enter jumps to the next door, state file carries both for the conductor), so the length of a night is a performance decision. Claude-Session: https://claude.ai/code/session_01Xju7GpzP6o2JwxtqfKqFiG --- fedac/native/pieces/notespatial.mjs | 44 ++++++++-- .../NOTESPATIAL-COMPOSITION.md | 81 +++++++++++++++++++ 2 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 grants/culturehub-la-2026/NOTESPATIAL-COMPOSITION.md diff --git a/fedac/native/pieces/notespatial.mjs b/fedac/native/pieces/notespatial.mjs index 8cdae214b4..920eb361c6 100644 --- a/fedac/native/pieces/notespatial.mjs +++ b/fedac/native/pieces/notespatial.mjs @@ -5,11 +5,18 @@ // // Conducting happens through /pieces/notespatial-state.txt, polled ~1Hz // and PUT-able over LAN by any machine (this is the leader protocol): -// { "lanes": [0,3,7] | "all", "rate": 1.0, "seek": ? } +// { "lanes": [0,3,7] | "all", "rate": 1.0, "seek": ?, +// "hold": true|false } // rate changes rebase the clock so time stays continuous — speed and // trajectory move live without a click. Lanes not owned still draw as // ghosts so every screen shows the whole field. // +// The 1:41 source expands to the 8–12 minute performance by DWELLING: +// hold loops the current movement until released (each pass can differ — +// assignments walk, rate bends), release lets the piece run on. On-device +// conducting for solo play: space toggles hold, enter jumps to the next +// movement's door. +// // Score selection: `notespatial:name` colon param, else // /pieces/notespatial-current.txt. Loops by default (installation form); // esc leaves. @@ -26,6 +33,8 @@ let err = null; let laneEvents = []; // per lane: sorted events with cursor let owned = null; // Set of lane indices, or null = all let rate = 1; +let hold = false; // dwell: loop the current movement until released +let heldMv = null; // the movement captured when hold engaged let base = 0; // wall ms at t=0 (rebased on rate change / seek / loop) let stateStamp = 0; let dmxLast = [-1, -1, -1]; @@ -63,6 +72,7 @@ function applyState(system) { rebase(st.seek); for (const l of laneEvents) l.cursor = 0; } + if (typeof st.hold === "boolean") hold = st.hold; } catch (_) { /* half-written PUT — next poll wins */ } } @@ -82,10 +92,24 @@ function sim({ sound, system }) { if (now() - stateStamp > 1000) { stateStamp = now(); applyState(system); } - if (t > score.dur + 2) { // loop — the installation form - rebase(-1); - for (const l of laneEvents) l.cursor = 0; - return; + // Dwelling: a held movement loops at its far door — this is how 1:41 + // becomes 8–12 minutes (see grants/culturehub-la-2026/ + // NOTESPATIAL-COMPOSITION.md). Cursors rewind to zero; the fire loop + // walks silently past everything before the door. + if (hold) { + if (!heldMv && t >= 0) heldMv = (score.movements || []).find(m => t >= m.t0 && t < m.t1) || null; + if (heldMv && t >= heldMv.t1) { + rebase(heldMv.t0); + for (const l of laneEvents) l.cursor = 0; + return; + } + } else { + heldMv = null; + if (t > score.dur + 2) { // full run loops — the installation form + rebase(-1); + for (const l of laneEvents) l.cursor = 0; + return; + } } for (let i = 0; i < laneEvents.length; i++) { @@ -158,7 +182,8 @@ function paint({ wipe, ink, box, write, screen, system }) { ink(200, 200, 210); write((mv ? mv.name + " — " + mv.sub : "…") + " " + (t < 0 ? "-" : "") + Math.abs(t).toFixed(1) + "s" + - (rate !== 1 ? " ×" + rate.toFixed(2) : ""), + (rate !== 1 ? " ×" + rate.toFixed(2) : "") + + (hold ? " [HOLD]" : ""), { x: 4, y: H - 14, size: 1 }); if (score.rotation && t >= 0) { const u = Math.min(1, t / score.dur) * (score.rotation.length - 1); @@ -169,9 +194,14 @@ function paint({ wipe, ink, box, write, screen, system }) { function act({ event: e, system }) { if (e.is("keyboard:down:escape")) system?.jump?.("prompt"); + if (e.is("keyboard:down:space")) { hold = !hold; heldMv = null; } if (e.is("keyboard:down:enter") || e.is("keyboard:down:return")) { - rebase(-1); + // jump to the next movement door (wraps to the top) + const t = scoreTime(); + const next = (score?.movements || []).find(m => m.t0 > t + 0.05); + rebase(next ? next.t0 : -1); for (const l of laneEvents) l.cursor = 0; + heldMv = null; } } diff --git a/grants/culturehub-la-2026/NOTESPATIAL-COMPOSITION.md b/grants/culturehub-la-2026/NOTESPATIAL-COMPOSITION.md new file mode 100644 index 0000000000..9ec6a39f89 --- /dev/null +++ b/grants/culturehub-la-2026/NOTESPATIAL-COMPOSITION.md @@ -0,0 +1,81 @@ +# Composition Model — *Note(s)pat(ial) Native* + +> How the fixed 1:41 of *Special Sign* becomes an 8–12 minute live +> spatial performance on six salvaged laptops. Companion to the runtime +> (`fedac/native/pieces/notespatial.mjs`, score data +> `special-sign.nsscore`). A model to rehearse against, not a playback +> plan — the conductor's holds decide each night's true length. + +**Source:** *Special Sign* release master, 101.4s, 13 lanes, six +movements, spatial-rotation ribbon (peaks at 16.9s, 27.8s, 47.7s, +70.6s, 97.4s — one per movement door, roughly). +**Target:** 8–12 minutes. **Forces:** six machines in a ring, one +conductor (seventh machine or one of the six), audience inside. + +--- + +## The core reading + +The expansion mechanic is **dwelling, not stretching**: each movement +is a holdable region that loops until released. What keeps a held +movement alive is that *every pass re-seats the sound* — the movement +subtitles in the score data are already spatial verbs, and the live +version performs them literally with machines instead of a stereo +field: + +| Movement | Source | Sub (verbatim) | Spatial choreography | Passes | Dwell | +|---|---|---|---|---|---| +| I · Assembly | 18.9s · lvl .38 | bodies enter high to low | **Enter.** Pass 1: the whole score sounds from ONE machine — the piece begins as a point. Each pass spreads lanes outward (1 → 3 → 6 machines alive). | 3–4 | ~60–90s | +| II · Signal | 20.6s · lvl .58 | question and answer | **Antiphony.** Lanes split into question/answer halves seated across the ring; the question walks one seat clockwise per pass. | 3–4 | ~60–90s | +| III · Super-Spin | 16.0s · lvl 1.0 | eight listener-relative turns | **The showpiece.** Assignments orbit the ring — one seat per bar, doubling each pass until the field blurs. Longest dwell; the audience sits inside the turn. | 4–6 | ~90–120s | +| IV · Constellation | 19.8s · lvl .82 | counterpoint and sine garden | **Scatter.** Maximal dispersion: one lane per machine, extras floating; rate bends down (×0.7–0.85) so the garden opens. Densest material (echo 121, pads 70/71) carries slowness well. | 3–4 | ~75–100s | +| V · Home Sign | 20.0s · lvl .62 | G7 resolves to C major | **Converge.** Reverse of Assembly: each pass pulls lanes toward one home machine; the resolution lands with every voice in one body. | 2–3 | ~45–70s | +| VI · Run-Down | 6.0s · lvl .28 | the physical field reaches rest | **Rest.** Once, plain, from the home machine alone. Then room silence. | 1 | ~10s | + +Passes alone total ≈ 5.5–8 minutes. The remaining time is **fermatas at +the movement doors**: the conductor may hold a door — the last sonority +loops thin as a drone — while the machines re-seat for the next +choreography. Five doors × 10–30s of held breath lands the night inside +8–12 minutes without ever slowing the material into sludge. + +## Per-pass variation (why repetition reads as composition) + +1. **Seating** — the choreography above; no two passes share an + assignment map. +2. **Level** — first pass of each movement enters at ~0.7 gain, opens to + full; the score's own movement `level` arc (.38 → 1.0 → .28) stays + the macro shape of the night. +3. **Rate** — bounded per movement: I rubato (0.85–1), III may + accelerate through its final pass (→ ~1.15), IV slow (0.7–0.85), + VI dying (~0.8). Rate rebases continuously — no clicks. +4. **The rotation ribbon** — the composed spin envelope from the + recording drives default orbit speed; the conductor overrides it + live. + +## Roles of the lane families in the room + +- **bass + boom** — the floor; prefers a single seat (subwoofer-ish + laptop), moves rarely. +- **pad-l / pad-r, hat-l / hat-r, nose-l / nose-r** — composed stereo + pairs → **opposite seats**; their l/r identity becomes ring geometry. +- **melody + jeffrey vowels** — the sign itself; the voice the + conductor moves by hand, the one the audience tracks. +- **echo + gong** — the resonant halo; happiest dispersed. +- **air** — weather; wherever nothing else is. + +## Runtime hooks (already live) + +`/pieces/notespatial-state.txt` (PUT over LAN, polled ~1Hz): +`lanes` (assignment), `rate`, `seek`, `hold`. On-device: space toggles +hold, enter jumps to the next movement door. Solo rehearsal = one box +playing all lanes, conducting itself. + +## Honest risks + +1. **~1s state-poll skew** is the current sync floor — fine for + assignments and holds, too coarse for beat-locked antiphony. If II's + Q&A needs tighter handoff, the UDP lane is the upgrade path. +2. **Dwell fatigue** — a held movement must earn its passes; if a + choreography reads static by pass 3, cut the dwell, not the tempo. +3. **Six-box audio latency spread** is unmeasured until the LA fleet + stages; the model assumes seat-level, not sample-level, coherence. -- 2.51.2