Skip to content

Types

@barline/core is the public, zero-dependency package: types, the AST, and the pattern primitives. It imports nothing — no Tone, no React, no DOM types — so a song file’s vocabulary is small and stable. These are the types you’ll touch most.

import type {
Pattern, QueryContext, NoteEvent, NoteName, Note,
TimeWindow, Beats, Position, SongDocument, SectionContext,
} from "@barline/core";

The central contract. Three members; the rest of the system composes against it.

interface Pattern {
readonly id: string;
readonly length: Beats;
query(window: TimeWindow, ctx: QueryContext): NoteEvent[];
readonly source?: { readonly line: number }; // call-site, set by leaf constructors
}

query must be pure and deterministic — see Custom patterns.

Passed to every query. Two members matter to authors:

  • rng(seed: string): number — a seeded 0..1 draw. The only sanctioned randomness. Same seed → same number, so visualization matches playback.
  • flags — the live flag values (read here for sub-beat control; the bar-snapshot view is t.flags in a section body).

What query returns — one scheduled note:

interface NoteEvent {
note: MidiNote; // branded MIDI number
velocity: number; // 0..1
duration: Beats; // length in beats
offset: Beats; // start, relative to the window
}
  • Beats / Position are branded numbers — build them with beats(n) / position(n) / bars(n), don’t pass raw numbers where they’re expected.
  • TimeWindow is { start: Position; end: Position }.
  • NoteName is the string form ("F1", "Ab3", "C4"); Note is a NoteName | MidiNote. noteToMidi() / midiNote() convert.

The argument to a section body:

interface SectionContext {
readonly tracks: Record<string, Track>;
readonly flags: FlagValues; // snapshot at the bar boundary (quantized)
readonly bpm: number;
position(): Position; // position within the section, in beats
inherit(sectionName: string): void;
}

SongDocument is the versioned, serializable AST — the public schema that persists and syncs. It is a public API:

  • BARLINE_VERSION is the current schema version (currently 2).
  • Any change to the shape bumps barlineVersion and ships a migration. Fields are never repurposed.
  • Section bodies are stored as TypeScript source in sources; structure is a derived index for the editor, LLM, and visuals — not the source of truth.

You rarely construct a SongDocument by hand; the studio and runtime own it. But if you serialize or migrate songs, this is the contract to respect.

  1. core has zero runtime dependencies. If it can’t run in a bare Node REPL, it doesn’t belong in core.
  2. Patterns are pure. Randomness only via ctx.rng(seed).
  3. Transforms are free functions, not methods — your Pattern class never needs chaining methods.
  4. The tagged-template grammar stays minimal (x, . / ~, *N, [...], ${velocity}). New power goes through interpolation or helpers.
  5. SongDocument changes require a version bump + migration.