← trusch.io

VibeLang

I got tired of DAWs. The mouse, the menus, the proprietary project files, the inability to diff two versions of a track. So I built a programming language for making music instead.

VibeLang runs a beat, melody, or full track from a plain text file — and keeps it running while you edit. Save the file, hear it change in under a millisecond. No restart, no rebuild, no lost state. Errors show up in the terminal; the audio keeps going. The whole feedback loop is as fast as typing.

The audio engine is SuperCollider under the hood, which means professional-grade DSP. The standard library ships 580+ sounds as plain .vibe files you can read, copy, and learn from. Your music is plain text — diff it, branch it, grep it, collaborate on it with git.

Quick Start

import "stdlib/drums/kicks/kick_909.vibe";
import "stdlib/drums/snares/snare_909.vibe";
import "stdlib/drums/hihats/hihat_909_closed.vibe";

set_tempo(120);

define_group("Drums", || {
    let kick  = voice("kick").synth("kick_909").gain(db(-6));
    let snare = voice("snare").synth("snare_909").gain(db(-9));
    let hihat = voice("hihat").synth("hihat_909_closed").gain(db(-12));

    pattern("kick").on(kick).step("x... x... x... x...").start();
    pattern("snare").on(snare).step(".... x... .... x...").start();
    pattern("hihat").on(hihat).step("x.x. x.x. x.x. x.x.").start();
});
Step sequencer grid
Step notation: x = hit, . = rest. 16 steps = 1 bar.

Patterns and Rhythms

Patterns use step notation or Euclidean distribution. Step notation is direct — each character is a 16th note. Euclidean rhythms distribute N hits across K steps using the Euclidean algorithm, which produces many classic world music patterns.

// Step notation
pattern("groove").on(kick).step("x... x..x x... x...").start();

// Euclidean rhythms
pattern("tresillo").on(conga).euclid(3, 8).start();   // Cuban tresillo
pattern("cinquillo").on(shaker).euclid(5, 8).start(); // Cuban cinquillo
pattern("samba").on(perc).euclid(7, 16, 2).start();   // rotated by 2 steps

Melodies and Harmony

Melodies use note names with holds and rests. Pipe separators mark bar lines. Chord notation like C3:maj or Am7 plays all notes at once. Array input works too for generated or computed note sequences.

// Note sequence with holds (-)  and rests (.)
melody("bassline").on(bass).notes("C2 - - . | E2 - G2 . | A2 - - -").start();

// Chords
melody("pads")
    .on(pad)
    .notes("C3:maj - - - | A3:min - - - | F3:maj7 - - - | G3:7 - - -")
    .start();

// Accidentals and octaves
melody("lead").on(lead).notes("C#4 - Eb4 - | F#4 - Ab4 - | Bb4 - - -").start();

Custom Synths

When the standard library isn't enough, define your own synths using unit generators (UGens) — the building blocks of SuperCollider's audio engine. Envelopes are built with a builder chain: envelope().adsr(...).gate(gate).cleanup_on_finish().build().

define_synthdef("my_bass")
    .param("freq", 110.0)
    .param("amp",  0.5)
    .param("gate", 1.0)
    .body(|freq, amp, gate| {
        let env = envelope()
            .adsr("10ms", "100ms", 0.7, "200ms")
            .gate(gate)
            .cleanup_on_finish()
            .build();
        let osc = saw_ar(freq) + saw_ar(freq * 1.01);
        rlpf_ar(osc, 800.0, 0.3) * env * amp
    });
// FM bell — carrier modulated by a decaying modulator
define_synthdef("fm_bell")
    .param("freq", 440.0)
    .param("amp",  0.4)
    .body(|freq, amp| {
        let mod_env = envelope().perc(0.001, 0.5).build();
        let modulator = sin_osc_ar(freq * 3.0) * 4.0 * freq * mod_env;
        let carrier   = sin_osc_ar(freq + modulator);
        let env = envelope().perc(0.001, 1.5).cleanup_on_finish().build();
        carrier * env * amp
    });

Groups and Effects

Groups route voices through a shared effects bus. Define them inline with a closure. Effects are applied at the group level so individual voices stay clean.

define_group("Synths", || {
    let lead = voice("lead").synth("my_bass").gain(db(-6)).poly(4);
    let bell = voice("bell").synth("fm_bell").gain(db(-9));

    melody("lead_line")
        .on(lead)
        .notes("C3 - E3 - | G3 - E3 - | C3 - - -")
        .start();

    melody("bells").on(bell).notes("C5 - - - | G4 - - -").start();

    fx("space").synth("reverb").param("room", 0.6).apply();
});

Standard Library

CategoryCountExamples
Drums125kick_808, kick_909, snare_909, hihat_909_closed, clap_808
Bass75sub_deep, acid_303, reese, moog, upright
Leads50supersaw, pluck, brass, strings
Pads41warm, shimmer, analog, cinematic
Keys19grand_piano, rhodes, wurlitzer, hammond
World24sitar, tabla, kalimba, koto, erhu
Effects66reverb, delay, chorus, distortion, compressor

All sounds are plain .vibe files — read them, tweak them, learn from them. The source is in stdlib/ and fully editable.

Try It

Install it, write a .vibe file, run vibe yourfile.vibe. Then start editing while it plays. That's the whole workflow.

cargo install vibelang-cli

Full docs, tutorials, and a live demo are at vibelang.org. Source and issues on GitHub. Alpha — core features are solid, some edges are rough.