Spaces:
Sleeping
Theme Generation Engines
This note documents the first generation experiments to run after the corpus audit. There are now two engines sharing the same corpus loader, symbolization, rhythmic constraints, MIDI/ABC export, and report writer:
markov: a small variable-order Markov model usingvo_regular_bpregular constraints and soft endpoint weights.transformer: a tiny key-relative decoder-only transformer using the same symbolic vocabulary and constrained sampling surface.
The unified runner is:
python3 scripts/run_theme_generation.py markov
python3 scripts/run_theme_generation.py transformer
The old Markov command remains available as a wrapper:
python3 scripts/run_vo_regular_baseline.py
The transformer wrapper is:
python3 scripts/run_transformer_baseline.py
The transformer engine requires PyTorch in the active Python environment. The local baseline run used:
python3 -m venv .venv
.venv/bin/pip install torch numpy mido
.venv/bin/python scripts/run_theme_generation.py transformer \
--samples 6 \
--length 24 \
--steps 500 \
--batch-size 64 \
--block-size 64 \
--d-model 96 \
--nhead 4 \
--layers 3 \
--feedforward 192 \
--temperature 0.95 \
--top-k 16 \
--output-dir outputs/transformer_baseline \
--write-abc
Available Corpus Tables
The generated SQLite database is audit/themes_audit.sqlite.
Important tables:
themes: catalog metadata, ABC/MIDI paths, key signature, meter, length, and note-level summary features.notes: one row per MIDI note event, sorted bystart_tickandnote_index.theme_descriptionsandtheme_text_fts: local generated search text.endpoint_analysis: first/last note and first-salient endpoint statistics.key_modulation_analysis: key-signature coverage, accidental counts, and heuristic local-key drift candidates.
Key Findings Used By The Model
The corpus contains 9,824 parsed themes and 196,679 MIDI note events.
Key metadata:
- Each theme has a single explicit key signature.
- No MIDI file contains multiple key-signature events.
- No ABC file contains inline
[K:...]key changes. - Minor mode is not encoded reliably;
abc_keyshould be interpreted as a key-signature/root feature, not a full tonal label.
Endpoint regularities:
- Last note is the notated tonic/root in only 16.8% of themes.
- Last note is tonic or dominant in 35.3% of themes.
- Last note is in the tonic triad in 54.5% of themes.
- The first-salient-to-last pitch-class relation favors intervals 0, 7, and 5.
Implication: endpoint constraints should be soft priors, not hard cadence rules.
Initial Symbolization
Start with a deliberately small vocabulary:
symbol = (relative_pitch_class, duration_value)
where:
relative_pitch_class = (pitch_class - key_root) mod 12
The initial version should keep the original duration labels from notes,
possibly filtered to the common values:
16theighthquarterhalfdotted eighthdotted quartereighth triplet16th triplet
Rare duration labels can either be mapped to the nearest common class or kept only if they occur above a threshold.
This transposes every theme into a common key-relative space and gives enough data for variable-order contexts.
The symbolization deliberately removes register. Both the Markov and transformer engines therefore generate relative pitch classes, not concrete octaves. The shared output layer realizes generated pitch classes into MIDI pitches for playback and notation; generated samples currently use a treble-friendly C4-C6 range so ABC, MIDI, MusicXML, and Verovio previews do not drift into unreadable ledger-line registers. A later model can make register/octave a first-class prediction target if we want the generators to learn that behavior directly.
Markov Training Model
Train an order-stack variable-order Markov model over these symbols.
Recommended first settings:
max_order = 4
min_context_count = 2
backoff = longest available context with nonzero feasible future mass
The generated sequence length can first be controlled in notes, e.g. 16, 24, or 32 notes. A later version should control length in beats/bars through a duration tracking acceptor.
Transformer Training Model
Train a deliberately small decoder-only transformer over the same symbol stream.
The implementation is in theme_generation/engines/transformer.py and uses:
token stream = START, symbol_1, symbol_2, ...
symbol = (relative_pitch_class, duration_value)
Recommended first settings:
block_size = 64
d_model = 96
nhead = 4
num_layers = 3
steps = 800
top_k = 16
temperature = 1.0
This is intentionally small enough to make overfitting visible and iteration cheap. Once PyTorch is installed, a tiny smoke run is:
python3 scripts/run_theme_generation.py transformer \
--samples 2 \
--length 16 \
--steps 20 \
--output-dir outputs/transformer_smoke
Regular / Weighted Constraints
The vo_regular_bp acceptor should encode constraints that are finite-state:
- Allowed symbol vocabulary.
- Optional maximum repetition of the same relative pitch class.
- Optional maximum number of chromatic relative pitch classes.
- Optional accumulated duration / bar length target.
- Boundary weights for initial and terminal relative degrees.
For the first note-count baseline, the weighted DFA state can be as small as:
state = position
and the transition weight can be:
if position == 0:
return start_degree_weight[relative_pitch_class]
if position == length - 1:
return terminal_degree_weight[relative_pitch_class]
return 1.0
If we want the first-salient-to-last relation, augment the DFA state:
state = (position, first_salient_relative_pc_or_none)
and apply the endpoint relation weight on the last transition:
endpoint_weight[first_salient_relative_pc][last_relative_pc]
This compiles the non-local relation into regular state memory.
Soft Priors
Use empirical degree distributions from endpoint_analysis, with smoothing.
Start weights:
P(first_salient_degree)
End weights:
P(last_degree)
Optional relation weights:
P(last_relative_pc | first_salient_relative_pc)
These should be temperature-scaled so the Markov model still has room to choose idiomatic continuations.
First Experiment
The first runnable Markov experiment:
- Load
themesandnotesfromaudit/themes_audit.sqlite. - Build key-relative sequences of
(relative_pitch_class, duration_value). - Train a max-order Markov count model.
- Generate samples of fixed note length with endpoint degree soft weights.
- Convert generated samples to MIDI in a chosen output key.
- Write a small report with sampled sequences and model diagnostics.
The default generation vocabulary should be rhythmically conservative but not overly square: allow durations down to a 16th note, require ordinary durations to fall on a 16th-note grid, and also allow regular triplet durations. This keeps ordinary short notes, dotted-eighth figures, and eighth-triplets. Triplets should be constrained to complete beat-aligned groups; isolated or phase-shifted triplets can make MIDI notation importers quantize surrounding material as awkward septuplets or nontuplets. This should be compiled into the finite-state acceptor, using beat phase and triplet-group state, rather than handled by generate-and-test filtering.
Suggested Markov outputs:
outputs/vo_regular_baseline/
generated_*.mid
report.md
Suggested transformer outputs:
outputs/transformer_baseline/
generated_*.mid
generated_*.abc
report.md
ABC export can still be useful for debugging, but should be opt-in rather than part of the default generation output.
Open Questions
- Should length be controlled by note count or by total duration?
- Should rare chromatic degrees be allowed freely, penalized, or modeled as a separate chromatic decoration layer?
- Should endpoint priors be global, meter-specific, composer-specific, or genre-specific?
- Should local key drift candidates be excluded from training the simplest key-relative model?
The simplest useful answer is: start global, note-count based, and key-relative; then listen.