Tzopilotl
Docs
GitHub

Frequently Asked Questions

The language

**Why does push not modify my array, while push! does?** The trailing ! is part of the identifier -- push and push! resolve to different functions. By convention, builtins ending in ! mutate an argument in place (push!, put!, insert!, pop!); the plain forms are non-mutating and return a new container. Array, Map, and Set are heap objects passed by reference, so in-place writes (a[0] = x, a push!(y)) are visible through every alias; use copy when you need an independent one.

I passed an array to a function expecting a scalar and it just... worked? That is auto-mapping: functions expecting scalars automatically apply over arrays and lists, element by element -- [1, 2, 3] add(10) is [11, 12, 13]. The @ operator gives explicit control over mapping depth and Cartesian products. See the Auto-mapping chapter of Tzopilotl by Example.

**Why does my function return Void when the body clearly computes a value? A function returns its trailing expression, which must have no** semicolon. { a + b; } is a statement -- the body evaluates it, discards it, and returns Void. Write { a + b }, or use the expression-body form fn add(a Int, b Int) Int = a + b;.

What does "NRT" mean in the library docs? Non-real-time. Functions marked NRT perform blocking syscalls (file IO, process launch, ...) and are rejected by the type checker in real-time contexts, where the VM must never block or call the system allocator. Each module's documentation states whether it is RT-safe.

Sound & synthdefs

**defSynthX fails with a compiler error about clang.** Synthdef compilation emits C++ and compiles it with the system toolchain, so a working clang must be on PATH. On macOS: xcode-select --install.

I redefined a synth while it was playing -- why didn't the sound change? Two reasons. Compilation is asynchronous: the clang step runs in the background so music that is already playing never pauses, and the new build is only available once it finishes loading (use await when the next line needs the def, i.e. its first play). And a redefinition never changes nodes that are already running: they keep the code they were created with, and only nodes created afterwards -- the next play, the next note from a sequence player -- use the new version. A running sound changes only when you tell it to; to crossfade a live sound into a new definition, use a proxy (ndef / <- from live.*).

Do the different engines/silos stay in sync? Engines and silos have independent tempos by design -- nothing assumes or enforces synchronization between them.

Project

What do "TZPL" and "Tzopilotl" each refer to? Tzopilotl is the programming language (tzopilotl is Nahuatl for vulture -- hence the emblem). TZPL is the whole platform: the language, the synthdef compiler, the real-time engine, and the desktop app.

Is it really GPL? What about the JUCE app? The project is GPLv3. The optional JUCE app target (tzpl_app_juce) links the JUCE framework under its AGPLv3 option -- JUCE is downloaded at build time, not included in the repository -- and binaries built from that target combine GPLv3 and AGPLv3 code as each license permits. Vendored third-party code keeps its original permissive licenses; see THIRD_PARTY_NOTICES.md.

Will it run on Linux or Windows? Currently macOS only (the engine speaks CoreAudio). Cross-platform support is planned; the interpreter and compilers are portable C++23, so the audio backend is the main porting surface.