Rendering: hearing the arrangement
Rendering is the change of state from the arrangement to sound. For a
concrete element it is exactly the walk you ran on the last page, plus a
transport: flatten the tree into a timeline of absolute beats, hand that
timeline to a Playhead on a clock, play. Nothing else — no second sequencer
is added; the one the client already has is reused
(Timelines and the playhead).
Play the piece
playhead = song.render(server, session.clock)
Eight beats: the take at 0 and 4, the bass walking under everything, the
melody entering at beat 2. What render did, in order:
to_timeline(song)— flatten to absolute beats. Contained generators (the bassPbind) are bounced in this pass: the pattern runs offline on a throwaway clock and its events are recorded at their logical beats. ASequencethat wraps a plain list of elements is laid out successively, each after the previous one's duration; an abstract element contributes context and no event.Playhead(timeline, clock, destination)— a transport over that timeline.playhead.play(at=0.0)— the playhead scans forward on the clock, playing each item onto the server at its exact logical beat.
The returned playhead is the transport, and you can drive it directly:
playhead.stop() # halt the scan
playhead = song.render(server, session.clock, at=4.0) # from beat 4
print(playhead.position()) # the song position, in beats
stop halts the scheduling — anything already sounding finishes its own
release. Stopping a playhead is not a panic button, and nothing here needs
one: every voice in this piece is an event with a length, so it ends when its
event does.
Let it run out, or stop it:
playhead.stop()
Rendering always re-reads the tree
There is no cached score. Every render re-flattens the tree as it now
stands, so an edit in code is heard on the very next play:
member = drums.handles[1]
drums.move(member, 5.0) # push the second take late
playhead = song.render(server, session.clock) # ... and it plays at beat 5
playhead.stop()
drums.move(member, 4.0) # back where it was
This is the property the whole editor rests on. A dragged clip will do exactly
what that move did — write a placement — and pressing play will do exactly
what render does — re-read the composition. The GUI adds no path of its own.
The same verb, offline
render takes a destination, and everything about it is
destination-agnostic: give it an offline session's server and clock and the
identical flattening accumulates a score instead of sounding —
sample-identical to the live playback, because it is the same walk feeding the
same engine. The bounce page closes the tutorial with it.
One more dispatch hides in the same verb: a logical group (the other
grouping kind) does not flatten at all — render sends it to the server as a
signal-graph definition instead. That is the logical page; the
piece does not need it yet.
You now have the whole loop in code: build elements, place them, play them, edit, play again. Time to put it on screen.