Skip to content

Conversation

@chinarjoshi
Copy link

@chinarjoshi chinarjoshi commented Dec 15, 2025

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:

# enter dev env (protobuf, g++, alsa-lib, libsndfile, pulseaudio, jack2)
cd chuck
nix develop

# optionally test the dsl binaries
# --------------------------------------------------------------------------------------------

cd src/core/dsl
make

# 1) Parse TOML -> proto (prints proto)
./dsl_dump test/beethoven.toml

# 2) TOML -> ChucK (writes simple.ck in the current dir by default)
./dsl_codegen test/beethoven.toml
./chuck --syntax beethoven.ck

# or explicitly choose output
./dsl_codegen test/beethoven.toml -o /tmp/beethoven.ck
./chuck --syntax /tmp/beethoven.ck

# 3) Optimize TOML -> TOML, then codegen -> ChucK, then syntax-check
./dsl_opt test/beethoven.toml --complexity 0.2 -o /tmp/opt.toml
./dsl_codegen /tmp/opt.toml -o /tmp/opt.ck
./chuck --syntax /tmp/opt.ck

# --------------------------------------------------------------------------------------------

# Build chuck and run the full pipeline:
# (runs DSL parse+codegen+compile; also dumps <basename>.ck in the current dir)
cd ../../..   # back to src/
make linux-pulse
./chuck --dsl core/dsl/test/beethoven.toml

message Note {
int32 pitch = 1; // MIDI pitch (0–127)
double dur = 2; // in beats
bool tie = 3; // optional: sustain into next note
Copy link
Author

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.

Copy link
Author

@chinarjoshi chinarjoshi Dec 24, 2025

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant