Skip to content

Nebelwand

Nebelwand is a hard-techno feature track in F minor at 148 BPM — and a single TypeScript file. It’s the song you’ll see when you open barline.dev, and it exercises a wide slice of the API: a custom Pattern class, a sidechain pump, two send buses, eleven of the built-in effects, several synth instruments, every combinator, three flag kinds, and automation throughout. Nothing here sets master effects, so it runs through the default master chain — glue → tilt → maximizer. This page walks its shape; the file itself is the canonical reference.

song.arrange([
{ section: "intro" }, // 16 ▂ a filtered heartbeat
{ section: "build1" }, // 16 ▄ inherits intro, layers up
{ section: "drop1", repeat: 2 }, // 32 ▇ the full machine
{ section: "breakdown" }, // 16 ▃ kick gone, harmony forward
{ section: "build2" }, // 16 ▆ a rising wall built by a for-loop
{ section: "drop2", repeat: 2 }, // 32 █ new bass engine + acid
{ section: "peak", repeat: 2 }, // 32 █ everything, plus chords
{ section: "drop1" }, // 16 ▇ one last lap
{ section: "outro" }, // 16 ▂ the machine winds down
]);

Three flags, one per kind, are the live controls:

song.flag("ratchet", { kind: "pad", note: 36, mode: "toggle" }); // hi-hat ratchet rolls
song.flag("drive", { kind: "cc", cc: 1, range: [0, 1], smooth: 0.4 }); // mod wheel → energy
song.flag("doubleKick", { kind: "constant", value: false }); // flip in code for 8th-note kicks

Two send buses give every track a shared space and echo instead of a reverb per track:

song.bus("space", { effects: [fx.reverb({ decay: 6, wet: 1 }), fx.eq3({ low: -10 })], volume: -6 });
song.bus("echo", {
effects: [fx.pingpong({ time: 0.75, feedback: 0.45, wet: 1 }), fx.filter({ frequency: 6500 })],
volume: -8,
});

Every track names a built-in synth instrument by id — kick, bass, hat, clap, metal, lead. The bass lives in the kick’s shadow via a sidechain:

const rumble = song.track({
name: "rumble",
output: { kind: "synth", synth: { id: "bass", params: { volume: -6 } } },
effects: [
fx.filter({ frequency: 700, type: "lowpass", q: 1.2 }),
fx.sidechain({ source: "kick", amount: 0.8, release: 0.12 }), // the pump
fx.compressor({ threshold: -20, ratio: 5 }),
],
});

RatchetPattern is the file’s showcase of “Pattern is an open interface” — it wraps offbeat hats and probabilistically explodes hits into rolls, reading the drive flag live at query time and seeding ctx.rng per event so the piano roll matches playback. The full class is in Custom patterns.

const ratchetHats = new RatchetPattern(offHats, {
probability: 0.2, divisions: 3, boostFlag: "drive", // knob up → more rolls, mid-bar
});

A few techniques worth lifting:

inherit to layer. build1 re-applies the whole intro, then adds hats and a bass with an opening filter sweep:

song.section("build1", bars(16), (t) => {
t.inherit("intro");
hats.play(offHats).gain(0.7);
hats.automate("gain", ramp(0.4, 1, bars(16)));
rumble.automate("cutoff", sweep(250, 2500, bars(16), "exp")); // the classic opening filter
clap.at(bars(8)).play(backbeat({ velocity: 0.6 })); // clap only from bar 8 of the section
});

Flags decide the details. drop1 reads the drive snapshot to scale gain and gate a percussion layer, swaps the hats on the ratchet pad, and routes the stabs through their own delay with .through():

song.section("drop1", bars(16), (t) => {
const drive = Number(t.flags.drive ?? 0);
kick.play(straightKick);
hats.play(t.flags.ratchet === true ? ratchetHats : tickHats).gain(0.85);
stab.play(stabsA).gain(0.5 + 0.3 * drive)
.through(fx.delay({ time: 1.5, feedback: 0.3, wet: 0.25 })); // delay colors only the stabs
if (drive > 0.5) perc.play(scatter).gain(drive * 0.8); // earn the knob
if (Math.floor(t.position() / 4) === 15) hats.play(fast(2, crescRoll)).gain(0.5); // turnaround
});

A wall built by a loop. build2 is the strongest “it’s real TypeScript” moment — a for loop stacks accelerating roll layers, each at(bars(n)) origin looping to the section’s end so later layers pile on earlier ones:

song.section("build2", bars(16), (t) => {
kick.play(at([0], 16)).gain(0.8); // one downbeat per bar — a pulse, not a groove
rumble.automate("cutoff", sweep(400, 6000, bars(16), "exp"));
for (let entry = 8; entry < 16; entry += 2) {
clap.at(bars(entry))
.play(entry >= 14 ? fast(2, crescRoll) : crescRoll) // last layers double speed
.gain(0.3 + (entry - 8) * 0.08);
}
if (Math.floor(t.position() / 4) >= 12) perc.play(scatterRolls).gain(0.6); // chaos for the run-in
});

Per-bar kick logic. drop2 chooses the kick pattern per bar from a flag and the bar index:

const kickThisBar =
t.flags.doubleKick === true ? doubleKick // 8th-note hammer mode
: Math.floor(t.position() / 4) % 8 === 7 ? fillKick // every 8th bar gets a 32nd flam
: straightKick;
kick.play(kickThisBar);

That’s the tour. Every line in apps/studio/src/example/project.ts is annotated in-file — open it next to the studio and edit while it plays. When the track sounds right, bounce it down and check the levels in Export & loudness. For smaller reusable fragments, see Recipes.