a tiny music programming language
Find a file
2026-06-10 20:58:50 -07:00
examples Initial commit 2026-06-10 20:55:23 -07:00
src Initial commit 2026-06-10 20:55:23 -07:00
.gitignore Initial commit 2026-06-10 20:55:23 -07:00
Cargo.lock Initial commit 2026-06-10 20:55:23 -07:00
Cargo.toml Initial commit 2026-06-10 20:55:23 -07:00
LICENSE Initial commit 2026-06-10 20:55:23 -07:00
README.md Initial commit 2026-06-10 20:55:23 -07:00
SPEC.md Update SPEC.md 2026-06-10 20:58:50 -07:00

oworpheus

A tiny music programming language. Write .owo source files in a C-like syntax, compile them straight to .wav.

cargo build --release
target/release/oworpheus examples/demo.owo
# wrote examples/demo.wav (4.66s @ 44100 Hz)

examples/ also has two simple chiptune covers to play with: mario.owo (Super Mario Bros. overworld, square lead + triangle bass) and zelda.owo (Zelda overworld theme, detuned saw "brass").

Language

The normative language definition is SPEC.md; this section is a working overview.

An .owo file has four kinds of top-level items: tempo, instrument, track, and play. Comments are // and /* ... */.

tempo 110;

instrument lead {
    let vib = sine(5) * 4;                  // 5 Hz LFO, +/- 4 Hz vibrato
    let body = saw(freq + vib) * 0.5
             + sine(freq * 2) * 0.2;        // octave-up sine layer
    out body * adsr(0.02, 0.15, 0.6, 0.3) * amp;
}

track melody uses lead {
    note E4 1/2;            // pitch, duration in beats
    note G4 1/2 0.9;        // optional velocity 0..1 (default 0.8)
    chord [C4, E4, G4] 2;   // several pitches at once
    rest 1;
}

play melody;

tempo

tempo <bpm>; sets the global tempo. Defaults to 120 if omitted.

instrument

An instrument describes how one note sounds, as a signal expression evaluated per sample. It is a sequence of let bindings followed by exactly one out.

Inside an instrument these names are predefined for the note being played:

name meaning
freq pitch in Hz
amp velocity from the score, 0..1
time seconds since the note started
dur the note's held duration in seconds

Signal generators:

  • sine(hz), saw(hz), square(hz), triangle(hz) — oscillators. The argument is itself a signal, so oscillators can modulate each other: sine(freq + sine(5) * 4) is vibrato, saw(freq * 1.01) is a detuned layer.
  • noise() — white noise.
  • adsr(attack, decay, sustain, release) — envelope, times in seconds, sustain as a 0..1 level. Release plays out after the note's duration ends, so notes ring past their slot in the score. Arguments must be constants.

Expressions support + - * /, unary minus, parentheses, and number literals. let bindings are evaluated once per sample and shared, so a bound noise() or oscillator is a single generator no matter how often it is referenced.

track

track <name> uses <instrument> { ... } is a sequence of timed events:

  • note <pitch> <beats> [velocity];
  • chord [<pitch>, <pitch>, ...] <beats> [velocity];
  • rest <beats>;

Pitches are written C4, F#3, Bb2 (A4 = 440 Hz). Durations are in beats and may be fractions (1/2, 3/4) or decimals (0.25).

play

play <track> [, <track> ...]; mixes the named tracks together starting at time zero. With no play statement, all tracks play. The final mix is peak-normalized only if it would clip.

Output

Mono 16-bit PCM WAV at 44100 Hz. oworpheus song.owo writes song.wav; use -o to pick another path.