Skip to content

Instruments & outputs

Every track has exactly one output: the thing that turns its notes into sound. It’s pure data — a tagged union, discriminated by kind — so the runtime maps it to the actual voice. Four kinds: synth, sampler, soundfont, and midi.

import type { Song } from "@barline/runtime";
output: { kind: "synth", synth: { id: "kick", params: { volume: -12 } } }

synth.id names a built-in voice; synth.params is an optional Record<string, number | string> of per-instrument knobs (e.g. { volume: -12, note: 86 }). The nine drum/synth primitives:

idRole
kickThe four-on-the-floor anchor
hatClosed hi-hat tick
clapOff-beat backbeat clap
metalMetallic perc / ride hit
bassSub / rumble bass
leadMono lead line
kick909Punchier 909-style kick
acid303Squelchy 303-style mono
stabChord/organ stab

Six voices are FAUST-compiled and 16-voice polyphonic — referenced by the same synth.id. If their wasm artifacts aren’t registered they fall back to a Tone PolySynth, so playback never breaks:

idRole
subtractiveClassic subtractive poly
supersawDetuned supersaw stack
fmFM operator voice
pluckKarplus-style pluck
acid303 diode-ladder mono
hardkickDistorted hard-techno kick
const lead = song.track({
name: "lead",
output: { kind: "synth", synth: { id: "supersaw", params: { volume: -8 } } },
});
output: {
kind: "sampler",
samples: { "C3": "/api/uploads/<id>", "C4": "/api/uploads/<id>" },
}

samples is a note → URL map. The runtime pitches each zone to fill the gaps, so a few well-placed root notes cover a whole range. URLs are uploaded assets at /api/uploads/<id> — see the Library for how to upload them.

output: { kind: "soundfont", font: "<id>", program: 0 }

font is an uploaded .sf2/.sf3 (an /api/uploads/<id>); program is a General MIDI melodic program number, 0..127 (0 = acoustic grand piano). Upload soundfonts the same way as samples — both live in the unified Library.

output: { kind: "midi", channel: 1, port: "IAC Driver Bus 1" }

Sends note events out over Web MIDI on channel (1..16). port is optional — omit it to use the default output port.

A song file is export const config plus a default function the runtime calls with a live song. Here are three tracks across kinds:

import type { Song } from "@barline/runtime";
import { fx, notes, bars, beats, r } from "@barline/core";
export const config = { bpm: 132, key: "A minor" };
export default function (song: Song) {
const kick = song.track({
name: "kick",
output: { kind: "synth", synth: { id: "kick909" } },
effects: [fx.distortion({ drive: 0.3, wet: 0.5 })],
});
const keys = song.track({
name: "keys",
output: { kind: "soundfont", font: "<id>", program: 4 }, // electric piano
});
const drums = song.track({
name: "drums",
output: {
kind: "sampler",
samples: { "C2": "/api/uploads/<id>", "D2": "/api/uploads/<id>" },
},
});
song.section("intro", bars(4), () => {
kick.play(r`x . . . x . . . x . . . x . . .`);
drums.play(r`. . x . . . x .`);
keys.play(notes(["C3", "Eb3", "G3"], beats(1)));
});
song.arrange([{ section: "intro" }]);
}

Once an instrument is making sound, shape it with the track’s effects chain.