Rationale and Motivation
Why build or learn a new programming language? This page tries to lay out the argument for Kronos.
Existing Music Languages
Audio is a compute-intensive problem. Many existing Music Languages rely on procedures written in C to peform the actual computation, and provide means to plumb data between them. There is a hard, opaque hood, under which the actual DSP is hidden.
Such languages are more akin to shell scripting than programming. This is not to say they can not be effective, but more often than not, the abstractions leak and the DSP engine does not work exactly how it should in every application.
The separation of signals into audio and control is another symptom of the leaky abstraction. It is done primarily to optimize: computational cost in DSP is proportional to the sample rate, so we curate our sample rates into a handful of buckets provided by the environment.
FAUST
Notably, FAUST is another compiler for music DSP that overcomes the ugen barrier. It is in many aspects similar to Kronos, and recently, there's been an increasing amount of cooperation and cross-pollination between the projects. However, the origins of the projects were different, even though they have recently converged.
FAUST started as a static compiler targeting C; Kronos started as an in-memory JiT compiler. Both projects have since gained both capabilities. Kronos has a more estabilished multirate system and full-featured generics, while FAUST has explored automatic parallelization and export to various architectures (plugin formats).
Objectives
- Approachable, visual, interactive and iterative development model
- Expressive specification of signal processors
- Unify all signal models and treat mixed-rate processing as a compiler optimization
- Runtime characteristics like C: fast, deterministic, no dependencies
Solutions
Kronos is a second-order programming language. The user program generates a lower-level program, which the compiler factors into different clock domains (with distinct sample rates) and produces low-level code for.
The generation step consists of a language roughly equivalent to a pure polymorphic lambda calculus, performing type derivation and signal rate inference. The palette of operators is extended with delay, a means of introducing controlled local state. From the programmer's point of view, delay looks like just another pure, side-effect free function. In a domain-specific twist, Kronos allows cyclic definitions that refers to itself through a delay operator.
The result of the generator step is a static circuit. The circuit is factored into clock domains: each clock domain has a distinct circuit with some shared state to facilitate cross-domain signals. Each clock domain is translated to efficient native code or webassembly via LLVM or Binaryen, with the final result of a minimal procedure to propagate a frame through a clock domain. The same method is used for all signals, from high bandwidth audio to event-based streams.
To learn more, please see the research papers section.
If you'd like to dive in, please take the interactive tour!