Record
The Record panel captures what you play. It has two kinds of take: a MIDI take (the notes you played, quantized to the section’s grid) and an audio take (mic/line input or an external-instrument return captured on exact sample frames). They commit in different directions: MIDI becomes a pattern you can edit; audio becomes immutable managed assets plus readable clip/take source.
What it captures
Section titled “What it captures”- MIDI take: the notes played over the transport while it loops, kept as played-note events you can fold into a pattern.
- Audio take: mono or stereo input captured by an AudioWorklet at scheduled sample-frame start/stop boundaries. Count-in, pre-roll, destination, calibrated/manual timing correction, safe monitoring, and punch length are explicit.
- TakeStack: a loop punch split into immutable 24-bit pass assets. Preview the lanes, choose the source pass, then commit all pass ids plus the selected range to a small TypeScript module.
- External return print: the same exact-frame path fed by a calibrated
logical return route from an
externaloutput track.
Set up audio first
Section titled “Set up audio first”Open Audio Setup before a serious take. It keeps machine-specific facts out of the song while giving the recorder one trustworthy clock and route model:
- choose the input and optional output device;
- probe the real channel count, sample rates, browser processing, and reported input/base/output latency;
- run the five-click acoustic loopback calibration and inspect confidence;
- apply a visible manual trim when measured hardware says it is needed;
- name a logical return such as
external-1.
Input monitoring unlocks only after you approve a measured headphone route with
at least 55% calibration confidence and an effective round trip at or below
35 ms. Bluetooth/headset routes stay blocked because their latency is high and
variable. The local v3 profile records which physical input resolved behind
default, the current output-route generation, and a verified physical output
identity. Connecting, removing, changing devices, switching the OS default, or
being unable to verify the output revokes approval until that physical path is
inspected, calibrated, and approved again. A physical device id is never
written into song code.
Recording MIDI needs a section with bars
Section titled “Recording MIDI needs a section with bars”MIDI capture rides the bar clock, so it needs somewhere to land: a section with bars and a running transport. A stopped transport disables the control — there is no grid to quantize against. Give the take a home first:
import type { Song } from "@barline/runtime";import { bars, beats, r } from "@barline/core";
export const config = { bpm: 128, key: "A minor" };
export default function (song: Song) { const lead = song.track({ name: "lead", output: { kind: "synth", synth: { id: "lead" } }, });
song.section("verse", bars(4), () => { // play into this section while it loops — the take quantizes here lead.play(r`x . x . x . x .`); });
song.arrange([{ section: "verse", repeat: 2 }]);}Loop the section, arm the track, and play. The captured notes come back as a
played-note take you can hand-edit into a notes([...]) literal in the section,
the same write-back surface the Clip detail piano roll edits.
Commit a jam straight to the arrangement
Section titled “Commit a jam straight to the arrangement”A finished MIDI take writes a recording-N.ts pattern file you own as code. Pick
a track and hit → arrangement and the studio commits it as real structure in
one edit: it adds the import, inserts a named section that plays the take —
import { recording1 } from "./recording-1";
song.section("jam-1", bars(4), (t) => { t.tracks["bass"]?.play(recording1);});— and appends { section: "jam-1" } to song.arrange([...]). The take is now an
addressable part of the song (it appears in the Arrange and
Session views), not a loose file to hand-wire. The edit goes
through the same Yjs path as every keystroke, so the arrangement array is
regenerated cleanly (never comma-spliced) and the result always parses.
Using an audio take
Section titled “Using an audio take”The local stage starts heartbeating while the take is armed, including count-in and pre-roll before the first audio chunk. Canceling an arm invalidates that specific attempt, so a late permission or count-in completion from an older take cannot attach itself to a newer recording.
On commit, an audio take is pinned in the Library and inserted
as a clip in the section, track, and beat selected before arming. It lives at
/api/uploads/<id> like any other immutable upload. You can still wire or
rearrange it manually in two ways.
As a clip — drop the take onto a track as an audio block with
track.clip({ url }), called inside a section body:
export default function (song: Song) { const recording = "/api/uploads/<id>"; // the take's url from your Library
const vox = song.track({ name: "vox", output: { kind: "synth", synth: { id: "lead" } }, });
song.section("intro", bars(8), () => { vox.clip({ url: recording, at: beats(0), beats: beats(16) }); });
song.arrange([{ section: "intro" }]);}clip() takes the same Clip options as any audio block —
at, beats, loop, gain, and stretch / semitones if you want the take
time-stretched or pitch-shifted to fit. at and beats are Beats, so wrap
bare numbers with beats() or bars().
As a sampler — map the take to a note and play it back pitched across the keyboard:
export default function (song: Song) { const chop = song.track({ name: "chop", output: { kind: "sampler", samples: { C3: "/api/uploads/<id>" }, // the recording-N take }, });
song.section("loop", bars(2), () => { chop.play(r`x . . . x . x .`); });
song.arrange([{ section: "loop", repeat: 4 }]);}Either way the take is a normal upload from there — content-addressed, served immutably, and shared with every other sample in your Library. Record captures the performance; the rest of the studio treats it like any other clip.
Punch and loop passes
Section titled “Punch and loop passes”Set Punch length to a number of bars to schedule an exact start and stop on the shared audio clock. Set Loop passes above one to record the same window repeatedly. The worklet marks pass boundaries on sample frames rather than splitting a timer-based recording afterward. Multi-pass punches must start on a bar boundary; Barline loops that arrangement range for every pass instead of continuing into later bars.
After the loop, TakeStack shows one preview lane per pass. Committing pass 2, for example:
- uploads and pins every pass as its own immutable managed asset;
- writes
take-stack-<id>.tswith destination, pass asset ids, andcomp.pass: 1; - atomically imports the module’s resolved asset, source range, and musical
duration into the audible
track.clip()region.
Changing comp.pass in the generated module changes playback without copying or
overwriting another performance. Unequal recovered pass lengths derive their
own end and clip duration from the selected pass. The current UI chooses whole
passes; arbitrary word-by-word comp painting remains outside this bounded
tracking workflow.
Print an external instrument
Section titled “Print an external instrument”Define the portable intent in code:
const hardware = song.track({ name: "hardware", output: { kind: "external", channel: 1, port: "USB MIDI Interface", returnRoute: "external-1", },});Bind and calibrate external-1 in Audio Setup, then choose External return
print in Record. Barline sends timestamped MIDI, includes measured return
latency in graph compensation, and captures the return into a managed audio
take. Once printed, that hardware performance can be exported or source-frozen
like any internal track.
Recovery
Section titled “Recovery”Chunks are staged in IndexedDB before upload. Device loss, processor failure, navigation, storage pressure, upload interruption, or source-document drift preserves the local recording for recovery instead of silently discarding it. The active capture and its finalizer heartbeat an owner lease; another tab ignores live work and atomically claims only interrupted or expired stages. Manual stop, scheduled punch completion, cancel, and failure share one bounded finalizer. Multipart upload resumes from completed parts, failed publication releases assets that never entered source, and source insertion happens only while the original song document is still current.