-
Notifications
You must be signed in to change notification settings - Fork 140
feat(dsl): semantic language that lowers to ChucK #530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
THIS COMMIT COMPILES SIMPLE.TOML
1. pitch simplification 2. pattern density 3. rhythmic quantization 4. timbre complexity
| message Note { | ||
| int32 pitch = 1; // MIDI pitch (0–127) | ||
| double dur = 2; // in beats | ||
| bool tie = 3; // optional: sustain into next note |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan to add start time in beats and velocity in [0.0-1.0] to support real polyphony, real rests, and MIDI. MIDI as an input to this pipeline will be particularly interesting to watch it flow through the complexity middle end.
| # | ||
| # Note: our DSL currently has no explicit rests, so each gesture is modeled as | ||
| # its own voice with an offset, leaving space between motifs. | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This piece is hacks-on-hacks-on-hacks. Real polyphony is only possible when voices have offsets and rests. Right now it's emulating chords by treating each stretch of time as its own voice. On the bright side, this file is entirely AI generated and sounds mostly correct, so Piece proto is semantically decent
ChucK is hard to language model; TOML is trivial. This PR adds a high-level TOML DSL for writing simple musical “pieces” and a complete end-to-end pipeline that compiles them to ChucK. The core idea is that the DSL is intentionally higher abstraction than ChucK source: instead of writing UGens and timing and whatnot directly, you describe musical intent in a structured form that can be reasoned about and transformed semantically. The DSL is backed by a Piece protobuf that models the essential musical units (notes+patterns), voice structure, and timbre configuration (oscillator, envelope, filter, LFO, gain, reverb), plus a global “complexity” knob.
On the front-end, a TOML parser reads the DSL into a dsl::Piece message with good validation and diagnostics. It performs explicit mapping from TOML tables/arrays into the proto and handles string-to-enum conversions validates ranges like MIDI pitch. To keep iteration time low, it also adds small standalone tools in src/core/dsl: dsl_dump for inspecting the parsed proto and dsl_codegen for generating ChucK from a TOML file without rebuilding the full Chuck binary.
The lowering step is intentionally template-style code generation rather than AST construction. From a Piece, dsl_codegen emits a plain ChucK program that sets up tempo, declares one function per voice, builds an appropriate UGen chain (osc → filter → ADSR → reverb → pan → dac), and schedules playback by iterating pattern notes with ADSR keyOn/keyOff and beat-scaled durations. Pattern-level knobs like density and repeat are reflected in the generated code, and the codegen avoids illegal operations (e.g., .freq assignment for Noise). This keeps the compiler integration simple because generated code goes through the existing ChucK compilation path.
The main value-add beyond “TOML that turns into ChucK” is the middle-end optimizer: a semantic transformation engine that operates on the proto, not on text. A sort of “creative intent” slider. Given a well-formed Piece, it returns a different Piece that preserves its identity while smoothly shifting it toward “simpler” or “richer” variants. It’s implemented as a pipeline of small, orthogonal rewrite passes, each sensitive to the same global complexity curve: Pattern Density, Pitch Complexity, Rhythmic Complexity, and Timbre Complexity. When simplifying, the passes reduce note density without destroying contour and collapses large leaps, merges rhythms, and simplify timbre (like less detune/LFO/reverb, smoother envelopes). When expanding, the same passes add variation like neighbor/approach tones, passing tones and octave displacement, subdivisions and off-beats, and more lively timbre. The optimizer is deterministic right now to make iteration and testing stable, and it outputs valid DSL TOML again to enable repeated optimize → codegen → compile cycles.
It wires the pipeline into the ChucK CLI with a --dsl <file.toml> flag. That path parses TOML → proto → codegen → compileCode() and also dumps the generated .ck into CWD. These steps are tested E2E in the Nix flake environment: