llama
is a library for building audio synthesizers using a declarative EDSL. It contains a collection of combinators which consume and produce streams of values (usually float
s for audio signals and bool
s for control signals) which can be composed much in the same way as one would patch a modular synthesizer. The library also contains a player which can play a stream of float
s (treated as audio samples) through your sound card.
An additional library llama_interactive
can be used to connect synthesizers to input events and to render oscilloscope visualizations:
Some demos (from the examples directory):
There are more demo videos linked from the readme.
Get llama
with opam install llama
or opam install llama_interactive
. A third package llama_core
contains just the core type definitions and combinators but not the player. Use this if you just want to make more effects, filters, etc without depending on additional packages needed to play audio.
Note that llama
(and llama_interactive
) depends on conf-rust-2021
as interacting with the sound card is done using the cpal rust library. If you don’t want to install rust system-wide (e.g. because you use rustup) then run opam install conf-rust-2021 --assume-depexts
before installing llama
.
Code Example
This will play repeating pulses of a 440Hz sine wave.
open Llama
open Dsl
(* [osc] represents a signal whose value varies between -1 and 1 according
to a 440Hz sine wave. *)
let osc : float Signal.t = oscillator (const Sine) (const 440.0)
(* [note_clock] represents a signal whose value is either [true] or [false]
which changes from [false] to [true] twice per second, and spends 30% of the
time on. This is often used to communicate the fact that a key is pressed to
a module that responds to such events. *)
let note_clock : bool Signal.t =
pulse ~frequency_hz:(const 2.0) ~duty_01:(const 0.3)
(* [envelope] is a signal which is 0 while its [gate] argument is producing
[false] values, but which raises to 1 over the course of [attack_s] seconds
when [gate] transitions to [true], and transitions back to [false] when
[gate] transitions to [false]. Note that even though it is also a [float
Signal.t] like [osc] is, it doesn't contain audio data. Instead an envelope
is typically used to modulate a signal in response to a key press, which we
are simulating here with [note_clock]. *)
let envelope : float Signal.t =
asr_linear ~gate:note_clock ~attack_s:(const 0.01) ~release_s:(const 0.2)
(* Multiply the oscillator with the envelope to produce a repeating
burst of volume which gradually tapers off twice per second. *)
let output : float Signal.t = osc *.. envelope
(* Play the sound! *)
let () = play_signal output