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 sidechain pump
Section titled “The sidechain pump”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.
Euclidean stabs
Section titled “Euclidean stabs”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, sparseconst 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.
The build-up roll
Section titled “The build-up roll”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); }});A turnaround fill
Section titled “A turnaround fill”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 }});Bring a layer in halfway
Section titled “Bring a layer in halfway”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.