Generate a MusicXML composition from song_contract.md, with an agent scratchpad for iterative refinement
You are a music composer generating a complete MusicXML score based on the song contract. You have deep knowledge of music theory, voice leading, orchestration, and MusicXML format.
song_contract.md from the project root (or the path provided as argument)output/scratchpad.md exists, read it for context on prior iterationsBefore writing any MusicXML, create/update output/scratchpad.md:
# Composition Scratchpad
## Contract Summary
[Key points from the contract]
## Composition Plan
### Harmonic Skeleton
- Measure 1-4: [chord progression]
- Measure 5-8: [chord progression]
- ...
### Melodic Sketch
- Main motif: [describe in terms of intervals/rhythm]
- Secondary motif: [if applicable]
- Development approach: [how motifs transform]
### Voice Leading Notes
- [Part-specific considerations]
### Expression Plan (apply canonical wisdom)
- Opening dynamic/expression: [what marks establish character in mm. 1-4?]
- Phrase boundary dynamics: [where do phrases end/begin, what dynamic changes mark them?]
- Staccato passages: [which sections use staccato texture, for how many measures?]
- Hairpin placement: [where do crescendos and diminuendos reinforce melodic contour?]
- Accent strategy: [which weak-beat moments deserve accents?]
- Expression text: [dolce, rall., a tempo, etc. — where?]
### Orchestration Notes
- [Which instruments double where, texture changes]
## Iteration Log
### Version [N] — [date]
- Changes made: [what changed from previous version]
- Quality assessment: [if available, paste classifier feedback]
- Remaining issues: [what still needs work]
These principles were extracted from statistical analysis of Bach, Beethoven, Mozart, Schubert, Chopin, and other canonical composers. Internalize them BEFORE writing any music — they should shape your compositional decisions, not be retrofitted after.
Dynamics are structural, not decorative. Place dynamics at phrase boundaries — 53% of dynamics in canonical music mark structural transitions. Every new phrase should establish its dynamic context. Don't scatter dynamics randomly; use them to articulate form.
Establish character early. Canonical pieces front-load expressive information — 11% of all dynamics appear in the first 10% of the piece. Set the tone immediately. Your opening measures should already have dynamic markings, tempo indication, and expression text.
Don't default to quiet→loud arcs. 55% of canonical pieces have a flat dynamic trajectory. The loudest moment typically falls in the first quarter (median position: 26%), not at a late climax. Local dynamic variation within a steady overall level is more common than a single dramatic build.
Balance crescendo and diminuendo. Canonical music uses nearly equal crescendos and diminuendos (0.92:1 ratio). Don't just build up — tapering down is equally important. A diminuendo after a phrase peak is as expressive as the crescendo that preceded it.
Staccato travels in packs. 87.6% of staccatos in canonical music appear near other staccatos. Use staccato as a passage texture, not as individual note decoration. When a passage is staccato, commit to it for several measures, then release.
Accents highlight the unexpected. 67% of accents in canonical music fall on weak beats. Strong beats already have natural emphasis — accenting them is redundant. Place accents on syncopations, upbeats, and harmonically surprising moments.
Dynamics follow pitch contour. When the melody rises, dynamics tend to increase (46% of cases). When it falls, dynamics tend to decrease (41%). Write dynamics that reinforce the natural shape of your melodic lines.
Use real expression vocabulary. The most common expression markings in canonical scores: dolce, a tempo, rall., ten. (tenuto), rit., sotto voce, una corda, sempre. Use these — they're the language performers understand.
Dynamic range is moderate. Most canonical pieces span pp to ff (not ppp to fff). Reserve extreme dynamics for truly extraordinary moments. A piece that's always at extremes has no room to move.
Generate a complete, valid MusicXML file. Follow these rules strictly:
<?xml?> declaration, <!DOCTYPE>, <score-partwise> root<part-list> before using them<measure> must have notes/rests totaling the correct duration<divisions> consistently. Common: divisions=1 means quarter=1, eighth=0.5, etc. Recommend divisions=4 for flexibility (quarter=4, eighth=2, 16th=1)<direction> elements<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="4.0">
<work>
<work-title>TITLE</work-title>
</work>
<identification>
<creator type="composer">Rachmaniclaude AI</creator>
</identification>
<part-list>
<score-part id="P1">
<part-name>INSTRUMENT</part-name>
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>4</divisions>
<key><fifths>0</fifths></key>
<time><beats>4</beats><beat-type>4</beat-type></time>
<clef><sign>G</sign><line>2</line></clef>
</attributes>
<!-- notes here -->
</measure>
</part>
</score-partwise>
| Duration | Value |
|---|---|
| Whole | 16 |
| Half | 8 |
| Quarter | 4 |
| Eighth | 2 |
| 16th | 1 |
| Dotted half | 12 |
| Dotted quarter | 6 |
| Dotted eighth | 3 |
After writing the MusicXML file to output/score.musicxml:
python -c "from music21 import converter; s = converter.parse('output/score.musicxml'); print(f'Parsed OK: {len(s.parts)} parts, {len(list(s.recurse().getElementsByClass(\"Note\")))} notes')" to verify it parsesRun the feature profile assessment to get ranked improvement instructions:
python -c "
import os, json
from rachmaniclaude.compose.feedback import run_feedback_loop
result = run_feedback_loop(
musicxml_path='output/score.musicxml',
output_dir='output',
classifier_path='models/quality_classifier.joblib' if os.path.exists('models/quality_classifier.joblib') else None,
regressor_path='models/quality_regressor.joblib' if os.path.exists('models/quality_regressor.joblib') else None,
distribution_scorer_path='models/distribution_scorer.joblib' if os.path.exists('models/distribution_scorer.joblib') else None,
profile_path='models/feature_profile.joblib' if os.path.exists('models/feature_profile.joblib') else None,
quality_threshold=0.5,
max_iterations=3,
)
print('PASSES:', result['passes'])
print('ITERATION:', result['iteration'])
print()
print(result['critique_text'])
"
Read the profile feedback — it gives you ranked, specific instructions like:
dynamics_count = 0 (percentile 3 in high-rated music, target median: 8). Add dynamic markings throughout the score.
If there are priority improvements to make: Revise the MusicXML to address the top 3-5 issues. Focus on adding expressive markings (dynamics, hairpins, articulations), harmonic variety, and chromatic color — these are typically the highest-impact improvements. Then re-run the assessment to see the delta.
If most features are at or above median: The composition is ready. Stop iterating.
Update output/scratchpad.md after each iteration with what changed and what the delta report shows.
When the feedback loop is done, render the final score:
musescore3 -o output/score.mp3 output/score.musicxml
output/score.musicxml — The generated scoreoutput/score.mp3 — Rendered audiooutput/scratchpad.md — Composition notes and iteration logoutput/revision_log.jsonl — Feature deltas across iterationsTell the user the score is ready and offer to play the MP3 or run /assess-quality for additional iterations.