Maurice — Attribute-Conditioned 🎹🎛️
⚠️ This is a symbolic (MIDI) model — not an audio model
Maurice generates note events — pitch, onset, duration, velocity — and writes a
.midfile. It does not generate waveforms, and it does not take audio as input. There is no vocoder, no spectrogram, no.wavanywhere in the pipeline.To hear the output you render the MIDI yourself, with a SoundFont (FluidSynth), a sampled piano library (SFZ/Kontakt), or any synth or DAW. The quality of what you hear therefore depends heavily on the piano you render it through — the same file can sound thin through a GM font and gorgeous through a good sampled grand.
If you want text-to-audio music generation, this is not that kind of model — look at MusicGen, Stable Audio or similar. Maurice is closer in kind to a composer writing a score than to a recording of one.
An attribute-conditioned fine-tune of Maurice, the ~300M from-scratch romantic-piano cold-start model. The base model takes composer + key; this one adds nine musical attribute dials, so a brief written in natural language ("a shimmering wave that descends, dark and stormy") can be mapped to structured conditioning and realised section by section.
This is the checkpoint that actually drives an end-to-end composition pipeline — see The architecture around the model below.
- Base: Maurice at step 37,000 (val ppl ≈ 2.96)
- This fine-tune: step 8,000, val loss 1.168, LoRA (r64, attn+MLP) merged into the weights
- Vocab: 17,832 = base 17,785 + 47 attribute tokens (ids 17785–17831)
- Architecture: unchanged from base — Qwen3-style decoder, 16 layers, d=1280, 20 heads / 4 KV heads, 4096 context, ~298M params
The nine attributes
| attribute | values |
|---|---|
register |
bass · low · mid · high · brilliant |
range |
narrow · moderate · wide |
density |
sparse · moderate · dense · torrential |
polyphony |
single-line · mixed · chordal |
contour |
ascending · descending · arch · valley · wave · static |
motion |
still · flowing · active · agitated |
dynamics |
hushed · soft · medium · loud · thunderous |
articulation |
staccato · detached · legato |
color |
dark · shadowed · neutral · warm · bright |
Any attribute may be omitted — it falls back to its any token. Bins were derived from percentiles of the real corpus rather than hand-picked thresholds; hand-picked ones left several bins (e.g. dynamics: thunderous) effectively unreachable.
How it works
Training sequences interleave attribute markers with the music:
[instr, composer, key, <S>, A1, chunk1, A2, chunk2, ..., <E>]
where each Ai is 9 attribute tokens describing the following ~6-second chunk. One formulation teaches both cold-start (A1 → chunk1) and continuation (context + Ai → chunk_i), which is what makes section-by-section building possible.
Inference seed: [instr, composer, key, <S>] + [9 attribute tokens]. Attribute and prefix ids must be masked from sampling and stripped before detokenizing.
Files
| file | what |
|---|---|
model.safetensors |
weights, fp32 (~1.2 GB), tied embeddings — this is step 8000, the final checkpoint |
config.json |
architecture + conditioning config |
attr_meta.json |
attribute schema, token ids, composer/key maps |
model_qwen.py |
model definition |
attr_sample.py / attr_dials.py |
sampling with attribute conditioning |
poly_server.py |
HTTP server: cold-start + plan-driven compose |
attributes.py / derive_bins.py |
attribute extraction and percentile bin derivation |
train_attr.py / prep_attr.py / run_attr.sh |
fine-tuning pipeline |
ckpt_attr_7500.pt |
best-val training checkpoint (step 7500) in original .pt form |
Hear it
Elegy in C-sharp minor — 608 notes composed by the model, then performed. The same notes twice:
| ▶ as the model composed it | raw output: no pedal, flat velocities, everything square on the beat |
| ▶ after a performance pass | identical pitches; only voicing, rolls, rubato and pedal differ |
Play those back to back. It's the fastest way to hear what this model gives you and what it doesn't,
and why we think dry-sounding symbolic output is usually unperformed rather than badly composed.
Source MIDI is in output_examples/.
Prior art & lineage
Maurice stands on other people's work, and it's worth being precise about what came from where.
Aria (loubb/aria-medium-base, aria-utils) is the closest ancestor: Maurice uses Aria's AbsTokenizer unchanged and trains on the Aria-MIDI corpus. The models are complements, not competitors — Aria continues existing material; Maurice cold-starts from silence, which a continuation model structurally cannot do. Shared tokenizer means the outputs interoperate directly.
GiantMIDI-Piano (Kong, Li, Song, Hantrakul & Wang, ByteDance) supplied a wide slice of classical/romantic repertoire — ~10k works transcribed from audio — and is much of the reason the composer conditioning has range rather than a narrow canon.
MAESTRO (Hawthorne et al., Google Magenta) contributed ~200 hours of aligned virtuoso performance. Being performed rather than quantized, it is disproportionately responsible for whatever expressive micro-timing the model has absorbed.
Broader lineage: Music Transformer (Huang et al.), the Anticipatory Music Transformer (Thickstun et al.), and score-level systems like SymphonyNet and NotaGen. For the performance layer discussed below: VirtuosoNet (Jeong et al.), the Vienna Basis Mixer (Cancino-Chacón, Grachten, Widmer), the KTH performance rules (Friberg, Sundberg, Bresin), and ASAP for score↔performance alignment.
The architecture around the model
Weights alone give you notes, not music. This checkpoint was built to sit inside a pipeline:
brief
│
▼ PLAN germ motif, section arc, per-section attribute targets, where the climax lands
▼ MAURICE composes EVERY note, plan-driven — states a theme, develops it
│ (fragment / invert / transpose / augment), returns it transformed
▼ GATE verifies ADHERENCE to the plan — arithmetic only, never taste
▼ PERFORMER an interpreter plays it: voicing, rolls, rubato, dynamics, pedal
│ — pitches strictly preserved, everything else decided
▼ RENDER MIDI → sampled piano
Three findings worth stating plainly, because they'll save you time:
1. Composition and performance are separate layers, and performance carries an enormous share of perceived quality. Testable both ways: take a piece that sounds good, quantize onsets to a grid and flatten velocities — same pitches — and it collapses into mush; take a raw "dry" model output and genuinely perform it — same pitches — and it comes alive. A dry generation is usually not a composition failure but an unperformed one. Judge symbolic output only after something has played it.
2. Rolled chords and rubato are acoustics, not decoration. Staggering a chord's attacks by 20–70 ms spreads beating partials out in time and measurably lowers sensory roughness. Expressive micro-timing and "sounds less muddy" are the same phenomenon — which is why quantizing a dense passage makes it rougher, not merely stiffer.
3. Structure must be imposed from outside. With a 4096-token context (~2–3 min) and no notion of overall arc, the model needs an explicit plan — capture a theme, develop it under named transformations, return it — to become something with a beginning, middle and end. For material longer than the context, carry the captured theme into the next span so later sections keep developing the same germ.
Limitations
- It can reproduce training data. The most important caveat, and measurable: screening generations against the training corpus with exact 12-interval melodic matching, most come back clean, but a minority quote one training file heavily — in a measured Liszt sample, 40 exact windows (~31% of the piece) traced to a single Aria-MIDI file. Incompletely deduplicated sources (Aria-MIDI documents this) and over-represented works both contribute. Screen anything you intend to publish or perform; do not assume novelty.
- Attribute conditioning is real but soft — and
densityis effectively non-functional. Measured across explicit density requests and repeated resampling, output lands in a narrow band regardless of the dial, and "sparse" is as likely to come out dense as the reverse. Register, color and dynamics steer more reliably. Get contrast from tempo, key, register and character, not from the density dial. - ~2–3 minutes is its natural form. Coherent within the context window; stretched much further, one unvarying texture reads as sameness rather than voice. Prefer a multi-movement suite with real tempo/key contrast between movements.
- Uneven composers — bold, well-represented ones (Liszt, Chopin, Debussy, Scriabin, Ravel) condition best; subtler ones drift toward a generic-romantic center.
- Solo piano only; symbolic MIDI output (render with your own SoundFont/synth).
Data & license provenance
Trained on transcribed/curated solo-piano MIDI — Aria-MIDI, GiantMIDI-Piano and MAESTRO — plus period-romantic pieces of unknown authorship (the unknown token), with ×12 transposition augmentation. Uses Aria's AbsTokenizer.
License: CC BY-NC-SA 4.0 — a non-commercial research release, matching the most restrictive of the training sources (MAESTRO is CC BY-NC-SA 4.0; Aria-MIDI and GiantMIDI carry research/non-commercial terms). See LICENSE.
Citation
@misc{maurice_attr2026,
title = {Maurice (attribute-conditioned): controllable romantic-piano cold-start},
year = {2026},
note = {Attribute fine-tune of the 298M Qwen3-style Maurice decoder; 9 musical dials, 47 added tokens}
}
- Downloads last month
- 49
Model tree for roygbiv33/Maurice-Attr
Base model
roygbiv33/Maurice