Skip to content

Recipes

Short, self-contained fragments drawn from Nebelwand. Each works inside export default function (song: Song) { … }.

import {
r, at, grid, euclidean, generate, notes, stack,
fast, beats, bars, ramp, sweep, fx,
} from "@barline/core";

The defining sound of techno: the bass ducks every time the kick hits. Put a sidechain keyed off the kick in the bass track’s chain — binding is lazy, so declaration order doesn’t matter.

const kick = song.track({
name: "kick",
output: { kind: "synth", synth: { id: "kick" } },
});
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
],
});
song.section("loop", bars(1), () => {
kick.play(r`x . . . x . . . x . . . x . . .`);
rumble.play(notes(["F1", null, "F1", "Ab1"], beats(0.5))).gain(0.9);
});

Raise amount toward 1 for a harder pump; lengthen release for a slower recovery.

A chord stab is one euclidean rhythm stacked across the chord’s notes. The helper keeps it one line per chord.

function chordStabs(hits: number, steps: number, chord: readonly string[]) {
return stack(...chord.map((note) => euclidean(hits, steps, { note, velocity: 0.85 })));
}
const stab = song.track({
name: "stab",
output: { kind: "synth", synth: { id: "lead", params: { volume: -12 } } },
effects: [fx.filter({ frequency: 2200, type: "bandpass", q: 1.5 })],
});
const stabsA = chordStabs(3, 16, ["F3", "Ab3", "C4"]); // F minor triad, sparse
const stabsB = chordStabs(5, 16, ["F3", "Ab3", "C4", "Eb4"]); // Fm7, denser at the peak
song.section("drop", bars(1), () => {
stab.play(stabsA).gain(0.6)
.through(fx.delay({ time: 1.5, feedback: 0.3, wet: 0.25 })); // delay only on the stabs
});

Change hits to redistribute the same notes over a busier or sparser grid.

A crescendo: velocity climbs across the bar, and rolls accelerate as the build peaks. generate computes the velocity ramp; a for loop stacks accelerating layers.

const crescRoll = generate(16, (i) => ({
hit: true,
velocity: Math.min(1, 0.25 + i * 0.05), // velocity climbs across the bar
}));
const clap = song.track({
name: "clap",
output: { kind: "synth", synth: { id: "clap" } },
});
song.section("build", bars(16), (t) => {
// Each at(bars(n)) origin keeps looping to the section's end, so later
// layers stack on earlier ones — a rising wall.
for (let entry = 8; entry < 16; entry += 2) {
clap.at(bars(entry))
.play(entry >= 14 ? fast(2, crescRoll) : crescRoll) // last layers double-time
.gain(0.3 + (entry - 8) * 0.08);
}
});

Drop a double-time roll on the last bar of a section to hand off to the next:

song.section("drop", bars(16), (t) => {
// ... the main groove ...
if (Math.floor(t.position() / 4) === 15) {
hats.play(fast(2, crescRoll)).gain(0.5); // last bar only
}
});

track.at(offset) makes subsequent play() calls start later in the section — the clap only exists from bar 8 onward:

song.section("build", bars(16), () => {
clap.at(bars(8)).play(at([4, 12], 16, { velocity: 0.6 }));
});

For a complete, club-length arrangement that uses all of the above together, read the annotated Nebelwand walkthrough.