Skip to content

Session

The Session view is the studio’s clip-launching surface: a grid of scenes, one per section, that you fire live instead of following the linear timeline. Where the Arrange view plays song.arrange([...]) top to bottom, the Session view lets you loop any single song.section(...) on its own and jump between them by hand — quantized to the next bar so nothing falls out of time.

There is no separate scene data to author. Sections are the scenes. The same bodies the Arrange view dry-runs are the things you launch here; the only difference is when and how long they play.

  • One scene per section, in declaration order — every song.section(name, bars(n), ...) becomes a launchable cell. The scene’s bar length is the section’s own length.
  • The live scene, highlighted while it loops. This is the runtime’s getLoopSection() — the section currently looping in place of the arrangement.
  • A queued scene, shown as pending until the bar boundary. Launch a scene while playing and it doesn’t cut in mid-bar; the runtime holds it as getQueuedScene() and swaps at the next onBar, so the highlight moves the instant the loop turns over.
  • The same bar-clock playhead as every other view, ticking through the looping section.

A song to launch scenes from is just a normal song — each section is a cell:

import type { Song } from "@barline/runtime";
import { bars, beats, notes, r } from "@barline/core";
export const config = { bpm: 148, key: "F minor" };
export default function (song: Song) {
const kick = song.track({
name: "kick",
output: { kind: "synth", synth: { id: "kick909" } },
});
const bass = song.track({
name: "bass",
output: { kind: "synth", synth: { id: "bass" } },
});
// Each section is a scene you can launch from the Session grid.
song.section("verse", bars(4), () => {
kick.play(r`x . . . x . . . x . . . x . . .`);
bass.play(notes(["F1", null, "C2", null], beats(0.5)));
});
song.section("drop", bars(4), () => {
kick.play(r`x . x . x . x . x . x . x . x .`);
bass.play(notes(["F1", "F1", "C2", "Eb1"], beats(0.25)));
});
// The linear order — what Arrange plays, and what Session falls back to.
song.arrange([
{ section: "verse", repeat: 2 },
{ section: "drop", repeat: 4 },
]);
}

Like Arrange, the Session view is performance, not authoring — launching a scene never touches your code. It drives transport state that resets with the runtime:

  • Click a scene to launch it. While playing it queues to the next bar; while stopped it goes live immediately, so the next play() starts on that section instead of the arrangement.
  • Stop the scene (or launch null) to release back to the linear arrangement — the timeline picks up from song.arrange([...]) again at the next boundary.
  • Double-click a scene to jump the editor to that section’s code, the same hand-off the Arrange blocks make to the Clip detail panel.

To change what a scene plays — its pattern, length, or which tracks it touches — edit the matching song.section(...); the cell re-derives on the next compile. To change the fallback order, edit song.arrange(...).

The same scene launching is exposed to the MCP-connected AI through the transport tool: launchScene { section: string | null } queues a section as the live scene, quantized to the next bar, with null releasing back to the linear arrangement — identical semantics to clicking a cell. That lets the AI audition a single section in a loop while it edits, then drop back into the full arrangement when it’s done.