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.
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();
});
x = hit, . = rest. 16 steps = 1 bar.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 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();
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 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();
});
| Category | Count | Examples |
|---|---|---|
| Drums | 125 | kick_808, kick_909, snare_909, hihat_909_closed, clap_808 |
| Bass | 75 | sub_deep, acid_303, reese, moog, upright |
| Leads | 50 | supersaw, pluck, brass, strings |
| Pads | 41 | warm, shimmer, analog, cinematic |
| Keys | 19 | grand_piano, rhodes, wurlitzer, hammond |
| World | 24 | sitar, tabla, kalimba, koto, erhu |
| Effects | 66 | reverb, 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.
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.