- Rust 100%
| examples | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
| SPEC.md | ||
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.