fn onepole(x, ratio){
x*(1.0-ratio) + self*ratio
}
self is initialized to 0 and retrieves the return value of that function 1 sample agofn biquad_inner(x,a1,a2){
let (ws, wss, _wsss) = self
let w = x - a1*ws - a2*wss
(w, ws, wss)
}
pub fn biquad(x,coeffs){
let (a1,a2,b0,b1,b2) = coeffs;
let (w,ws,wss) = biquad_inner(x,a1,a2);
b0*w + b1*ws + b2*wss
}
self is polymorphic (becomes as same as return type)fn fbdelay(input, time, fb, mix){
input * mix + 1.0 - mix * delay(40001.0, input + self * fb, time)
}
delay is a built-in function. Three arguments: maximum delay size, input signal, delay lengthuse math::*
fn phasor_zero(freq){
(self + freq/samplerate)%1.0
}
pub fn phasor(freq,phase_shift=0){
(phasor_zero(freq) + phase_shift)%1.0
}
pub fn sinwave(freq,phase=0){
phasor(freq,phase)*2.0*PI() |> sin
}
samplerate is a runtime-defined environment variable|> is notation for writing a(b) as b |> a — pairs well with functional signal dataflow#stage(macro)
let detune_table = [128,-128,408,-417,704,720]
let numbers = detune_table |> len |> lift
let detunepitches = map(|x| x /(2^7),detune_table)
fn supersaw(){
let init = `|freq,detune|{ saw(freq,0) / $numbers}
foldl(detunepitches,init,|elem,acc|{
`|freq,detune|{
let f = freq + $(elem|>lift) * detune
($acc)(freq,detune)+saw(f,0) / $numbers
}
})
}
`) and Splice($) in lisp, with type safety
use core::*
use mininotation::*
use osc::*
use env::adsr
use delay::stereo_delay
use filter::lowpass
fn dsp(){
let note = run_note!(mini("60 <62 72> <[64 [74 72] 65] [65 62]> ~ 69")
||> legato(0.2,_), 1)
saw(note.val-12 |> midi_to_hz, 0.0)
* adsr({attack = 0.001,release=0.1,sustain=0.9,gate=note.gate})
||> lowpass(_,((sinwave(0.1,0)+1)*500+400),4)
}
script inside mini function is embedded DSL on mimium
use ...
let notes = [60,62,64,67,72]
fn melody(cps){
let l = notes |> len
let phase = phasor(cps/(l-1),0)
let i = phase * l |> floor
let freq = notes[i] |> midi_to_hz
let gate = {gate = (phase*l%1) < 0.2} |> adsr
let ff = (sinwave(1,0)+1)*1000+200
saw(freq,0)
||> lowpass(_,ff,4)
||> _ * gate
}
let cps = 2
fn dsp(){
melody(cps)
|> tostereo
||> pingpong_delay(_,0.5,0.3,1.0,0.5)
}
fn fbdelay(input, time, fb, mix){
input * mix + 1.0 - mix * delay(40001.0, input + self * fb, time)
}
fn dsp(input){
input
||> fbdelay(_, 2000, 0.9, 0.5)
||> fbdelay(_, 3000, 0.7, 0.5)
||> fbdelay(_, 5000, 0.5, 0.2)
}
a ||> foo(_,b,c) is equivalent to foo(a,b,c)fbdelay should be intepreted as different instancesfn fbdelay(input, time, fb, mix){
...
let s = get_self();
shift_state_position(1);
...
update_ringbuffer(...);
shift_state_position(-1);
...
let ret_value = ...
set_self(ret_value);
ret_value
}
fn dsp(input){
let a = fbdelay(input,2000,0.9,0.5);
shift_state_position(40004);
let b = fbdelay(a, 3000,0.7,0.5);
shift_state_position(40004);
let c = fbdelay(b,5000,0.5,0.2);
shift_state_position(-80008);
c
}
dsp function call tree between 2 versionState ::= Feed(Type)
| Mem(Type)
| Delay(Type,Max_Time)
| DirectFnCall(Vec(State))
fn phasor(freq){
(self+(1/freq))%1.0
}
fn osc(freq){
- phasor(freq )* 2 * PI |> sin //case a
+ phasor(freq+(phasor(freq/10))) * 2 * PI |> sin //case b
}
fn fmosc(freq,rate){
osc(freq + osc(rate))
}
fn dsp(){
- fmosc(440,10) + osc(880) + fmosc(1320,10)//case 1
+ fmosc(440,10) + fmosc(1320,10)//case 2
}
Each of "Patch" is like memcpy operation
samplerate and now literals) and basic scheduler are implemented with plugin systemSystemPlugin trait and mimium_export_plugin! macro.(Dynamically loadable)fn counter(){
self+1.0
}
//add -1.0 offset so that the counter start from 0 at t=0
fn dsp(){
let sampler = Sampler_mono!("../tests/assets/konnichiwa.aif")
let player = sampler.player
let len = sampler.length
(counter()-1.0)%len
|> player
|> Probe!("out")
}
foo!(bar) is the shorthand for macro invocation $(foo(bar))
fn counter(){
self+1.0
}
//add -1.0 offset so that the counter start from 0 at t=0
fn dsp(){
let sampler = _get_sampler(0) //0 is internal id
let player = sampler.player
let len = sampler.length
(counter()-1.0)%len
|> player
|> _probe_intercept(0)//0 is internal id too
}
After the macro expansion
#[derive(Default)]
pub struct SamplerPlugin {
sample_buffers: Vec<Arc<Vec<f64>>>,
sample_namemap: HashMap<String, usize>,
}
impl SamplerPlugin {
...
#[mimium_plugin_macro]
pub fn make_sampler_mono(&mut self, v: &[(Value, TypeNodeId)]) -> Value {
...
Value::Code(Self::sampler_record_expr(idx, sample_length))
}
#[mimium_plugin_fn]
pub fn get_sampler(&mut self, pos: f64, file_idx: f64) -> f64 {
let idx = file_idx as usize;
let samples = self.sample_buffers.get(idx);
match samples {
Some(vec) => interpolate_vec(vec, pos),
None => {
mimium_lang::log::error!("Invalid file index: {idx}");
0.0
}
}
}
}
mimium_export_plugin! {
plugin_type: SamplerPlugin,
plugin_name: "mimium-symphonia",
plugin_author: "mimium-org",
capabilities: {
has_audio_worker: false,
has_macros: true,
has_runtime_functions: true,
},
runtime_functions: [
("__get_sampler", get_sampler),
],
macro_functions: [
("Sampler_mono", make_sampler_mono),
],
type_infos: [
{ name: "Sampler_mono",
sig: SamplerPlugin::sampler_mono_signature(), stage: 0 },
{ name: "__get_sampler",
ty_expr: function!(vec![numeric!(), numeric!()], numeric!()),
stage: 1 },
],
}
Hello, My name is Tomoya Matsuura. This is the last session of the ADC Japan 2026 and thank you for choosing my talk among the other wonderful talks. My presentation is about my programming language mimium, which is mainly for signal processing. My talk is gonna be a little bit theoretical rather than practical content for product development, but I hope it may contribute to some fundamental knowledge in DSP development and audio production.
This is me. I'm now an independent researcher — until last year I was working at a university, teaching programming and doing research, and now I'm basically unemployed. I sometimes perform improvisation with my self-made instruments as a musician, and I keep doing my own research on programming languages. I call my research area "Civil Engineering of Music" — an imaginary field that explores the infrastructural layer of music technology and tries to rebuild it with the critical eye of a practitioner.
So, what is mimium? mimium is a functional programming language for signal processing, implementing the minimum set of music-oriented features on top of a general-purpose language. It's developed in Rust, and the language itself has a Rust-like syntax, though its actual semantics are more functional. It runs on multiple backends.
Usually it runs as a native runtime through the VS Code extension or CLI. The extension automatically downloads command line tools and it also has basic language server for error reporting and basic autocompletion.
It also runs in the browser — mimium has a web component widget and a web editor for making sound in real time using WebAssembly. I recently added even more backends. One is a Rust transpiler that converts entire mimium source code to Rust, so you can use it in production-level software like a VST plugin. The transpiler also works on Web version.
I'm also building an audio plugin version of the mimium editor.
Here is today's roadmap. First, I will talk about the motivation for developing the language and the basic principles behind its design. Then I will show several concrete examples of writing fundamental DSP algorithms in mimium. Next, I will give a short demo of mimium's live-coding feature, one of its distinctive capabilities. After that, I will explain how the runtime is actually implemented to support these language features. Then I will present performance benchmarks obtained from these implementations. Finally, I will introduce the plugin system that connects mimium's features to real-world use cases.
So, when I started designing mimium, what I was thinking was: sound programming languages are just too complicated. When you start learning Max, SuperCollider, or ChucK, you need to know hundreds of built-in primitive functions and a whole bunch of unit generators. Most of them aren't built on top of the language itself — they're implemented in C or C++, and the internal structure is a black box. Why are they so complicated?
Let me put it another way: why are music programming languages built in such a patchwork manner, instead of being more like ordinary programming languages? It's a somewhat paradoxical question — we're trying to build a domain-specific language, and yet we're also seeking generality. But really, this is a contradiction that any domain-specific language potentially faces.
Musical programming languages, or computer music languages, have a long history. Their primary purpose was to play music on electronic computers, and since the 1980s they've been developed for systems capable of executing in real time. The early languages emerged in an era before displays and mice existed, so in a sense, using a programming language as an interface was simply the only option available. From that perspective, we could view the languages developed from the 1990s onwards as having deliberately chosen the less conventional interface of a programming language. The pursuit of unique forms of expression that can only be achieved through programming may well have only begun relatively recently.
In today's OS environments, there are many constraints when building an audio language. To ensure real-time safety, we need to minimize dynamic memory allocation as much as possible and synchronize threads correctly. At the same time, modern programming languages provide abstractions for these concerns. So, could we build an audio DSL as a library or framework in an existing language, instead of designing a new language from scratch?
In fact, James McCartney, the original author of SuperCollider, made an interesting comment about this. (Read) He goes on to mention that client-side languages like Sonic Pi, which use SuperCollider as their backend, are actually implemented as Ruby libraries. However, there are currently no examples of a general-purpose programming language replacing the sound synthesis engine itself — something like SuperCollider — as a library.
So, how is the situation in 2026? Unfortunately, there's still no general-purpose language that satisfies all the requirements for building audio DSLs as a library. C, C++, and Rust require careful manual resource management. Languages with garbage collection like Go aren't really usable for real-time audio since the GC can randomly block the process. And compiled languages aren't great for hot-swapping code, while interpreter-based interactive languages always rely on garbage collection. And that's what led me to the idea — what if we design a general-purpose programming language with audio processing as its primary goal?
With those reflections on existing languages in mind, I decided on mimium's design goals. SuperCollider's dynamic type system gives you concise, flexible notation, but the runtime system for live coding is complex and the codebase is enormous — so porting it to the web or microcontrollers isn't easy. ChucK, developed at Stanford, is great for sample-accurate scheduling, but it's hard to compose signal processing parametrically, and its live coding works by simply swapping instances, which means delay and reverb tails get cut off on update. Faust is a language specifically designed for DSP description, with very rigorously defined semantics, which lets it transpile to many platforms. But since it's focused purely on DSP synthesis, using it for live performance is difficult, and composing at the musical control level isn't straightforward either. And while its semantics are rigorous, it has an exotic syntax that's incompatible with other programming languages and has a steep learning curve.
Taking those shortcomings into account, I settled on mimium's design goals. First, the syntax should be familiar — close to standard programming languages like JS or Rust. Second, no special types for signal processors or signal data — everything should be expressible through plain function definition and application. This is achievable by taking inspiration from Faust's semantics: define feedback and delay as minimal primitive stateful operations. And for memory management, the goal is to avoid heap allocation as much as possible.
So let me get into the details of the language design. mimium is based on a formal calculus called λmmm, which is an extension of the lambda calculus — the theoretical foundation underlying pretty much all functional programming languages.
I know this slide might only make sense to programming language theorists, so I'll skip the details — but the parts unique to mimium are the `feed` expression for feedback, and `delay`, both built in as primitives. Let's look at some concrete code examples to make this clearer.
The simplest and clearest way to show how mimium works is this one-pole low-pass filter — basically an integrator. It mixes the current input with its own previous output at some ratio. The syntax looks a lot like Rust, but the `self` keyword has a very different meaning here. Inside a function, `self` gives you the return value of that function from one sample ago. That's how you express a feedback connection in signal processing. So this function is stateful — it returns a different value each sample. But importantly, it always starts from zero at sample zero and computes deterministically, so given the same input, it always produces the same sequence.
Let's extend this further and look at a biquad filter — the kind you'd find in a typical equalizer. I'll skip the implementation details, but notice that the type of `self` always matches the function's return type, which means it's polymorphic — it adapts to whatever the function returns.
Just like `self` is a primitive for expressing feedback, `delay` is also a built-in function. It takes three arguments: the maximum delay size, the input signal, and the actual delay length. Here, we combine it with `self` to write a simple mono feedback delay.
We've looked at a few effect examples, so let's make an oscillator. Using `self`, we can build a numerical counter running at sample rate. Apply modulo to that, and you get a sawtooth phasor. Then scale it and pass it through a sin function — done. In the last function, I used the pipe operator — that's functional programming notation that just applies the left-hand value to the right-hand function. It's common in functional languages, and in mimium it's used a lot for writing signal processing flows, which would otherwise be method chains in other languages.
So far we've looked at the basic features of mimium as a signal processing language. Now let's look at more powerful abstractions that become possible specifically because mimium is based on lambda calculus and represents processors as functions. The key point is that since the basic unit of signal processing is a function, we can compose complex processors using higher-order functions.
Higher-order functions might sound intimidating, but we deal with exactly this kind of problem all the time in audio synthesis. Take a supersaw oscillator — it's essentially abstracting and duplicating multiple sawtooth waves. With mimium's map and fold functions, we can express this very concisely. You'll notice some new syntax here — the backtick and dollar sign — that's mimium's type-safe macro system, which I'll explain in a moment. mimium doesn't have control structures like for-loops, just if-expressions — but function definition and application alone is enough to express quite complex algorithms.
This macro system is based on something called Multi-stage Computation from lambda calculus theory. Think of it like quote and splice in Lisp — you can embed expressions inside backtick-quoted code using the dollar sign. What makes it distinctive is that type checking and inference are completed before the macro is expanded. This prevents a common macro problem where the expanded expression causes a cryptic syntax error that's hard to debug. That's actually a well-known pain point in Faust, so I consider this one of mimium's strengths. And since macros essentially let you run complex computations at compile time — if you run a text parser as a macro, for instance, you can embed an entirely different DSL inside mimium.
Here's an actual example: I implemented a DSL called uzulang on top of mimium — it's the same notation used in TidalCycles and Strudel for expressing rhythmic patterns. Inside the `mini` function, you can see the mini-notation — a compact syntax for describing rhythmic patterns. Importantly, the parser for this notation is written in mimium itself, not in Rust.
So we've covered how you write DSP algorithms in mimium. The macro system is quite unique, but in terms of overall coverage, mimium is broadly comparable to languages like Faust or CMajor. Now I want to talk about another feature that really sets mimium apart from those languages — live coding.
mimium's live coding is actually very simple. While a file is running in the VS Code extension or CLI, you just edit the file and save, that's it. — and the sound updates naturally. I already did a realtime demo and actually doing it but Let me do a another quick demo. The problem about this live coding feature is that because it is super natural, it is hard to tell what is happening and what is great. This code has a looping melody with a feedback delay on it. Let's try changing the tempo of the phrase. You can hear the new melody come in while the delay tail keeps going without cutting out, right? You can also try changing the feedback amount... and now let's set the delay input to zero and the feedback rate to one. This is a very strange state — nothing is going in, but sound keeps coming out. That's mimium's live coding. It was actually something of a by-product of the design, but it ended up being quite unique compared to other languages. Rather than modifying the existing runtime, mimium compiles a completely new virtual machine from scratch and swaps the whole thing on every save. Since we're not modifying the machine, I call this "Static Live Coding" — which sounds contradictory, I know. I'll explain how it works at the end of the implementation section.
So we've gone through what mimium can do. Now let's look at how it's actually implemented under the hood.
This diagram gives a rough overview of mimium's virtual machine runtime. Whether it's the VM backend, WASM, or the Rust transpiler, they all follow roughly this same model. It might look a bit complex, but it's basically a standard register machine architecture — you've got a program counter, a call stack, and so on. The program consists of a collection of static variables and a list of function prototypes, each holding a sequence of bytecode instructions. At runtime, most values live on the stack rather than the heap. Since mimium is based on call-by-value lambda calculus, most arguments are copied when functions are called. The only things that go on the heap are closures, arrays, and strings — and those are garbage collected using reference counting. The part unique to mimium is what I call the State Storage, and I'll explain that in more detail next.
As we saw in the code examples, mimium expresses stateful operations like delay and feedback as plain functions, so the runtime needs to handle these properly. In most languages, state encapsulation like this is done through classes, closures, or state monads. But mimium uses a different model — I call it the Relatively Moving Pointer Model. Let's look at a concrete example. Here's the feedback delay from earlier, applied three times with different delay times and feedback amounts. The `||>` notation is a macro pipe — it's partial application that substitutes the left-hand operand into the underscore on the right. Each of these three `fbdelay` calls needs to be treated as a separate instance. So how does the runtime represent that?
This is a pseudo-code representation of what the lower-level machine or transpiled Rust code looks like. First, all the internal DSP state in this code is managed as a single flat array. On top of that, there's a single pointer telling you where in the array to read and write. Let's look at fbdelay. When executing the feedback, at the start of the function we pull one value from the pointer position onto the stack. Then to handle the delay, we shift the pointer forward by one, and interpret that region as a ring buffer, updating it. Finally, we need to write back the return value for `self`, so we move the pointer back and write the data. Now in the dsp function — each time fbdelay is called, dsp offsets the pointer by the amount of state that fbdelay uses, then calls it again. This way, fbdelay always executes the same code, but operates on different data each time. The key insight is that pointer movement is expressed as a relative offset, not an absolute address. That's what allows the same fbdelay function to behave like separate instances. And this is really important for the transpiler too — functions in mimium can map directly to functions in the target language without converting them to classes, which keeps the implementation much simpler.
Now, the fact that all this state data is laid out in a flat linear array turns out to be crucial for live coding as well. Because the state is ordered according to the dsp function's call tree, we can structurally compare two call trees and find what they have in common. If you're familiar with web development, think of how React does something similar with the Virtual DOM. The algorithm we use is Longest Common Subsequence — the standard algorithm for comparing text and tree-structured data. From that structural comparison, we extract the branches shared between two versions of the VM and generate a sequence of patches to copy state data from the old machine into the new one.
Specifically, if we traverse each stateful operation, feedback, delay, and mem(mem is a one sample delay. it is distinct operation because it does not need to track read-write point of ring buffer.), reduced version of call tree can be derived and it is saved to the compiler. Call tree traversal usually have a problem of that may fall into infinite loop. However, it actually does not. Because the function used for higher order function and recursive function generates "closure"... think like closure is some kind of function instance. And State storage for closure is allocated on the instance, not on the global space.
Let me walk through an example. We have a function called `fmosc` that modulates an oscillator's frequency with another oscillator. `fmosc` depends on `osc`, which starts out as a simple sine wave. Initially, `dsp` uses two `fmosc` calls and one standalone `osc`. Then we change `osc` to use `phasor` twice, and remove one `osc` from `dsp`. At that point, intuitively, you'd expect the phase accumulation already happening inside `fmosc`'s phasors to carry over naturally — you don't want them to reset. That's exactly what mimium's live coding delivers.
This diagram shows what happens during a code update. The root dsp call tree changes from top to bottom. Since osc's definition changed and new nodes were added, the state for those new nodes gets zero-initialized, while everything else carries over. The compiler runs the LCS algorithm to generate patches. Think of each patch as a simple memcpy operation — a source address, a destination address, and a size. These patches are applied at the moment the VM is swapped, which is how the state gets transferred.
What's nice about this approach is that no matter how large the codebase is, generating the patches happens off the audio thread. The audio thread is only blocked for as long as it takes to apply the patches — which is typically very small, because in live coding you usually make incremental changes rather than rewriting everything at once. This means we don't need any complex cross-thread synchronization in the runtime to keep audio seamless. It means that, for example, let's say we want to adds transpiler backends for microcontroller without operating system like arduino or daisy. The basic runtime still does not rely on complex synchronization mechanism in OS layer. So it keeps runtime's portability. There's also a meaningful difference in mental model: instead of having to keep track of the current runtime state and carefully modify it in place, you can just focus on the algorithm as it's written in the text.
Finally, let's talk about performance. mimium was not designed solely to be ultra-fast, but being extremely slow would still be a serious problem. So, how fast is it in practice?
In this benchmark, I compared four platforms. I chose Rust-compatible platforms to evaluate the Rust transpiler — libpd(library version of puredata)'s Rust bindings, Faust's Rust backend with default compile options, the fundsp library as a native Rust DSP library, and then mimium in three flavors: VM interpreter, WASM, and the Rust transpiler. The task is additive synthesis with sine wave oscillators — 10 oscillators on the left, 100 on the right. The vertical axis is time to process 1024 samples, so lower is better. Note the logarithmic scale, since the VM and WASM are much slower. Honestly, I was surprised by how well-tuned Pure Data is. Though keep in mind it operates at a coarser granularity than Faust or mimium. The Rust transpiler matches fundsp at 10 oscillators, but scales a little worse than 10x going to 100 — the same pattern Faust shows, probably due to the difference between buffer-based and sample-by-sample computation. The remaining gap versus Faust is likely the theoretical overhead of the Relatively Moving Pointer Model. And VM and Wasm backend are vely slow, wasm is 10x slower and VM is another 10x slower than Wasm. one of the reason is VM and WASM backend can use only 64 bit precision float while the other platforms are runnning 32 bit float. However, that said, the VM backend isn't useless — it's still fast enough for basic real-time DSP, and you can run 100 oscillators without dropouts. Also, the VM interpreter is used by all backends for macro execution — for complex macro code like the mini-notation parser, you need the VM to run it fast enough. And importantly, the compiler currently does almost no optimizations. Constant folding, inlining — there's still a lot of room to improve on the mimium side.
So yes, mimium has been shown not to be ultra-fast. But that doesn't mean it's useless. One issue I see with music programming languages is that there are no shared metrics for evaluating them. And I think language design is fundamentally about making choices among trade-offs. Improving runtime speed generally requires more static analysis, which increases compile time. Finer-grained expression and performance tuning tend to make dynamic modification harder — just look at how most live coding languages only operate at MIDI-level signals. Specializing to specific architecture makes runtimer performance faster but makes it hard to port to another platform. And if you try to tick all the boxes, the implementation becomes enormous and complex. Implementation simplicity is an underappreciated quality I think. A patchwork implementation leads to an ambiguous spec. Code size becomes a barrier for new contributors. Less people cares about the fact that, most music programming languages are still maintained mainly because their original author is still alive — SuperCollider being a notable exception. I feel it's serious problem whether the language can be maintained after the death of the original author. mimium trades some runtime performance for simplicity. And it resolves the tension between performance and dynamic code modification through VM hot-swapping — which I think is a genuinely unique aspect.
Since we still have a little time, let me explain one more point where mimium differs from DSP languages such as Faust and CMajor: the plugin system. The plugin here I mean is nothing related to Audio/VST plugin but it's plugin for mimium. For example, Faust can call functions defined in C, but those are generally limited to pure mathematical functions, and calling stateful functions is not generally supported. In mimium, functions defined in Rust can be called relatively easily through the plugin system. The plugin system also allows arbitrary hooks, such as at system startup or at sample-processing timing, which makes it possible to call functions that handle state.
In the native version of mimium, features such as MIDI input, a simple audio-file sampler, GUI parameter control, and wave-scope visualization are currently provided through the plugin system. Even some literal value like samplerate and current time via reserved value "now" from the audio driver, while partly treated as special cases, is basically exchanged via the plugin system. Plugin developers can add their own plugins to mimium by using several procedural macros. These plugins can also be loaded dynamically, making it possible to provide new features without rebuilding all of mimium.
Let's look at an actual code example. This is the most basic sampler example. In this code, Sampler_mono and Probe are plugins, and you may have already seen Probe in earlier examples. These functions are invoked with an exclamation mark, which is shorthand for a macro mechanism where the called function returns a code fragment and that fragment is embedded. For a sampler, for example, loading audio from a file into memory should happen only once at startup, so it is provided as a macro. Otherwise, it would imply loading the file on every sample.
After macro expansion, this code is rewritten as follows. get_sampler and probe_intercept are functions provided by plugins, but they are usually invisible to users. For example, when loading multiple files, every Sampler_mono call is replaced with a get_sampler call, while the internal argument increments as 0, 1, 2, and so on. These are internal IDs corresponding to file names managed by the plugin. The Probe function works similarly. From the user side, probe_intercept appears to simply return the input value as-is. In practice, it sends the received value to the GUI for graph display. Here as well, the probe name is internally mapped to an internal ID.
On the Rust side, these plugins are implemented in this style. The macro API for plugin implementation is still somewhat unstable, so I will not go too deep here, but in principle you can export a function by annotating it with mimium_plugin_fn or mimium_plugin_macro. Macro functions mainly operate at the AST level and typically return a code fragment of type Code. Runtime functions can export primitive-to-primitive functions, and the Rust macros validate type-system consistency with mimium.
Finally, we define metadata for exporting as a dynamic library. The export declarations appear again here and feel redundant, so I would like to improve this in the future. There is a reason: GUI functions such as Probe are generic functions that can accept not only scalar numbers but also numeric tuples (stereo or multichannel signals), and work as multichannel probes. Plugins with generic functions need to interact with the compiler during type inference, which makes the behavior more complex. I think this may be excessive for typical users.
Alright, I think I talked almost all of topics now, let me wrap up.
It's been over six years since I started building this language. At the beginning of the talk, I mentioned that DSLs carry an inherent contradiction — they aim for a specific domain while also wanting some degree of generality. The reason I put "multi-purpose" in the subtitle is because I've been wrestling with that philosophical question. I don't think programming languages should only exist to solve predefined problems — they should extend how you think, make computing enjoyable, and serve as tools for exploration. Recently I've been performing live with mimium and using the Rust transpiler to build my own VST plugins. I didn't start mimium with a fixed goal in mind, but it's becoming a genuinely useful and interesting tool — at least for me. mimium incorporates some modern PL theory like multi-stage computation, but there's still a lot more theory out there to bring in. And AI agents — something that didn't exist when I started designing this language — have turned out to be a really powerful tool for language implementation, even if I have complicated feelings about AI-generated audio. Language development is basically write-a-test, make-it-pass, repeat — which is a perfect fit for autonomous coding. So if today's talk sparked some curiosity, I'd encourage you to try designing your own language.
There's still a lot I want to do with mimium. The Rust transpiler is still in early stages but it's becoming usable. I'd love to target a broader range of platforms — especially microcontrollers. A C++ backend would also be nice. I didn't cover this today, but mimium has a module system similar to Rust's, which is still pretty immature. A package manager for sharing code online would also be an important part of the ecosystem. On the type system side — I used map and fold today, and those functions need generic types. Generic support is still limited, so that's something I want to improve. I don't want to add too much complexity, but something like type classes or interfaces might be worth adding. Another thing I didn't mention is GUI integration, which is currently done through a plugin system inspired by Lua's design — I want to make that more accessible to other developers. That plugin system also covers MIDI and audio file access, but those don't work on the web platform yet, so I'd like to think about a more portable approach. And from my experience performing live with mimium — handling longer temporal structures at the composition level, beyond just DSP control, is something I'd like to revisit, whether as a library or as a language feature.
That's everything. mimium is a functional music programming language based on lambda calculus. UGens are expressed as functions, enabling higher-order composition. With the type-safe macro system, you can embed other DSLs inside the language. And static live coding lets you hot-swap code naturally. Documentation is available at mimium.org, and contributions to the project are very welcome.