Editing on screen: the loop closed
This page is the working rhythm the whole layer was built for. It has three
beats: gesture in the window, editor.poll() to fold the gesture into
the arrangement, editor.play() to hear the piece as it now stands.
Everything else here is detail on those three.
The transport, from code
The editor owns a transport over the piece. Give it a destination and a clock once, and drive it from the interpreter:
editor.locate(0.0) # the cursor waits at the top
editor.play(server, session.clock) # render and play from the cursor
The piece plays, and a line sweeps the clips — the playhead, anchored to the engine's own sample clock, so it tracks the audio rather than a guess. Pause and resume:
editor.pause() # halt here; the position stays
editor.play() # resume from where we paused (destination remembered)
play is always a fresh render from the transport's position — the tree
is re-flattened, so it plays the composition as it now stands. pause
stops the scheduling and keeps the position; what is already sounding finishes
by itself (a transport is not a panic button — every voice in this piece ends
with its clip). And:
editor.stop() # pause + back to the top
editor.locate(4.0) # seek: put the transport at beat 4
locate while stopped just moves the cursor — the thin static line the
lanes draw; while playing, it re-renders from the new position (so a seek
also picks up any pending edit). Clicking a lane's ruler, or its empty
space, is the same locate — try it: click somewhere in the empty part of a
lane and watch the cursor move. That click reaches the data the same way
every other gesture does, which is the next section.
Two playheads, to be precise, and telling them apart matters: the sweeping
line is anchored to the engine clock and moves with the audio; the cursor
is where a stopped transport sits (where the next play starts). You never
set either directly — the transport calls do.
The rhythm: gesture → poll() → play()
The window accumulates your gestures as events; nothing touches the
arrangement until you ask. editor.poll() drains everything pending and applies it — one
call, no loop. Run the whole cycle once: drag the second drums clip
somewhere later in its lane (it snaps to the half-beat grid, your quant),
then:
editor.poll() # -> True: the composition changed
print(drums.members) # the second take's offset is where you dropped it
The drag became a Group.move on the member handle — the same edit you made
in code two pages ago, arriving through the window. Hear it:
editor.play(at=0.0)
Resize works the same way: drag a clip's edge (the outer few pixels) and
its placement gets a dur — the trim rule from the grouping page, by hand.
Pull the second take's right edge in to one beat, then:
editor.poll()
print(drums.members) # (offset, 1.0, <Buffer>) — trimmed
print(take.to_event()["dur"]) # 2.0 — the element, untouched as ever
editor.play(at=0.0) # you hear one beat of it
That is the entire loop: the graphic edits the data, the sound plays the data, and the three never disagree. A few mechanical notes:
poll()returns whether the composition changed, and it is safe to call any time — unknown messages are ignored, a closed window just marks itself.- Call it from the interpreter (or your own loop), never from the clock thread — the same golden rule as every routine.
- The edit-back arrives in timeline samples; the editor converts to beats and
snaps to
quant— the same grid the lane snapped the drag to, so the round trip is exact. A drag that moved less than half a sample is not an edit. - Only what actually changed is written: a plain drag carries the clip's length along unchanged, and the editor is careful not to re-snap that — snapping a length that was never touched would silently shorten the element.
dirty, and the follow variant
An edit does not interrupt what is sounding. It changes the arrangement and marks the editor:
print(editor.dirty) # True after an applied edit, until the next render
The next transport action — a play, a resume, a seek — re-reads the composition, because rendering always re-flattens. If you want the piece to re-schedule itself on every edit instead, turn on follow:
editor.follow = True # the live editor: poll() now re-renders on each edit
With follow on, the same rhythm loses its third step: gesture, poll(),
and it already plays from the playhead's position. The semantics are honest —
re-schedule from here, not a sample-exact splice: a synth already sounding
keeps sounding, and what changes is what has not been scheduled yet.
editor.follow = False # back to the explicit rhythm for the next pages
The piece ends where the arrangement says
Let the piece play to the end: the playhead reaches editor.extent() — read
from the tree, remember — and the scan simply runs out of events. Drag a clip
past the old end, poll(), play(): the piece is longer now, and the
playhead sweeps to the new end. Nothing was configured; the length is not a
setting, it is a fact about the elements.
Undo, by the way, is a placement again: you watched every edit land as one
(drums.members), so putting one back is a move — from code or by dragging
it home.