Decomposer: Learning to Decompile
Symbolic Music to Programs
July 02, 2026
Musical performance involves executing a set of high-level musical instructions, yet recovering those instructions from the performance is a challenging inverse problem. We present Decomposer, a post-training framework for symbolic music decompilation: the task of recovering executable, editable music programs from symbolic music. We instantiate the task as MIDI-to-Strudel decompilation, where the model takes symbolic MIDI as input and produces a program in Strudel, a music programming language, that reconstructs the input when executed. The task poses two challenges: Strudel is a low-resource language with little naturally paired MIDI–code data, and optimizing faithful reconstruction of MIDI alone can collapse to unreadable note-by-note transliteration. We address these challenges in two stages. First, we construct Strudel-Synth, a synthetic corpus of paired Strudel programs and rendered MIDI, and use it for supervised fine-tuning. Second, we refine the model with reinforcement learning on unpaired MIDI, optimizing rewards for both MIDI reconstruction faithfulness and code readability. Our evaluation across synthetic and real-world MIDI benchmarks shows that Decomposer achieves substantially higher MIDI reconstruction faithfulness than closed-source LLMs while producing more readable and diverse code than the heuristic converter.
Decompilation, the task of generating an editable, re-executable program from a rendered artifact, has emerged as a central problem across diverse modalities: binaries to source code [1], [2], raster images to SVG programs [3], [4], UI screenshots to frontend code [5], [6], and 3D shapes to CAD scripts [7], [8]. The motivation is shared across these domains: rendered artifacts are often the only representation available in practice, yet downstream use—analysis, modification, integration with existing tooling—benefits from a programmatic form that human users can read, modify, and extend [1], [2], [4]. A direct consequence is that functional equivalence alone is insufficient: a recovered program that reproduces the artifact but is unreadable defeats the purpose of decompilation [9], [10]. Effective decompilation thus requires jointly optimizing faithfulness (the program reproduces the input artifact) and readability (the program is easy to read and edit). 1
We extend this paradigm to the music modality and introduce symbolic music decompilation: generating code in a music programming language [11]–[16] corresponding to a symbolic music input. We instantiate the paradigm here through a MIDI-to-Strudel [13] task, where Strudel is a domain-specific language (DSL) for algorithmic composition [17] and live coding [18], [19] with JavaScript syntax. Unlike MIDI, which represents music as a low-level sequence of note events, Strudel represents music through high-level pattern expressions and programmatic operators that can encode repetition, layering, and temporal structure (Figure 1; see Appendix 6.1 for more examples). Recovering Strudel code from MIDI therefore does more than reproduce the input notes: it exposes latent musical structure that MIDI flattens into individual events, produces code that is readable and directly editable, and places music generation and editing in a code modality well suited to modern language model tooling.
MIDI-to-Strudel decompilation is challenging for both task-level and data-level reasons. At the task level, faithfulness alone does not define a useful decompilation: a heuristic converter can achieve near-perfect rendered-MIDI faithfulness [20], yet it does so by emitting verbose note-level code that ignores the abstractions that make Strudel useful to humans. In contrast, frontier LLMs [21]–[23] often generate readable Strudel programs, but their rendered MIDI is not faithful to the input. At the data level, this mapping is hard to learn because naturally paired (MIDI, Strudel) examples are not available. Moreover, Strudel itself is low-resource: our initial crawl found only \(688\) non-trivial human-authored programs, and open-weight LLMs rarely produce executable Strudel without adaptation, making direct execution-based optimization difficult.
We present Decomposer, a framework for post-training LLMs to decompile MIDI into Strudel code (Figure 2). The framework consists of a two-stage training pipeline. First, supervised fine-tuning (SFT) teaches a model to produce valid and idiomatic Strudel code, providing a warm start for on-policy learning. Second, reinforcement learning (RL) optimizes the decompilation objective directly. For each MIDI input, the model samples candidate programs, executes them with the Strudel runtime, and receives a composite reward that measures both rendered-MIDI faithfulness and code-level readability. Because this reward depends only on the input MIDI and the generated program, the RL stage can train on any unpaired MIDI at scale without requiring reference Strudel code. To supply the paired examples needed for the SFT warm start, we additionally construct and release Strudel-Synth, a synthetic (MIDI, Strudel) corpus obtained by generating Strudel programs with a frontier LLM and rendering each program to MIDI.
Our experiments show that Decomposer substantially improves MIDI-to-Strudel decompilation, producing programs that better reconstruct the input MIDI while preserving readable code structure. On both Strudel-Synth and real-world LMD [24], Decomposer produces markedly more faithful renderings than frontier LLMs [21]–[23], which often generate plausible-looking but poorly grounded Strudel code. At the same time, unlike a heuristic converter [20] that achieves high faithfulness by emitting verbose note-level transcriptions, Decomposer preserves readable program structure comparable to frontier LLMs.
We highlight the main contributions of this paper below:
We introduce symbolic music decompilation, the task of recovering executable, editable music-programming-language code from symbolic music input.
We propose Decomposer, a two-stage post-training framework that combines supervised fine-tuning with execution-based reinforcement learning for symbolic music decompilation.
We release Strudel-Synth, a synthetic corpus of \(21{,}174\) (Strudel, MIDI) pairs constructed by frontier LLM generation of Strudel code, followed by Strudel-to-MIDI code execution.
We find that an 8B open-weight model trained with our pipeline substantially outperforms closed-weight frontier LLMs on decompilation faithfulness, while retaining comparable readability.
We approach symbolic music decompilation as program synthesis with execution feedback, following recent decompilation work that optimizes programs against a verifiable renderer [3], [7]. Given an input MIDI segment \(x\), the goal is to model \(p(y \mid x)\) where \(y\) is a corresponding Strudel program. Specifically, for the Strudel runtime \(\mathcal{C}\) and a program \(\hat{y} \sim p(y \mid x)\), our goal is to improve faithfulness of the MIDI output from Strudel execution (\(\mathcal{C}(\hat{y}) \approx
x\)), while ensuring \(\hat{y}\) is human-readable. We parameterize a conditional language model policy \[\label{eq:policy} \pi_\theta(y \mid x)
=
\texttt{LLM}_\theta(\texttt{MidiToText}(x)),\tag{1}\] where MidiToText is a helper function that converts the MIDI input into a text representation compatible with a general LLM (see Appendix 7). Because many Strudel programs can render to the same or similar MIDI output (see Appendix 6.1 for examples), decompilation quality is defined by both rendered-MIDI
faithfulness and code-level readability.
The remainder of this section first introduces our two-stage training pipeline combining SFT and RL (Section 2.1) and details the composite reward used in the RL stage (Section 2.2). Finally, we describe Strudel-Synth, the synthetic paired (MIDI, Strudel) corpus we construct (Section 2.3).
Directly applying RL to this task from a general-purpose open-weight LLM is difficult: unlike common LLM post-training domains such as math or conventional code generation, MIDI-to-Strudel decompilation requires grounding a dense symbolic music input while generating a low-resource DSL that appears rarely in pretraining data. As a result, early rollouts from the unadapted model seldom produce valid Strudel programs, making reward signals sparse and unstable. We therefore use a two-stage pipeline: supervised fine-tuning first gives the model a prior over executable Strudel code, and reinforcement learning then optimizes the decompilation objective.
We first adapt the base LLM \(\pi_\theta\) to the MIDI-to-Strudel decompilation task using the paired dataset \(\mathcal{D}_{\text{S}}\). Each example \((x^*, y^*) \in \mathcal{D}_{\text{S}}\) consists of a Strudel program \(y^*\) and its executed MIDI representation \(x^* = \mathcal{C}(y^*)\), where \(\mathcal{C}\) denotes the Strudel runtime.2 Given the MIDI input \(x^*\), the model is trained to generate the corresponding program \(y^*\) with the standard next-token prediction objective:
\[\label{eq:sft} \mathcal{L}_{\text{SFT}}(\theta) = -\, \mathbb{E}_{y^* \sim \mathcal{D}_{\text{S}}} \!\left[\,\sum_{t=1}^{|y^*|} \log \pi_\theta(y^*_t \mid y^*_{<t},\, x^*)\right].\tag{2}\]
Without this stage, RL collapses onto a degenerate solution where the model learns to emit short, literal note lists that compile but capture almost none of the input music, and reward plateaus early at a low value. SFT instead provides a warm start that places the policy in a region on program space where rollouts express nontrivial musical structure, so that the gradient signal from the faithfulness and readability terms is meaningful (Section 3.3).
After SFT, we further train the model to optimize the decompilation objective: a generated program should faithfully reproduce the input MIDI when executed while remaining readable. We use the SFT model \(\pi_{\theta}\) as the initial policy and optimize it by maximizing the expected reward \[\label{eq:objective} \mathcal{J}(\theta) = \mathbb{E}_{x \sim \mathcal{D}_{\text{M}},\; \hat{y} \sim \pi_\theta(\cdot \mid x)} \left[ R(\hat{y}, x) \right],\tag{3}\]
where \(\mathcal{D}_{\text{M}}\) is a dataset of unpaired MIDI and \(R\) is a composite reward combining decompilation faithfulness and code readability (see Section 2.2). Because \(R\) depends only on the input MIDI and the executed program, this stage requires no reference Strudel code and can train on any unpaired MIDI at scale.
To optimize Equation 3 , we adopt Group reward-Decoupled normalization Policy Optimization (GDPO; [25]), a GRPO-style policy optimization method [26] for multi-reward objectives. For each input MIDI \(x\), the policy samples a group of \(G>0\) candidate programs, executes them with the Strudel runtime, and evaluates each program with \(K\) reward components \(r_1,\ldots,r_K\). In our setting, \(K=2\), corresponding to faithfulness and readability. A standard GRPO-style update first scalarizes these components as \(r^{(g)}=\sum_k w_k r_k^{(g)}\) where \(w_k\) are reward weights, and then normalizes the scalar rewards across the group; GDPO instead constructs a group-relative advantage for each reward component and combines the normalized advantages only afterward.
Concretely, let \(r_k^{(g)}\) denote the \(k\)-th reward component of the \(g\)-th sampled program, where \(k \in \{1,\ldots,K\}\) and \(g \in \{1,\ldots,G\}\). For each component, GDPO computes \[\label{eq:gdpo} A_k^{(g)} = \frac{r_k^{(g)}-\mu_k}{\sigma_k+\epsilon}, \quad \mu_k=\frac{1}{G}\sum_{g'=1}^{G}r_k^{(g')}, \quad \sigma_k=\mathrm{std}\!\left(\{r_k^{(g')}\}_{g'=1}^{G}\right).\tag{4}\] The final advantage is \(A^{(g)}=\sum_{k=1}^{K} w_k A_k^{(g)},\) which is used in the standard clipped group-relative policy objective to update \(\pi_\theta\). Full optimization details are provided in Appendix 8.
We design a reward function \(R(\hat{y},x)\) that evaluates a generated Strudel program \(\hat{y}\) for an input MIDI segment \(x\) along two axes: faithfulness, whether the rendered MIDI \(\hat{x} = \mathcal{C}(\hat{y})\) matches the input \(x\), and readability, whether the generated program \(\hat{y}\) is easy to understand and edit.
The faithfulness reward \(R_{\text{faith}}(\hat{x},x)\in[0,1]\) measures how accurately the rendered MIDI \(\hat{x}=\mathcal{C}(\hat{y})\) reproduces the input MIDI \(x\). We adopt the instrument-aware onset metric from the multi-track automatic music transcription literature [27]–[30]: \(R_{\text{faith}}\) is the multi-instrument onset F1 between \(\hat{x}\) and \(x\), where a predicted note is counted as correct only if it (i) matches the pitch of a reference note, (ii) has an onset within \(\pm 50\) ms of that reference onset, and (iii) is assigned the
same General MIDI program number as the reference note. The optimal pairing between reference and predicted notes is obtained by bipartite matching, and \(R_{\mathrm{faith}}\) is the resulting F1, computed with the standard
mir_eval implementation [31].
The readability reward \(R_{\text{read}}(\hat{y}) \in [0, 1]\) measures whether the generated program \(\hat{y}\) is easy to read and edit. This term is essential because optimizing \(R_{\text{faith}}\) alone admits a degenerate solution: the model can learn a literal, note-by-note transliteration that renders correctly but captures none of the repeated patterns or musical structure that make Strudel concise and editable.
Following the rubric-as-rewards paradigm [32], [33], we therefore score readability with a fixed set of \(J=12\) rubric items, each a binary yes/no check evaluated by an LLM judge. The items cover (i) general code readability such as formatting and structure, adapted from established code-readability metrics [9], [10]; and (ii) Strudel-specific criteria targeting concision and musical abstraction, such as using pattern operators and chord symbols rather than literal pitches (full rubric in Appendix 9). To turn these checks into a scalar reward, we let \(c_j : \hat{y} \mapsto \{0,1\}\) indicate whether a sampled program \(\hat{y}\) satisfies rubric item \(j\) and aggregate with uniform weights: \[\label{eq:read} R_{\text{read}}(\hat{y}) = \frac{1}{J}\sum_{j=1}^{J} c_j(\hat{y}).\tag{5}\]
Both rewards presume an executable program: a non-compiling output yields no rendered MIDI and exposes no executable structure for the readability judge to credit. We therefore gate each reward on successful compilation, assigning a penalty \(-\rho\) (\(\rho>0\)) when the Strudel runtime \(\mathcal{C}\) fails to render \(\hat{y}\): \[\label{eq:reward} \hat{R}_{m} = \begin{cases} R_{m}, & \text{if } \mathcal{C}(\hat{y}) \text{ succeeds}, \\ -\rho, & \text{otherwise}, \end{cases} \qquad m \in \{\mathrm{faith}, \mathrm{read}\}.\tag{6}\]
The gated rewards (\(\hat{R}_{\mathrm{faith}}\), \(\hat{R}_{\mathrm{read}}\)) form the two components \((r_1, r_2)\) that GDPO normalizes separately before combining (Section 2.1).
Our SFT warm start requires a paired (MIDI, code) corpus, which the public web does not supply at scale: crawling public Strudel programs yields only \(688\) non-trivial human-authored examples. Following prior work on synthetic data generation [34]–[36], we construct Strudel-Synth by distilling from a frontier LLM (Claude-Opus-4.6) to write Strudel programs and executing each through the Strudel runtime \(\mathcal{C}\) to obtain its rendered MIDI.
Our pipeline produces (MIDI, Strudel code) pairs in three stages (Figure 3). We begin by generating Strudel programs from an LLM, conditioned on a musical seed \(s_{\text{music}}\) and a code style seed \(s_{\text{code}}\) under a fixed task prompt. In the second stage, we clean the generated programs, as LLM-written programs frequently include dead voices: layers that compile but emit no MIDI events, typically from hallucinated instrument names or malformed operator chains. We first apply deterministic regex rewrites that correct these recurring mistakes, then use the Acorn JavaScript parser to split each program into its independent voices, compile each voice in isolation, and prune the silent ones together with the variable declarations they alone reference. A program is retained only if at least one voice survives. Lastly, we render each cleaned program to MIDI with the runtime \(\mathcal{C}\). Full details of the pipeline are given in Appendix 10.1.
To ensure diversity in Strudel-Synth, we independently vary two conditioning seeds during generation: a musical seed \(s_{\text{music}}\) and a code-style seed \(s_{\text{code}}\), which respectively specify the musical content of the piece and how it should be written. Specifically, \(s_\text{music}\) samples musical attributes—genre, mood, key, tempo,
meter, chords, and instrumentation—from existing music metadata [37]–[39], and we vary how many of these attributes are specified during generation. The code-style seed \(s_\text{code}\) selects among different program idioms that realize the same musical
target, ranging from mini-notation ($) based patterns to multi-section programs using arrange functions3. We render each generated program to MIDI
with a headless Strudel runtime \(\mathcal{C}\) and use the same deterministic renderer to score policy rollouts during RL (Section 2). Further details on the conditioning inputs and
runtime are provided in Appendix 10.2 and 6.2, respectively.
| Metric | Train Split | Test Split |
|---|---|---|
| # of (MIDI, Code) Pairs | 20,152 | 1,022 |
| # of Instruments | 122 | 98 |
| Avg. # Insts / Song | 3.73\(\pm\)1.22 | 3.68\(\pm\)1.26 |
| Avg. Lines of Code | 52.20\(\pm\)20.88 | 52.46\(\pm\)21.70 |
| Avg. Duration (s) | 24.17\(\pm\)19.03 | 24.10\(\pm\)18.70 |
r0.52
4.5pt
Table 1 presents the statistics of Strudel-Synth. It comprises \(21{,}174\) (MIDI, Strudel) pairs generated from Claude-Opus-4.6 with diverse conditioning seeds, roughly \(30\times\) the \(688\) non-trivial programs found from public Strudel crawl. The generated programs cover \(122\) of the \(128\) General MIDI instruments and render to short musical fragments, averaging \(24\) seconds in duration (from \(2\)s to \(90\)s).
We evaluate the performance of Decomposer and the effect of its proposed components on both synthetic and real-world MIDI data. Our experiments address the following questions:
Can Decomposer recover faithful, readable Strudel programs from MIDI? (Section 3.2)
How do Decomposer’s components affect decompilation quality? (Section 3.3)
Can Decomposer generalize across diverse MIDI datasets? (Section 3.4)
What downstream applications does music decompilation enable? (Section 3.5)
We evaluate Decomposer along three axes: faithfulness, readability, and diversity. For faithfulness, we report compile rate (Comp.) together with transcription metrics adapted from automatic music transcription [27], [29], [40]: Onset F1, Frame F1, and Multi-Instrument Onset F1 (Inst F1). Readability is measured by the proposed rubric score (Rubric) and average rank from a list-wise LLM reranker (Rank; [41]). Diversity is measured by the average pairwise CodeBLEU self-similarity (SelfCB; [42]) among compiled outputs from the same method. Further details are provided in Appendix 11.1.
Our main experiments use two corpora: Strudel-Synth (synthetic MIDI–Strudel pairs) and LMD (real-world MIDI; [24]). For LMD, we use short fragments (\({<}30\) s; \({\sim}9\)K train / \({\sim}1\)K test). To test generalization beyond these primary datasets, we further evaluate on four held-out datasets spanning different MIDI distributions: longer LMD (\(30\)–\(60\) s), GigaMIDI [43], NES-MDB [44], and Nottingham [45]. Except for longer LMD, all held-out corpora use short fragments (\({<}30\) s); see Appendix 11.2 for details.
Since MIDI-to-Strudel decompilation has no direct prior baseline, we compare against two classes of baselines. First, we prompt frontier LLMs to generate Strudel code from the MIDI input: Claude-Opus-4.6 [22], Gemini-3.5-Flash [23], and GPT-5.5 [21]. Second, we include a heuristic MIDI-to-Strudel converter [20], which directly transliterates MIDI events into Strudel code. Baseline prompts and decoding settings are described in Appendix 11.3.
All experiments use open-weight models from the Qwen3 family [46], specifically 4B (Qwen3-4B-Instruct-2507) and 8B
(Qwen3-8B) models. We split the Strudel-Synth’s training set into two disjoint halves of approximately 10K samples each. The first half is used for SFT with paired (MIDI, Strudel) examples; the second half is
used for RL, where only the MIDI input is provided. For SFT, we fine-tune each model for one epoch with LoRA [47]. For RL, we initialize from the SFT
checkpoint and train on a mixture of Strudel-Synth MIDI and short-fragment (<30s) LMD MIDI. Unless otherwise specified, we set the reward weights to \((w_1, w_2) = (1.0, 0.5)\) for
faithfulness and readability. Additional hyperparameters and compute details are provided in Appendix 11.4.
4pt
clccccccc && & & Diversity
(lr)3-6 (lr)7-8 (lr)9-9 Dataset & Method & \(\uparrow\)Comp. & \(\uparrow\)Onset & \(\uparrow\)Frame & \(\uparrow\)Inst & \(\uparrow\)Rubric & \(\downarrow\)Rank & \(\downarrow\)SelfCB
& Heuristic & 1.00 & 0.99 & 0.83 & 0.99 & 0.05 & 7.89 & 0.72
& & 0.73 & 0.50 & 0.59 & 0.42 & 0.42 & 4.41 & 0.46
& & 0.57 & 0.50 & 0.59 & 0.42 & 0.45 & 3.75 & 0.46
& & 0.80 & 0.57 & 0.63 & 0.50 & 0.39 & 2.76 & 0.47
()2-9 & Qwen3-4B & 0.06 & 0.03 & 0.08 & 0.01 & 0.37 & – & –
& + SFT & 0.58 & 0.41 & 0.46 & 0.39 & 0.58 & 4.76 & 0.42
& + SFT+RL (ours) & 0.99 & 0.71 & 0.67 & 0.70 & 0.73 & 3.65 & 0.56
()2-9 & Qwen3-8B & 0.19 & 0.05 & 0.10 & 0.02 & 0.33 & – & –
& + SFT & 0.62 & 0.46 & 0.46 & 0.41 & 0.58 & 4.96 & 0.44
& + SFT+RL (ours) & 0.99 & 0.73 & 0.69 & 0.72 & 0.74 & 3.83 & 0.55
& Heuristic & 1.00 & 0.95 & 0.67 & 0.89 & 0.09 & 7.62 & 0.77
& & 0.75 & 0.28 & 0.33 & 0.21 & 0.36 & 4.47 & 0.60
& & 0.63 & 0.22 & 0.30 & 0.17 & 0.34 & 4.37 & 0.51
& & 0.82 & 0.27 & 0.31 & 0.23 & 0.29 & 4.27 & 0.56
()2-9 & Qwen3-4B & 0.17 & 0.04 & 0.12 & 0.03 & 0.35 & – & –
& + SFT & 0.45 & 0.09 & 0.13 & 0.07 & 0.55 & 4.01 & 0.38
& + SFT+RL (ours) & 0.99 & 0.54 & 0.49 & 0.52 & 0.70 & 3.13 & 0.53
()2-9 & Qwen3-8B & 0.18 & 0.04 & 0.11 & 0.02 & 0.28 & – & –
& + SFT & 0.44 & 0.10 & 0.14 & 0.09 & 0.54 & 4.01 & 0.37
& + SFT+RL (ours) & 0.99 & 0.60 & 0.53 & 0.58 & 0.61 & 4.12 & 0.47
Table ¿tbl:tab:main? summarizes the main quantitative results. Overall, Decomposer achieves a more favorable faithfulness–readability tradeoff than any baseline: it is substantially more faithful than frontier LLMs while producing far more readable code than the heuristic converter.
Frontier LLMs (Claude-Opus-4.6, Gemini-3.5-Flash, GPT-5.5) can generate executable Strudel code, yet their renders often diverge from the input MIDI. This gap widens on LMD: compared with the best frontier LLM, Decomposer (8B) improves Onset F1 by \(+0.16\) on Strudel-Synth and by \(+0.32\) on LMD, suggesting that real-world MIDI requires stronger input grounding than general-purpose LLMs provide.
The heuristic converter is nearly perfectly faithful by construction, but its note-by-note transliteration yields the lowest readability of all methods (rubric score 0.05 on Strudel-Synth and 0.09 on LMD; worst average rank on both). Decomposer instead attains rubric scores an order of magnitude higher (0.61–0.74) and ranks comparably to frontier LLMs.
Compared with SFT alone, applying RL increases SelfCB: for the 8B model, SelfCB rises by \(+0.11\) on Strudel-Synth and \(+0.10\) on LMD. This is consistent with prior work showing that reward optimization can reduce output diversity [48]–[50]. Importantly, its SelfCB remains well below that of the heuristic converter (\(-0.17\) on Strudel-Synth and \(-0.30\) on LMD) and comparable to frontier LLMs, showing that Decomposer does not collapse to a fixed transliteration template.
SFT alone transfers poorly to real-world MIDI: for the 8B model, SFT improves Onset F1 by \(+0.41\) on Strudel-Synth but only by \(+0.06\) on LMD. Execution-based RL shows the complementary effect, giving a larger gain on LMD than on Strudel-Synth (\(+0.50\) vs. \(+0.27\) Onset F1). This improvement requires no reference Strudel programs during RL, showing that Decomposer can bootstrap from synthetic paired data and then adapt to real-world MIDI through label-free execution feedback.
We vary the readability weight \(w_2\) while keeping the faithfulness weight fixed at \(w_1=1.0\). Figure 4 reveals a clear faithfulness–readability tradeoff: with \(w_2=0\), the model produces programs that better match the input MIDI but are often verbose and close to note-by-note transcriptions. Adding a readability reward shifts the model toward more abstract and editable code, yet sacrificing faithfulness. We set \(w_2=0.5\) for our main experiments, as it improves readability substantially without the large faithfulness drop observed at higher weights.
We compare RL initialized from the SFT checkpoint against RL initialized directly from the unadapted Qwen3-8B model. As shown in Figure [fig:ablation95sft], RL without SFT plateaus early: the policy collapses to short, literal note lists that compile but capture little of the input, and readability does not improve. SFT places the policy into a region
where sampled programs compile and express nontrivial musical structure, making execution-based RL feasible.
We test whether Decomposer generalizes beyond the training distributions using the 8B model on four datasets: longer LMD (\(30\)–\(60\)s), GigaMIDI, NES-MDB, and Nottingham, covering shifts in duration, corpus source, genre, and instrumentation.
| GPT-5.5 | ||||
|---|---|---|---|---|
| 2-3 (lr)4-5 Dataset | \(\uparrow\)Onset | \(\uparrow\) Rubric | \(\uparrow\)Onset | \(\uparrow\) Rubric |
| Str-Synth | 0.70 | 0.37 | 0.86 | 0.69 |
| LMD (<\(30\)s) | 0.44 | 0.29 | 0.70 | 0.56 |
| LMD (\(30\)–\(60\)s) | 0.21 | 0.31 | 0.35 | 0.68 |
| GigaMIDI | 0.57 | 0.25 | 0.61 | 0.56 |
| NES-MDB | 0.37 | 0.28 | 0.43 | 0.61 |
| Nottingham | 0.51 | 0.28 | 0.76 | 0.45 |
r0.52
4.5pt
Table 2 shows that Decomposer outperforms GPT-5.5 across all evaluated distributions. The model transfers well to Nottingham and GigaMIDI, suggesting that it can recover compact programmatic structure beyond the synthetic Strudel distribution. Performance is lower on longer LMD excerpts and NES-MDB, indicating that robustness still varies across distributions, especially when inputs become longer or differ more substantially from the training data. At the same time, these limitations show a practical advantage of our pipeline: because the RL stage requires only unpaired MIDI, such held-out real-world corpora can be incorporated into post-training without reference Strudel code.
We discuss how Decomposer can support downstream music workflows by turning music into executable, editable code. We also provide proof-of-concept examples on our project page.
Combined with automatic music transcription [27], [29], [51], Decomposer provides a path toward audio-to-code decompilation: audio is first transcribed to MIDI, and the resulting MIDI is then decompiled into a Strudel program. Since MIDI only partially captures acoustic attributes such as timbre, dynamics, and audio effects, extending music decompilation to audio would require modeling these attributes, potentially using audio production modeling methods [52], [53] or audio language models with music understanding [54], [55].
Decomposer provides a programmatic editing interface for MIDI by converting sequential note events into Strudel programs that expose musical structures such as repetition, layering, harmony, and rhythm. These programs could be used in live coding settings or alongside DAW workflows, allowing users to edit higher-level musical structures such as chord progressions or scales in code rather than adjusting individual notes manually. Moreover, in creative settings where exploring multiple alternatives matters more than a single fixed output [56], [57], the faithfulness–readability tradeoff (Section 3.3) could be a controllable feature: users can dial the system toward faithful transcriptions for precise editing, or toward abstraction for creative exploration.
Recovering code from MIDI can make musical structure easier to inspect and reason about. Unlike a low-level MIDI sequence, a Strudel program with explicit musical structure could serve as an intermediate representation for LLM-based music understanding [55], [58], allowing models to reason over compact, interpretable programs rather than long sequences of note events. Such programs could also benefit human users: decompiled code could provide interactive material for music education [59], and its explicit repetition could aid analysis tasks such as motif discovery [60].
We discuss the most relevant literature here and provide more discussion in Appendix 12.
Decompilation casts program synthesis as inferring source programs from non-code artifacts, including source code from binaries [1], [2], [61] or user interfaces [5], [6], [62], [63], vector graphics programs from raster images [3], [4], [64], [65], and CAD programs from 3D shapes [7], [8], [66], [67]. Earlier methods rely on SFT over paired (artifact, code) data [1], [4], [66], while recent work adds RL with execution-based rewards [3], [7]. For example, [3] decompile a raster image into an SVG program by rewarding a rendered SVG’s visual similarity to the input, and [7] decompile a 3D shape into a CAD program by rewarding the executed solid’s geometric agreement with the input. We extend this paradigm to music with the MIDI-to-Strudel decompilation task, using execution-based RL that rewards faithful and readable renders.
An observed musical signal can be explained by fitting a generative process whose output reconstructs it. Prior work instantiates this idea with parametrized audio-generation processes, ranging from classic instrument tone analysis [68] and spectral modeling synthesis [69] to modern differentiable synthesis [70], [71], physical or instrument models [72]–[74], and audio-effect or mixing-chain models [52], [53], [75]–[78]. While closely related, these methods cast music analysis as parameter recovery for a predefined acoustic generator; Decomposer instead operates over symbolic music, recovering Strudel programs whose structure is inferred rather than specified a priori.
While Decomposer demonstrates that symbolic music can be decompiled into faithful and readable programs, several directions remain for future work. First, our readability reward uses a fixed checklist, which may favor abstractions that fit some musical materials better than others, such as harmonic constructs in primarily percussive music. Future work could make the checklist more fine-grained and input-conditional [79]. Second, our experiments focus on short MIDI segments; scaling to long-form music would require longer music–code data and RL reward design that captures sections, recurrence, and development. Third, our reward design does not explicitly optimize diversity; future work could incorporate diversity directly into reward design [80] or optimization [81]. Finally, music decompilation could move beyond recovering a single final program toward inferring a creative trace: the evolution of a program over time that reveals how musical structure emerges through iterative REPL interactions [82], [83].
In this paper, we have introduced symbolic music decompilation, the task of recovering executable and editable music programs from symbolic music. This task requires a model to infer the higher-level musical structure that MIDI leaves implicit, such as repetition, layering, and temporal organization. We instantiated this problem as MIDI-to-Strudel decompilation and presented Decomposer, a two-stage post-training framework that combines supervised learning on synthetic (Strudel, MIDI) pairs with reinforcement learning on unpaired MIDI. Across synthetic and real-world benchmarks, Decomposer improves the faithfulness–readability tradeoff over both heuristic conversion and frontier LLMs, producing programs that better preserve the input music while remaining substantially more readable than note-by-note transcriptions. We hope this work helps facilitate future research on music decompilation systems that recover not only performed notes, but also the editable programmatic structure that makes them interpretable, reusable, and extensible.
We thank Michael Freeman for sharing his ModArchive-to-MIDI conversion code and resources, which made our genre-wise evaluation of Decomposer possible. We also thank Sihyun Yu for insightful discussions and feedback throughout the project. This work was supported by the Humanities and AI Virtual Institute (HAVI) at Schmidt Sciences. Apurva Gandhi is supported by the Amazon AI PhD Fellowship.
A central property of the MIDI-to-Strudel task is that many distinct Strudel programs can render to the same (or nearly the same) musical content (MIDI), while exposing different degrees of musical structure. Figure 5 shows four Strudel programs that produce the same short loop of drums, bass, and electric guitar, but differ in how they represent the underlying musical ideas:
Event-level transliteration. A program can stay close to the MIDI event representation by converting note events into fixed-resolution mini-notation patterns (Figure 5 (a)). At each grid step, the code emits the corresponding drum hit, pitch, or rest. This representation can preserve precise timing and pitches, but it produces long event lists that obscure repeated musical structure.
Pattern-level abstraction. The same material can be written more compactly by using pattern operations and symbolic musical units (Figure 5 (b)). Repeated events can be compressed into shorter patterns, while explicit pitch collections can be replaced with chord symbols (chord()) shaped by rhythmic operators such as struct(). This exposes rhythm and harmony hidden in a literal event list.
Shared structure and layering. A program can further factor out musical information that is shared across voices (Figure 5 (c)). For example, a chord progression can be defined once and reused by different layers, so that each layer is expressed relative to the current chord rather than as a separate list of absolute pitches. Simultaneous voices can also be grouped with stack(), making the layered structure of the music explicit and reusable in the program.
Arrangement-level organization. At a higher level, a program can separate reusable voice definitions from the temporal organization of the piece (Figure 5 (d)). Individual parts can be named once and then combined over time using arrange(), allowing users to edit each layer independently and rearrange sections without rewriting the note content.
The runtime \(\mathcal{C}\) is a headless execution server built around the open-source Strudel implementation.4 It provides two interfaces used throughout our pipeline. First, it executes generated Strudel programs and returns symbolic note events, which are serialized to MIDI for dataset construction, reward computation, and evaluation. Second, it parses programs into abstract syntax trees (ASTs), which we use for data cleaning and code-level analyses.
In implementation, a Node.js process loads the Strudel JavaScript engine and handles program execution and AST parsing, while a FastAPI wrapper exposes these functions to Python workers and performs MIDI serialization. Programs that fail to compile or exceed the timeout (30s) are treated as invalid and receive the compilation penalty in Equation 6 during RL.
Unlike MIDI, a Strudel program does not explicitly specify a finite duration. It instead represents a time-queryable musical pattern [13]: given a requested time span, the runtime returns the events active within that span, including their onset, pitch, and sound name. To obtain a finite MIDI rendering, \(\mathcal{C}\) chooses a segment length from the program’s musical content rather than from an arbitrary query window: it first queries the pattern over a sufficiently long fixed window (\(90\) s in our experiments) and trims the resulting event sequence to one complete loop. Specifically, we use the Knuth–Morris–Pratt algorithm [84] to identify the fundamental period of the queried event sequence, and retain only the events within the first period. The trimmed events are then serialized to a MIDI file by the FastAPI wrapper using the mido package [85]. Each Strudel sound is mapped to a General MIDI program for melodic instruments or to a channel-\(9\) drum note for percussion samples using fixed lookup tables [86]. Note names and numeric pitches are resolved to MIDI note numbers, and Strudel gain and velocity attributes are soft-clipped to the MIDI velocity range.
For code-level analyses, \(\mathcal{C}\) exposes a parsing interface that returns the AST of a Strudel program. Since Strudel is implemented as a JavaScript-based DSL, we use the Acorn JavaScript parser [87] to parse generated programs into an AST before execution. We use the resulting ASTs in two parts. During dataset construction (Section 2.3; Appendix 10), the parser identifies top-level musical voices and traces the variable declarations on which each voice depends. We use this information to clean LLM-generated programs by retaining the code needed for each extracted voice while removing invalid fragments. During evaluation, we use ASTs to compute the TSED diversity metric (Section 3.1; Appendix 11.1). Specifically, each program is serialized as a labeled tree of its syntax-node types, and diversity is measured by the normalized tree-edit distance between pairs of such trees.
Figure 5: Different Strudel representations of the same musical loop. The same musical content can be expressed by many distinct Strudel programs. Among many possible programs for the same musical content, we show four illustrative examples that render to the same short loop of drums, bass, and electric guitar. They differ in their levels of abstraction and coding idioms, ranging from literal event-level representation to pattern-, layer-, and arrangement-level abstractions.. a — Event-level transliteration. Musical contents are quantized into mini-notation strings that explicitly list drum hits, pitches, and rests., b — Pattern-level abstraction. The bassline compacts events with pattern operators, and the guitar layer represents harmony with chord() and rhythm with struct()., c — Shared structure and layering. A shared chords variable anchors the bassline and the guitar harmony, while stack() groups the layers as simultaneous voices., d — Arrangement-level organization. Voices are factored into named variables, and their combinations are sequenced into sections with arrange().
This section details the MidiToText helper of Equation 1 , which converts a MIDI segment into the textual representation used as input to the language model. An example is shown below:
[acoustic_grand_piano] C4@0.00 E4@0.25 G4@0.50 C5@1.00
[acoustic_bass] C2@0.00 G2@0.50 C2@1.00
[drum] bass_drum@0.00 closed_hi_hat@0.25 closed_hi_hat@0.50 snare@0.75
We serialize MIDI as symbolic, instrument-wise note events, making the input the input closer to musical notation and to the layered structure of Strudel programs. Specifically, we first parse the input MIDI into a flat list of note-on events, where each event is represented by its onset, instrument, and pitch. Events are then grouped by instrument and serialized as a sequence of pitch@onset tokens sorted by onset. For melodic instruments, pitches are rendered as note names (e.g., C4, F#3); for drums, pitches are rendered as drum names (e.g., closed_hi_hat). This representation choice yields tokens closer to common textual music notation and idiomatic Strudel code than raw MIDI indices, while also keeping each musical part contiguous and exposing the segment’s layered structure without requiring additional notation.
Strudel represents time in its native unit called cycles rather than seconds: here, tempo specifies how many cycles occur per second or minute, and events are placed at positions within these cycles.5 For our MIDI representation, we adopt the convention that one cycle corresponds to one measure of the input segment. Under this convention, onset positions become normalized coordinates within the measure: for example, in \(4/4\), notes on the four beats occur at , , , and , instead of at absolute timestamps in seconds.
To convert MIDI timestamps to cycle positions, we use the tempo and meter of the segment, where BPM denotes beats per minute and meter denotes the number of beats per measure. Under our one-cycle-per-measure convention, one cycle spans \(T_{\text{cycle}} = (60 / \text{BPM}) \times \text{meter}\) seconds. We then convert each MIDI onset time \(t_{\text{sec}}\) to its cycle coordinate onset \(t_{\text{cycle}}\) by \[\label{eq:cycle95conversion} t_\text{onset} = \frac{t_{\text{sec}}}{T_{\text{cycle}}} = \frac{t_{\text{sec}}}{(60 / \text{BPM}) \times \text{meter}} .\tag{7}\]
In the text representation, we prepend a tempo–meter header (e.g., ) and express each onset using this cycle coordinate. When BPM and meter metadata are available in the MIDI file, we use them directly. Otherwise, we render the MIDI to audio with
FluidSynth [88] and use madmom [89] to
estimate beat and downbeat locations, from which we derive BPM and meter.
The serialized MIDI representation is inserted into a chat prompt for MIDI-to-Strudel decompilation. The system instruction and user message is shown below.
You are an expert live coder in Strudel, a JavaScript-based platform that ports the TidalCycles pattern language to the web. Your task is to decompile MIDI events into idiomatic Strudel code.
STRUDEL QUICK REFERENCE
Basic syntax
Chain functions with dot notation, e.g., s("bd").gain(0.8).fast(2).
Use double quotes for mini-notation strings.
Use single quotes for ordinary JavaScript strings.
Use backticks for multi-line mini-notation strings.
Core functions
| Tempo | setcpm(bpm/bpc), e.g., setcpm(120/4) |
| Sound | s(), bank(), n(), begin(), end(), loop() |
| Pitch | note(), scale(), chord(), voicing(), rootNotes() |
| Timing | fast(), slow(), off(), stack(), rev() |
| Dynamics | gain(), velocity(), attack(), delay(), sustain(), release(), postgain() |
Core mini-notation
"bd*4" |
Repeat four times per cycle |
"<bd sd>" |
Alternate across cycles |
"[bd sd]" |
Sequence within one cycle |
"bd, hh" |
Layer simultaneously |
" " |
Silence or rest |
"x(5,16)" |
Euclidean rhythm with five hits over sixteen steps |
INPUT FORMAT
Header
The first line gives the tempo and meter of the source MIDI: [BPM=120 Meter=4].
Use this information to set the tempo at the top of the program: setcpm(120/4).
If the header is missing, use setcpm(120/4).
Events
Each following line contains one instrument part: [instrument_name] pitch@cycle_onset pitch@cycle_onset ... Here, instrument_name is a General MIDI program name or drum; pitch is either a note name,
such as C4 or F#3, or a drum name, such as bass_drum or closed_hi_hat; and cycle_onset is the onset position in cycle units. For example, in \(4/4\),
0.00, 0.25, 0.50, and 0.75 correspond to the four beats of one measure.
Example
[BPM=120 Meter=4]
[acoustic_grand_piano] C4@0.00 E4@0.25 G4@0.50 C5@1.00
[drum] bass_drum@0.00 closed_hi_hat@0.25 closed_hi_hat@0.50 snare@0.75
TRANSLATION GUIDELINES
1. Structure
Group instruments into separate musical layers, usually inside stack().
Identify repeated bars or motifs and express them with concise mini-notation.
MIDI note durations are not explicitly encoded; infer reasonable durations from onset gaps and musical context.
2. Timing
Map cycle onsets directly to rhythmic subdivisions.
Quantize to the smallest common subdivision that preserves the rhythm.
Use for rests, *N for repeated hits, [...] for subdivisions, and <...> for alternation.
3. Pitch
For melodic instruments, use note() with note names or n().scale() when a scale is clear.
For drums, use s() with sound banks.
For same-instrument notes that occur at the same onset, prefer chord() with voicing() when the harmony is recognizable; otherwise use explicit grouped notes.
Do not merge simultaneous notes from different instruments into a single chord layer.
4. Musical structure
Prefer pattern abstraction over event-by-event transliteration.
Use stack() for simultaneous parts and arrange() for section-level organization.
Use <...> for alternating cycles and fast() for intra-cycle density.
Separate bass, chords, melody, and drums when they play distinct musical roles.
STYLE GUIDELINES
Ensure readability: voices, structure, and harmonic intent should be visible at a glance.
Add short comments that label each major voice, such as // walking bass or // backbeat drum groove.
Extract reused chord progressions, rhythms, motifs, or sounds into named const variables.
Break long method chains across lines, with one method per line.
Prefer chord() and voicing() over long explicit note lists when the harmony is clear.
Prefer scale degrees with scale() when the line belongs to a clear key.
Avoid raw MIDI numbers such as n(60).
Use Strudel repetition operators such as hh*8 or <[bar1] [bar2]> instead of spelling out repeated events.
Separate voice blocks with blank lines.
CRITICAL CONSTRAINTS
Output format: return only raw Strudel code. Do not use Markdown code fences, explanations, or conversational text.
Sound equivalence: the rendered program should be audibly equivalent to the input MIDI in rhythm, pitch, density, and instrumentation.
Idiomatic code: prefer clean, readable Strudel patterns over verbose transliterations.
Analyze the following note events and write corresponding Strudel code.
During training, we vary the natural-language request in the user message while keeping the underlying MIDI representation fixed. The request is sampled from a set of paraphrased task instructions:
Analyze the following MIDI events and write corresponding Strudel code.
Can you help me turn this MIDI into Strudel live coding syntax?
Convert the following MIDI events into Strudel code.
Encode the following MIDI events into Strudel notation.
Express this MIDI sequence as a Strudel program.
Generate Strudel code from the MIDI below.
Generate the Strudel code that reproduces the following MIDI events.
Given the following MIDI events, write the equivalent Strudel pattern.
Interpret this MIDI and express it as Strudel live coding syntax.
Reproduce the following MIDI pattern in Strudel.
Rewrite the following MIDI events using Strudel live coding syntax.
Take a look at the MIDI below and write Strudel code that reproduces it.
Transform this MIDI representation into Strudel.
Translate this MIDI sequence into Strudel code.
Write Strudel code that is functionally equivalent to the following MIDI.
We provide a description of the two policy-optimization methods referenced in the main paper (Section 2.1). We first explain Group Relative Policy Optimization (GRPO) in Section 8.1 and Group reward-Decoupled normalization Policy Optimization (GDPO) in Section 8.2. For detailed explanations and ablations, please refer to the original papers [25], [26].
We consider a general verifiable generation setting in which a policy \(\pi_\theta\) maps an input \(x\) to an output \(\hat{y}\), and an external evaluator assigns rewards after generation. The evaluator may be non-differentiable, since the policy-gradient objectives below only require scalar reward values and do not differentiate through the evaluation procedure. This setting covers our training setup, where rewards are computed from the executed output and auxiliary evaluation metrics.
GRPO [26] is a policy-gradient method that estimates advantages relative to a group of completions sampled for the same input prompt, removing the separately trained value network of PPO [90]. For each input \(x\), the old policy samples \(G\) candidates \(\{\hat{y}^{(g)}\}_{g=1}^{G}\sim\pi_{\theta_{\text{old}}}(\cdot\mid x)\). Each candidate is scored with a scalar reward \(r^{(g)}=r(x,\hat{y}^{(g)})\), and the rewards are normalized within the group: \[\label{eq:app:grpo-adv} A^{(g)} = \frac{r^{(g)} - \mu}{\sigma + \varepsilon}, \quad \mu = \frac{1}{G}\sum_{g=1}^{G} r^{(g)}, \quad \sigma = \mathrm{std}\!\left(\{r^{(g)}\}_{g=1}^{G}\right).\tag{8}\]
The policy is then updated by maximizing a token-level clipped surrogate with a KL penalty to a reference policy \(\pi_{\text{ref}}\): \[\begin{align} \label{eq:app:grpo} \mathcal{J}_{\text{GRPO}}(\theta) &= \mathbb{E}\!\left[\frac{1}{G}\sum_{g=1}^{G} \frac{1}{|\hat{y}^{(g)}|} \sum_{t=1}^{|\hat{y}^{(g)}|} \min\!\left( \rho_t^{(g)} A^{(g)},\; \mathrm{clip}\!\left(\rho_t^{(g)},\, 1{-}\epsilon,\, 1{+}\epsilon\right) A^{(g)} \right)\right] \nonumber \\ &\quad - \beta\, \mathbb{D}_{\text{KL}}\!\left[\pi_\theta \,\big\|\, \pi_{\text{ref}}\right], \end{align}\tag{9}\]
where \(t\) indexes positions in the generated token sequence and \[\rho_t^{(g)} = \frac{ \pi_\theta(\hat{y}_t^{(g)}\mid \hat{y}_{<t}^{(g)}, x)}{\pi_{\theta_{\mathrm{old}}}(\hat{y}_t^{(g)}\mid \hat{y}_{<t}^{(g)}, x)}\] denotes the per-token importance ratio between the current and old policies. Here, \(\epsilon\) is the clipping threshold, \(\beta\) is the KL coefficient, and \(\mathbb{D}_{\text{KL}}\!\left[\pi_\theta \,\big\|\, \pi_{\text{ref}}\right]\) denotes the averaged token-level KL regularization term.
GRPO assumes a scalar reward for each sampled output; when the training signal has multiple reward components, a straightforward way to apply GRPO is to first scalarize them and then apply the group-wise normalization in Equation 8 . Concretely, for each sampled output \(\hat{y}^{(g)}\), one may form \[\label{eq:app:wsum} r^{(g)} = \sum_{k=1}^{K} w_k r_k^{(g)},\tag{10}\] where \(r_k^{(g)}=r_k(x,\hat{y}^{(g)})\) is the \(k\)-th reward component and \(w_k\) is its corresponding weight. Because normalization is applied only after scalarization, high-variance components can dominate both the scalar reward and the resulting advantage, even when the intended objective assigns non-negligible weight to multiple components.
GDPO [25] instead normalizes each reward component within the group before aggregation. For each component \(k\), GDPO computes a component-wise group-relative advantage \[\label{eq:app:gdpoadv} A_k^{(g)} = \frac{r_k^{(g)}-\mu_k}{\sigma_k+\varepsilon}, \quad \mu_k=\frac{1}{G}\sum_{g=1}^{G} r_k^{(g)}, \quad \sigma_k = \mathrm{std}\bigl(\{r_k^{(g)}\}\bigr),\tag{11}\]
aggregates the normalized advantages with the weights \(w_k\): \[\label{eq:app:gdpowsum} A_{\text{sum}}^{(g)} = \sum_{k=1}^{K} w_k A_k^{(g)},\tag{12}\] and finally applies a batch-wise normalization across all rollouts in the batch \(\mathcal{B}\) to keep the advantage magnitude from growing with the number of objectives: \[\label{eq:gdpo-batchnorm} \hat{A}_{\text{sum}}^{(g)} = \frac{A_{\text{sum}}^{(g)} - \mathrm{mean}\bigl\{A_{\text{sum}}^{(g')} \,\big|\, g' \in \mathcal{B}\bigr\}}{\mathrm{std}\bigl\{A_{\text{sum}}^{(g')} \,\big|\, g' \in \mathcal{B}\bigr\} + \varepsilon}.\tag{13}\]
The policy is updated with the clipped surrogate of Equation 9 , with \(A^{(g)}\) replaced by \(\hat{A}_{\text{sum}}^{(g)}\).
The readability reward \(R_{\text{read}}\) (Section 2.2) is computed by an LLM judge using a fixed binary checklist of \(J=12\) items. We use rule-based binary criteria to obtain a stable reward signal in a domain where many programs can be valid decompilations, following the rubric-as-rewards paradigm [32], [33], [79]. The checklist combines general code readability criteria, adapted from prior readability metrics [9], [10], with Strudel-specific criteria for concision and musical abstraction. Specifically, items \(1\)–\(5\) capture general readability, such as variable reuse, comments, and formatting, while items \(6\)–\(12\) reward the use of Strudel’s pattern operators and musical abstractions over note-by-note transliteration. The score is the fraction of satisfied items: \(R_{\text{read}}=\tfrac{1}{12}\sum_j c_j \in [0,1]\).
We use Qwen3.6-35B-A3B [91] as the judge. We follow the Qwen recommended temperature setting of \(0.7\), set the maximum output length to \(4000\) tokens, and disable thinking (enable_thinking=false). The full judge system instruction, including the rubric, is shown below.
You are an expert Strudel code reviewer. Strudel is a JavaScript-based live coding language for music. Given a Strudel program, your task is to evaluate its readability using the 12 binary checklist items below. Apply each criterion literally and independently. Return only valid JSON in the following format:
{"items":[{"id":"1","reasoning":"...","score":0},...,{"id":"12",...}]}
Include all 12 items in order. Each score must be exactly 0 or 1. Keep each reasoning to one short sentence.
Item 1: Variable usage. Score \(1\) iff the code declares at least one top-level const X = … or let X = … and reuses X elsewhere.
Item 2: Comment usage. A meaningful comment is a // or /* */ comment with at least two words (e.g., , // syncopated drum groove). Decoration-only comments do not count. Score \(1\) iff \(\text{meaningful\_comments}/\max(\text{code\_lines},1)\ge 0.10\).
Item 3: Repeated method chains. Score \(0\) iff the code repeats the same contiguous sequence of \(\ge 4\) chained dot-method calls at least twice. The repeated sequence
must match exactly in both method names and arguments. Otherwise score \(1\). Do not count expression heads such as note(…), stack(…), or chord(…).
Item 4: Line length. Over non-empty lines, score \(1\) iff the average line length is at least \(10\) characters and the maximum line length is at most \(90\) characters.
Item 5: Blank line usage. Score \(1\) iff blank lines make up at least \(10\%\) of all code lines, i.e., \(\text{blank\_lines}/\max(\text{code\_lines},1)\ge 0.10\).
Item 6: Mini-notation compression. Inspect each mini-notation string passed to note, n, sound, or s. Score \(0\) iff it contains any avoidable
repetition: the same token/group appears four or more times in a row, a phrase repeats in a row, or a <…> alternation repeats the same element. Ignore repetitions already expressed with *N or !N. Otherwise score
\(1\).
Item 7: Time transformation. Score \(1\) iff the code uses at least one timing-change methods: .fast, .slow, .ply, .iter, .early,
.late.
Item 8: Rhythm structure.. Score \(1\) iff the code uses at least one of rhythm-structure method: .struct, .mask, .euclid, .euclidLegato,
.euclidRot, .swing, .swingBy.
Item 9: Pattern layering. Score \(1\) iff the code uses at least one of pattern-layering method: .jux, .layer, .superimpose.
Item 10: Scale usage. Score \(1\) iff the code uses chained .scale(…) to map scale degrees to pitches (e.g., n("0 2 4").scale("c:major")).
Item 11: Chord voicing. Score \(1\) iff the code uses chord(…) with a voicing-related method in the same expression chain: .voicing(…) or .rootNotes(…).
Item 12: Pitch shift. Score \(1\) iff the code uses at least one pitch-shift operation: .transpose(…), .add(note(…)), .sub(note(…)), or
.add/.sub with an integer semitone count. Small-float .add(<1) for detuning and non-pitch strings do not count.
This section provides additional details on the construction of Strudel-Synth, the synthetic paired corpus used to train Decomposer (Section 2.3). We describe the overall generation pipeline in Section 10.1 and the conditioning inputs used to control musical content and coding style of the generated Strudel programs in Section 10.2.
We detail each stage of the data generation pipeline used to construct Strudel-Synth.
We prompt Claude-Opus-4.6 [22] with a system prompt instructing it to write a complete, idiomatic Strudel program, conditioned on a musical seed \(s_{\text{music}}\) and a code-style seed \(s_{\text{code}}\) supplied in the user message (detailed in Appendix 10.2). To ensure deterministic MIDI rendering, we forbid random modifiers and constructs unsupported by the headless engine, including visuals, sliders, and external samples. We sample with temperature \(1.0\) and up to \(4{,}096\) new tokens, with extended thinking disabled. The system prompt is shown below.
You are an expert live coder specializing in Strudel, a JavaScript-based platform that ports the TidalCycles pattern language to the web. Your task is to translate natural language music descriptions into executable, idiomatic Strudel code.
STRUDEL QUICK REFERENCE
Basic syntax
Chain functions with dot notation.
Use double quotes (") for single-line mini-notation patterns.
Use backticks (‘) for multi-line mini-notation strings.
Use single quotes (’) for regular strings that are not parsed as mini-notation.
Core mini-notation
"bd*4" |
Repeat four times per cycle |
"<bd sd>" |
Alternate across cycles |
"[bd sd]" |
Sequence within one cycle |
"bd, hh" |
Layer simultaneously |
" " |
Silence or rest |
"bd(3,8)" |
Euclidean rhythm shorthand |
"c@3 e" |
elongate a note with @ |
Core functions
| Tempo | setcpm(bpm/bpc), e.g., setcpm(120/4) |
| Sound | s(), bank(), n(), begin(), end(), loop() |
| Pitch | note(), scale(), chord(), voicing(), rootNotes() |
| Timing | fast(), slow(), off(), stack(), arrange(), rev(), jux() |
| Rhythm | struct(), mask(), ply(), euclid(), swingBy() |
| Dynamics | gain(), velocity(), attack(), sustain(), release(), adsr(), postgain() |
| Modulation | fm(), fmh(), vib() |
| Filters | lpf(), hpf(), bpf(), bpq() |
| Effects | room(), delay(), chorus(), compressor() |
General advice
Use $: labeled statements or stack() to layer simultaneous parts.
Use arrange() for song-level section structure.
Use voicing() for chord progressions and rootNotes(n) for bass lines from chord symbols.
Use struct() for rhythmic gating; use off() or superimpose() for harmonic echoes.
Use Euclidean rhythms (n,k) and swingBy() for natural-feeling grooves.
Use synths such as sawtooth, square, sine, and triangle, or GM soundfonts such as gm_acoustic_bass and gm_electric_piano_1.
INPUT FORMAT: MUSIC DESCRIPTION
The input is a natural-language music description specifying any combination of genre, mood, instrumentation, tempo, key, time signature, or structural ideas. Descriptions range from terse prompts such as “chill lo-fi beat” to detailed requests. Treat missing details as creative latitude.
GENERATION GUIDELINES
1. Interpret the description
Infer genre conventions: tempo, instruments, rhythmic feel, and harmonic language.
Plan the main sound layers from the description, such as melody, drums, bass, and pads.
2. Build harmony
Express chord progressions symbolically, e.g., chord("<Am7 Fmaj7 G C>").voicing().
Control register with anchor (e.g., .anchor("c4")) and mode
(e.g., .mode("below"), .mode("duck"), or .mode("above")).
Derive basslines using chord symbols, e.g.,
chord(chords).rootNotes(2).s("gm_acoustic_bass").
Use interval stacks such as note("<[c3,e3,g3] [a2,c3,e3]>") for explicit voicings.
3. Build rhythm and structure
Use stack() or $: for simultaneous parts, and arrange([n, section], ...)
for song sections.
Use Euclidean rhythms for organic feels, e.g., s("bd(3,8)") or s("hh(5,8)").
Add swing with .swingBy(1/8, 4) or .swing(4).
Add variation with pattern effects, e.g., .add("<0 1 2 1>"), or .off(1/8, add(7)).
4. Apply style and effects
Match effects to genre, such as reverb for ambient or jazz, compression for electronic music, and delay for dub.
Keep patterns concise by using mini-notation repetition and <...> alternation instead of spelling out every bar.
Use layer() or superimpose() to add harmonic richness without writing extra voices manually.
FORBIDDEN CONSTRUCTS
The following constructs must not be used, even if they appear in a few-shot example.
Visuals:
.color(), ._scope(), ._pianoroll(), ._waveform(), Hydra, initHydra, src(), .out()
Randomness:
rand, irand, perlin, Math.random(), choose, wchoose, chooseCycles, randcat,
wchooseCycles, wrandcat
Probabilistic modifiers:
degradeBy, degrade, undegradeBy, undegrade, sometimesBy, sometimes, someCyclesBy, someCycles, often, rarely, almostNever, almostAlways, never, always, "bd?", "bd | sd"
Interactivity:
slider(), button(), xy(), midin(), cc(), initMidi(), await midin()
External samples:
loadSamples(), samples(‘url’), and any explicit .wav or .mp3 path string
CRITICAL CONSTRAINTS
Output format: return only raw Strudel code. Do not use Markdown code fences, conversational text, or explanations. The output must be directly copy-pasteable into the Strudel REPL.
Fidelity: closely follow the musical intent of the description.
Idiomatic code: prefer clean, readable Strudel patterns with mini-notation abstractions rather than verbose literal sequences.
Tempo: always use setcpm(bpm/bpc), e.g., setcpm(120/4) or setcpm(90/2).
LLM-written programs frequently contain dead voices: music layers that compile successfully but emit no audible events. These typically arise from hallucinated instrument names or malformed operator chains. For example, if a voice in the generated program references a sound name that does not exist in the Strudel sound library, such as $: note("c# e g a").sound("sound_404"), the program may still compile during rendering (Stage 3), but that voice would not produce any audible events. Such dead voices are problematic for training: the code suggests an active musical layer, while the rendered MIDI contains nothing, creating a mismatch between the program and the music that the model would otherwise learn as a valid pattern. We address this in two passes: regex rewriting and voice-level pruning.
We first apply a fixed sequence of deterministic regular expression rewrites that correct recurring syntactic mistakes we observed in early generations. The rewrites used in our pipeline are summarized in Table 3; together, they correct \({\sim}37\%\) of programs at least once before any compilation is attempted6.
| Rewrite | Description |
|---|---|
| [1pt][0pt] | |
| \(\to\) gm_epiano1 | Correct the invalid e-piano alias to the valid sound name. |
| \(\to\) sh | Replace the invalid bare shaker alias with sh; keep shaker_small and shaker_large as-is. |
| [1pt][0pt] | |
| \(\to\) .voicing() | Collapse .voicings(arg) calls to .voicing(). |
| Repair note(X.voicing()) by rewriting it as a chord().voicing() call. | |
| Rewrite voiced note() / n() heads as chord(). | |
| [1pt][0pt] | |
| \(\to\) \(\emptyset\) | Remove lambda-style .note() calls. |
| Remove empty .note() calls after tonal operators. | |
| \(\dots\)).note() \(\to\) stack(\(\dots\)) | Remove a .note() appended to the stack(\(\dots\)) call. |
| [1pt][0pt] | |
| restructure | Reorder s(sample).note(expr.rootNotes(N)) to expr.s(sample), correcting broken bass-line chains. |
| [1pt][0pt] | |
| section lengths | Cap each arrange() section at \(\leq 2\) cycles and the total arrangement at \(\leq 12\) cycles to prevent overly long outputs. |
| When a .struct(\(\dots\)) pattern contains no trigger character (, t, or x), replace the first with . |
5pt
We then parse each rewritten program with Acorn JavaScript parser [87] and use the resulting AST to identify top-level musical voices, following the representative Strudel structures introduced earlier in Appendix 6.1: mini-notation statements ($:), stack arguments, and arrange tracks (Figures 5 (b)-5 (d)). For each voice, we record its character span in the original source and collect the top-level variable declarations (e.g., let chords = chord(“<Bbm9 Fm9>/4”)) and helper definitions (e.g., samples(‘github:eddyflux/crate’)) it depends on. We render this isolated program with the runtime \(\mathcal{C}\) using a \(60\) s window, and remove the voice if it produces zero audible events. Finally, we remove top-level declarations that are no longer referenced and retain a program only if at least one voice survives. This full parsing-and-pruning procedure is illustrated in Figure 6.
Each cleaned program is rendered to MIDI with the runtime \(\mathcal{C}\), which queries the pattern over a fixed window (\(90\) s) and trims the result to one fundamental loop (Appendix 6.2). We discard renderings shorter than \(2\) s, which are typically degenerate programs that produce a very short event sequence. The surviving (MIDI, Strudel) pairs constitute Strudel-Synth; summary statistics are reported in Table 1.
We condition each generation on two inputs: a musical seed \(s_{\text{music}}\) that specifies what kind of piece to write, and a code-style seed \(s_{\text{code}}\) that specifies how the piece should be expressed as a Strudel program. These two seeds vary independently so that the same musical target can be realized in different program idioms and a given idiom can be applied to diverse music.
| # Attrs | Source | Music Attributes |
|---|---|---|
| \(1\) | EveryNoise | |
| \(5\) | ||
| TheoryTab | ||
| , | ||
| \(7\) | MidiCaps | |
| , , | ||
| , | ||
r0.48
3.6pt
The musical seed specifies the intended musical content of each generated piece and is injected into the user message of the generation prompt. We construct these seeds from three metadata sources: (i) EveryNoise [37], a fine-grained vocabulary of genres from Spotify listening data; (ii) TheoryTab [39], a collection of popular song analyses; and (iii) MidiCaps [38], a captioned MIDI dataset built on LMD [24] that combines MIR-extracted metadata with LLM-generated natural-language descriptions. To control how much musical information is given to the LLM generator, we use prompts at several levels of musical detail, from genre-only prompts to richer prompts that include multiple musical attributes. Table 4 summarizes these conditioning modes and their exposed attributes.
The code-style seed selects among program idioms that realize similar musical targets with different code structure: (i) labeled mini-notation patterns using $:; (ii) layered programs built with stack; and (iii) multi-section arrangements using arrange (see Figures 5 (b)–5 (d) for examples). To further ground Strudel-Synth in real Strudel practice, we additionally draw structural templates from web-crawled programs and provide them as references for a randomly selected subset of generations. Specifically, using the AST-based procedure in Appendix 10.1, we parse each program into top-level musical voices, sample one or two voices, and include the resulting fragments as a reference for Strudel coding patterns. These templates are used only to guide code structure and operator use; the model is still instructed to compose new musical content. Each code-style seed is implemented with a style-specific user prompt with an optional reference-template block for template-conditioned generations; we list the prompts below.
STRUCTURE
Your output’s top-level structure must use labeled Strudel statements of the form $: pattern, with one statement per musical layer.
Use \(3\)–\(5\) top-level $: statements, corresponding to musical layers such as drums, bass, chords, melody, or pads.
Each layer should be written primarily with concise mini-notation patterns.
Do not use a top-level stack or arrange call; keep layers as separate $: statements.
Use idiomatic pattern operations such as struct(), fast(), slow(), off(), ply(), swingBy(), chord(...).voicing(), and n(...).scale(...) when
useful.
Keep the program readable by reusing variables for shared chord progressions, scales, rhythms, or sound choices.
{% Optional block: included only when a reference program is provided.%}
REFERENCE TEMPLATE
Use the following reference only as a guide for coding style and program organization. Write entirely new musical content from scratch, including your own choice of key, harmony, rhythm, and instrumentation.
The reference may contain forbidden constructs, such as randomness, visuals, interactivity, or external samples. Do not reproduce those constructs; use deterministic and headless-compatible alternatives instead.
{% End optional block. %}
Create music in Strudel live coding language using the following specifications:
STRUCTURE
Your output’s top-level structure must be a single stack(...) call containing \(3\)–\(5\) inner streams.
Each stream should loop within \(4\)–\(8\) cycles.
Inside each stream, you may use any idiomatic Strudel pattern freely.
Do not use top-level $: statements; wrap all streams inside a single stack(...) instead.
Use clear variable names and add comments when they improve readability.
{% Optional block: included only when a reference program is provided.%}
REFERENCE TEMPLATE
Use the following reference only as a guide for coding style and program organization. Write entirely new musical content from scratch, including your own choice of key, harmony, rhythm, and instrumentation.
The reference may contain forbidden constructs, such as randomness, visuals, interactivity, or external samples. Do not reproduce those constructs; use deterministic and headless-compatible alternatives instead.
{% End optional block. %}
Create music in Strudel live coding language using the following specifications:
STRUCTURE
Your output’s top-level structure must be a arrange([cycles, section_expr], ...) call.
Use \(2\)–\(4\) sections.
Each section should last \(2\)–\(3\) cycles, with a total length of at most \(12\) cycles.
Each section_expr could be any idiomatic Strudel expression, including stack(...), mini-notation, scale-degree melodies, symbolic chord progressions, drum banks, polyrhythms, and effects.
Use clear variable names and add comments when they improve readability.
{% Optional block: included only when a reference program is provided.%}
REFERENCE TEMPLATE
Use the following reference only as a guide for coding style and program organization. Write entirely new musical content from scratch, including your own choice of key, harmony, rhythm, and instrumentation.
The reference may contain forbidden constructs, such as randomness, visuals, interactivity, or external samples. Do not reproduce those constructs; use deterministic and headless-compatible alternatives instead.
{% End optional block. %}
Create music in Strudel live coding language using the following specifications:
We organize evaluation metrics along the three axes of faithfulness, readability, and diversity introduced in Section 3.1. The main result table (Table ¿tbl:tab:main?) reports the metric mean over the evaluation set under a single generation per input. For the generalization datasets (Section 3.4, Table 2) and additional evaluations (Appendix 13), we instead report Best@\(K\) (\(K=5\)): each method generates five candidate programs per input, and we select the candidate with the highest Inst F1. All remaining metrics are computed on the candidates selected by Inst F1.
We measure how accurately the rendered MIDI \(\hat{x}=\mathcal{C}(\hat{y})\) reproduces the input MIDI \(x\), adapting metrics from the automatic music transcription literature [27], [29], [40], [51] together with executability check from decompilation literature [1], [92], [93].
Comp.(compile rate) is the fraction of generated programs that the runtime \(\mathcal{C}\) successfully executes to a non-empty event list within the \(30\) s timeout. Programs that error out, time out, or emit zero events are counted as failures.
Onset F1 treats each note as a (pitch, onset) tuple and counts a predicted note as correct if it has the same pitch as a reference note whose onset lies within \(\pm 50\) ms of the prediction. We use
the standard mir_eval implementation [31] to perform optimal bipartite matching between reference and predicted notes, and compute per-piece
precision, recall, and F1 from the resulting match counts. We report Onset F1 as the primary metric, following evidence that onset correctness is the principal determinant of perceptual transcription accuracy [94].
Frame F1 measures pianoroll-level overlap of sustained notes. A pianoroll is a \([T \times 128]\) binary matrix where the entry at frame \(t\) and pitch \(p\) is \(1\) iff at least one note of pitch \(p\) is sounding during the \(10\) ms span covered by frame \(t\) (i.e., we use a frame rate of \(100\) frames per second); each note’s velocity is binarized so that any non-zero velocity counts as active. Frame F1 is the element-wise binary F1 between the
reference and predicted pianorolls, computed using precision_recall_fscore_support from scikit-learn [95].
Inst F1 (multi-instrument onset F1) is the same as Onset F1 but with notes partitioned by their assigned General MIDI program number, treating each program as a distinct instrument and the channel-\(9\) percussion track as a separate drum partition. Each partition is scored independently with the \(\pm 50\) ms onset criterion, and the per-partition results are micro-averaged (i.e., each partition’s precision and recall are weighted by its predicted and reference note counts respectively before the F1 is taken). Inst F1 is also the faithfulness reward \(R_{\text{faith}}\) used during RL (Section 2.2).
We report two complementary readability measures, both by LLM judges: an absolute per-program score (Rubric) and a relative cross-method score (Rank).
Rubric is the \(12\)-item factual checklist score (Section 2.2; Appendix 9) in \([0,1]\),
computed with the same Qwen3.6-35B-A3B [91] judge used during RL training. Each program is scored independently; we report the
mean rubric across the evaluation set.
Rank is the average rank from a listwise LLM reranker [41]: for each input, the (compiling) outputs of all methods are shown together to an LLM judge that orders them from most to least readable; to control position and label bias we average over multiple random permutations of the candidate order and labels and aggregate the per-permutation orderings into a consensus rank. Lower average rank is better.
In creative settings, useful AI suggestions are often expected to surface multiple plausible directions rather than returning near-identical outputs [56], [57], [96]. For symbolic music decompilation, this means that a model should not collapse to a single programming template: the same musical input can often be expressed through different Strudel idioms, such as chained patterns, shared variables, layering operators, or section-level structure. We therefore evaluate diversity as a measure of whether a method explores different coding idioms across generations.
Among different similarity measurement method [42], [97], [98] we adopt an AST-based metric for programming languages. Pure \(n\)-gram measures [98] can be dominated by mini-notation pitches and rhythms, which obscures idiom-level diversity, whereas embedding-based code-similarity metrics [97] are trained primarily on high-resource, mainstream languages and are thus less reliable for a low-resource DSL such as Strudel. We therefore use the AST-based metric of [42] for diversity computation, detailed below.
SelfCB measures intra-method self-similarity using CodeBLEU [42]. For a method with programs \(\{p_1,\dots,p_N\}\) over the shared input set, \(\text{CodeBLEU}(\cdot,\cdot)\in[0,1]\) scores code similarity as a weighted combination of standard BLEU, weighted \(n\)-gram matching, syntactic AST matching, and semantic data-flow matching. We weight the four terms by \((\alpha,\beta,\gamma,\delta)=(0.1,0.1,0.4,0.4)\) which [42] find correlate most strongly with human judgments. We parse programs with a Acorn JavaScript AST parser [87]. As CodeBLEU is asymmetric, we symmetrize each unordered pair and average over all such pairs:
\[\text{SelfCB} = \binom{N}{2}^{-1}\!\!\sum_{i<j} \tfrac{1}{2}\!\left(\text{CodeBLEU}(p_i,p_j)+\text{CodeBLEU}(p_j,p_i)\right).\]
Analogous to Self-BLEU [99], a lower SelfCB indicates more diverse generations. To compare methods fairly, we restrict the pair set to the inputs on which all methods compile successfully (and as such exclude vanilla Qwen models which has less than 20% compile rate to ensure sample size).
This section describes the MIDI datasets and sampling method used in our evaluation. Our experiments cover two settings: (i) the main evaluation (Section 3.2) and ablation studies (Section 3.3) use Strudel-Synth and short LMD fragments, matching the distributions used during post-training; and (ii) the generalization evaluation (Section 3.4) uses additional held-out corpora that introduce distribution shifts in duration, source, and instrumentation.
The main experiments (Section 3.2) and ablation studies (Section 3.3) use two corpora: Strudel-Synth and LMD. Strudel-Synth is our synthetic paired corpus of executable Strudel programs and rendered MIDI outputs, as described in Section 2.3. LMD [24] is a large-scale real-world MIDI corpus of multi-instrument, multi-genre music, including arrangements of modern pop songs and transcriptions of Western classical compositions. For LMD, we sample short fragments shorter than \(30\) s for evaluation, yielding approximately \(9\)K training segments and \(1\)K test segments. Together, Strudel-Synth and short LMD fragments define the in-domain distribution against which we evaluate the effects of post-training.
To evaluate whether Decomposer transfers beyond the training distributions, we additionally evaluate on four held-out settings. For each setting, we sample \(200\) evaluation inputs and report Best@\(5\) results: each method generates five candidate programs per input, and we select the candidate with the highest Inst F1. All remaining metrics are computed on the candidates selected by Inst F1.
LMD (\(30\)–\(60\) s; [24]) consists of longer LMD excerpts and tests generalization to inputs longer than those used during RL post-training and main evaluation.
GigaMIDI [43] is a large-scale multi-instrument, multi-genre symbolic music corpus that consolidates several major MIDI resources. It provides a source-level generalization setting over real-world MIDI files drawn from a broader collection than LMD; because GigaMIDI includes LMD among its sources, we exclude LMD-derived files when sampling evaluation inputs. We use short fragments shorter than \(30\) s.
NES-MDB [44] is a multi-instrument symbolic music corpus of approximately \(5\)K chiptune pieces from \(397\) Nintendo Entertainment System games. Each song is represented with four NES instrument voices, corresponding to two pulse channels, one triangle channel, and one noise channel, with annotations for expressive dynamics and timbre attributes. This setting tests generalization to chiptune music with a source and instrumentation distribution that differs substantially from Strudel-Synth and LMD. We use short fragments shorter than \(30\) s.
Nottingham [45] is a collection of over \(1\)K British and American traditional folk tunes, originally represented in ABC notation as lead sheets with monophonic melody and chord accompaniment. We use the available MIDI versions of the original ABC files. This setting tests generalization to homophonic lead-sheet music, where melodic and chordal roles may be encoded within a compact, single-instrument representation rather than separated into distinct instrumental tracks. Since the pieces are generally longer than the short-fragment setting used for post-training, we crop each piece to a \(16\) s segment, matching the mean duration of the short LMD fragments used during RL post-training.
Heuristic converter [20] transliterates MIDI directly into Strudel mini-notation. Starting from the open-source
converter, which quantizes each note onset to a fixed grid and emits the nearest Strudel sound, we extend it for our setting with: (i) drum support, mapping General MIDI channel-\(9\) percussion to Strudel drum
tokens (e.g., bd, sd, hh); (ii) multiple-meter support, reading time-signature changes to compute the beats-per-cycle of each segment; and (iii) broader heuristic instrument coverage, mapping MIDI
programs to Strudel sound names through an expanded lookup table (with a piano fallback for unmatched melodic programs and a synth-drum fallback for percussion). This provides a strong reference point for low-level faithfulness but a weak baseline for
readable program structure.
Frontier LLMs. We compare against three closed-weight general-purpose models: Claude-Opus-4.6 [22], Gemini-3.5-Flash [23], and GPT-5.5 [21]. These models represent strong general-purpose code-generation systems but are not specialized for Strudel or for symbolic music decompilation. We query each model through its provider’s chat API using the same MIDI-to-text input representation and decompilation prompt described in Appendix 7. For all models, we decode with a maximum of \(4096\) new tokens and disable model-specific extended reasoning modes when available.
For SFT, we fine-tune both models for one epoch with LoRA [47] using rank \(64\) and \(\alpha=128\). We use a learning rate of \(5\times10^{-4}\) with a cosine schedule, weight decay \(0.05\), gradient clipping \(1.0\), and batch size \(128\). SFT training is performed on a single node with \(8\times\)H100 GPUs and completes in approximately \(1.5\) hours.
For RL, we initialize from the SFT checkpoint and optimize with rollout group size \(16\), batch size \(64\), and learning rate \(5\times10^{-6}\) for \(10\) epochs. We use KL coefficient \(0.05\), clipping threshold \(0.4\), and sampling temperature \(1.0\), a maximum generation length of \(4096\) new tokens, and disable the model-specific thinking mode during rollout generation (enable_thinking=false). We select checkpoints using validation reward on a held-out mixture of Strudel-Synth and LMD MIDI, stopping when the validation faithfulness–readability tradeoff no longer improves. RL training is performed on a single node with \(8\times\)H100 GPUs and takes approximately \(8\) days. We implement the training backend using AReaL [100].
RL for language models often optimizes multiple, partially conflicting rewards, such as task accuracy versus reasoning length [101]–[106] or multiple dimensions of human preference [107], [108]. A common approach is to collapse these objectives into a single weighted sum and directly optimizing it [3], [7], [101]–[103]; yet it often removes distinctions across reward dimensions and leads to suboptimal reward convergence. Recent methods therefore decouple reward signals: DRPO separates the learning signals of correct and incorrect rollouts [106], while GDPO normalizes each reward component independently before aggregation [25]. Our work adopts GDPO to optimize execution faithfulness and program readability as separate reward components.
Structured rubrics and checklists score LLM outputs along multiple criteria in settings without a single verifiable answer. They are widely used for evaluation [79], [109]–[114], and recent work uses them directly as RL reward signals: Rubrics as Rewards [33] and RLCF [32] decompose a task into binary checklist items scored by an LLM judge, yielding more stable training signal than scalar preference rewards in non-verifiable domains. We use the same design for our readability reward, with a fixed checklist combining general code-quality items and Strudel-specific criteria.
This section provides additional results and analysis that complement the experiments in the main paper (Section 3). We first provide the full generalization results behind the summary in Section 3.4 (Appendix 13.1). We then evaluate Decomposer across genres to analyze how decompilation faithfulness varies with musical style (Appendix 13.2).
Table 5 reports the full generalization results corresponding to the summary in Table 2 of Section 3.4, where we evaluate whether Decomposer generalizes beyond the training distributions. The table includes the in-domain Strudel-Synth and short-fragment LMD settings for reference, followed by out-of-domain datasets that vary duration, source, genre, and instrumentation: longer LMD excerpts (\(30\)–\(60\) s; [24]), GigaMIDI [43], NES-MDB [44], and Nottingham [45]. For the generalization study, we compare Decomposer against GPT-5.5 as the representative frontier-LLM baseline. Among the frontier models in our main evaluation, GPT-5.5 provides the strongest overall tradeoff, achieving the best Inst F1 and readability rank on both Strudel-Synth and LMD while remaining competitive in diversity (Table ¿tbl:tab:main?). We report best@5 results selected by Inst F1; all other metrics are computed on the selected candidates.
Compared with GPT-5.5, Decomposer achieves stronger execution faithfulness on most datasets, while maintaining comparable readability and diversity. The largest gap between GPT-5.5 and Decomposer appears on Nottingham (\(+0.25\) Onset F1). This may reflect dataset-specific structure rather than genre alone: Nottingham is a single-instrument, ABC-derived folk collection with comparatively regular symbolic timing, and our instrument-grouped MIDI representation (Appendix 7) collapses melodic and chord accompaniment into the same track. Such inputs may make melodic and harmonic organization easier for the learned decompiler to recover. At the same time, although Decomposer outperforms GPT-5.5, absolute faithfulness remains lower on LMD (\(30\)–\(60\) s) and NES-MDB than in the other settings. These cases highlight different aspects of decompilation: longer LMD excerpts require structure to be recovered over a wider temporal span, while NES-MDB introduces specialized chiptune timbres and dense note patterns. For readability and diversity, Decomposer obtains higher Rubric scores than GPT-5.5 across the held-out datasets while Rank and SelfCB remain comparable. Overall, these results support the main claim of Section 3.4: the learned decompiler transfers beyond the synthetic paired data, yet its robustness remains sensitive to the temporal and stylistic structure of the input.
| Faithfulness | Readability | Diversity | ||||||
|---|---|---|---|---|---|---|---|---|
| 3-6 (lr)7-8 (lr)9-9 Dataset | Method | \(\uparrow\)Comp. | \(\uparrow\)Onset | \(\uparrow\)Frame | \(\uparrow\)Inst | \(\uparrow\)Rubric | \(\downarrow\)Rank | \(\downarrow\)SelfCB |
| 0.97 | 0.70 | 0.67 | 0.62 | 0.37 | 1.61 | 0.45 | ||
| 1.00 | 0.86 | 0.74 | 0.78 | 0.69 | 1.39 | 0.47 | ||
| 0.94 | 0.44 | 0.41 | 0.39 | 0.29 | 1.44 | 0.54 | ||
| 1.00 | 0.70 | 0.57 | 0.68 | 0.56 | 1.56 | 0.46 | ||
| 0.83 | 0.21 | 0.25 | 0.13 | 0.31 | 1.81 | 0.43 | ||
| 1.00 | 0.35 | 0.31 | 0.31 | 0.68 | 1.19 | 0.42 | ||
| 0.99 | 0.57 | 0.38 | 0.57 | 0.25 | 1.61 | 0.55 | ||
| 1.00 | 0.61 | 0.35 | 0.61 | 0.56 | 1.39 | 0.47 | ||
| 0.91 | 0.37 | 0.33 | 0.28 | 0.28 | 1.49 | 0.49 | ||
| 1.00 | 0.43 | 0.35 | 0.40 | 0.61 | 1.51 | 0.48 | ||
| 1.00 | 0.51 | 0.46 | 0.51 | 0.28 | 1.50 | 0.46 | ||
| 1.00 | 0.76 | 0.65 | 0.76 | 0.45 | 1.50 | 0.49 | ||
4pt
To complement the corpus-level generalization study with a finer-grained analysis of stylistic variation, we evaluate Decomposer on ModArchive,7 a public archive of tracker-format music. A tracker module is a self-contained music file that stores symbolic note patterns together with instrument samples and sequencing information. By combining sample-level audio material with explicit symbolic structure, tracker modules provide a natural intermediate representation for the broader audio-to-code decompilation setting (Section 3.5). ModArchive is also well matched to our evaluation because tracker music and Strudel are both closely tied to electronic and sample-based music-making practices, and many modules include community-provided genre tags which allow us to analyze decompilation faithfulness across fine-grained musical styles.
We crawl tracker modules from ModArchive across \(51\) genre tags, recording each module’s genre, format, and rating. For each genre, we target the top \(100\) modules by rating. Because
tracker formats (e.g., .mod, .xm, .it, .s3m) differ substantially from the General MIDI inputs used elsewhere in this paper, we convert each module to GM MIDI using a headless wrapper around the open-source MilkyTracker engine.8 Each tracker instrument is mapped to a General MIDI program using Qwen2.5-Omni [54]; samples without a clear pitched or percussive identity, such as speech or sound effects, are discarded. We then extract a \(16\) s segment from each valid module, matching the mean
duration of the short LMD fragments used during RL post-training (Appendix 11). The resulting benchmark contains \(4{,}463\) inputs across all \(51\) genres,
with an average of \(87.5\) examples per genre (range: \(61\)–\(97\)).9 As
in the generalization study, we evaluate Decomposer-8B using Best@\(5\) metrics (Section 3.4).
3.2pt width=0.98,center
Table ¿tbl:tab:apd:genre? and Figure 7 report per-genre faithfulness, sorted by Onset F1. Performance varies substantially across genres, while compile rates remain almost saturated: all genres achieve a compile rate of \(1.00\) except Electronic–Gabber (\(0.99\)) and Classical (\(0.98\)). The genre-wise gaps therefore reflect differences in musical reconstruction rather than failures to produce executable Strudel programs.
At the level of broad genre families, Table ¿tbl:tab:apd:genre? shows that Electronica achieves the highest aggregated scores, followed by Seasonal, Alternative, and Pop/Rock. The fine-grained ranking in Figure 7 shows a similar trend: dance- and electronic-oriented genres tend to score higher, with Disco ranked first and Electronica genres such as Jungle, Minimal, Acid, and House concentrated near the top. This pattern is consistent with the close fit between Strudel’s pattern-based abstractions and rhythmically regular electronic and dance music. By contrast, lower-scoring genres include Jazz, Funk, Chiptune, Orchestral, and Reggae. These genres often involve expressive timing, off-beat rhythmic patterns such as syncopation, or dense note and instrumentation patterns, which can make the input harder to recover as a concise executable Strudel program. Overall, the results suggest that Decomposer transfers best to genres whose structure is well captured by repeated pattern abstractions, while highlighting rhythmic variation and dense textures as key directions for improving faithful decompilation.
Figures 8 and 9 show qualitative examples comparing decompilations of the same MIDI input across representative methods: Decomposer
(8B), the vanilla Qwen3-8B model (Qwen3-8B; [46]), Qwen3-8B after SFT, the heuristic converter [20], and the three frontier models used in our evaluation: Claude-Opus-4.6 [22], Gemini-3.5-Flash [23], and GPT-5.5 [21]. To make structural differences easier to compare, we omit sound effect parameters such as gain() and room(). For additional examples with
sound, please refer to the project page: https://yewon-kim.com/decomposer.
Figure 8: Qualitative examples: trained model variants. We show the input MIDI visualization and the corresponding Strudel decompilations produced by Decomposer (8B), the vanilla Qwen3-8B model, and Qwen3-8B after SFT. Sound-effect parameters such as gain() and room() are omitted so that the comparison focuses on how each method encodes the underlying MIDI structure.. a — Piano roll visualization of the input MIDI, b — Decomposer (8B), c — Vanilla Qwen3-8B, d — Qwen3-8B after SFT
Figure 9: Qualitative examples: baselines. For the same MIDI input as Figure 8, we show decompilations produced by the heuristic converter and the frontier models (Claude-Opus-4.6, Gemini-3.5-Flash, and GPT-5.5). Sound effect parameters are omitted for consistency.. a — Heuristic converter, b — Claude-Opus-4.6, c — Gemini-3.5-Flash, d — GPT-5.5
Project page: yewon-kim.com/decomposer↩︎
We use \(y^*\) to indicate that, during SFT, the target program corresponding to \(x^* = \mathcal{C}(y^*)\) is known, in contrast to a generic MIDI input \(x\) at test time or during RL where the target program is unknown.↩︎
These rewrites are heuristic and target the most common failure modes observed. They are not guaranteed to be sound and may occasionally modify already-valid programs; cleaned outputs are re-validated afterward.↩︎
The final number of examples per genre is below the initial crawl target of \(100\) because some genres contain fewer than \(100\) modules, or because some modules become empty after MIDI conversion. See https://modarchive.org/index.php?request=view_genres for the genre categories and their listed module counts.↩︎