File size: 8,395 Bytes
187bf9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# 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 using `vo_regular_bp` regular
  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:

```bash
python3 scripts/run_theme_generation.py markov
python3 scripts/run_theme_generation.py transformer
```

The old Markov command remains available as a wrapper:

```bash
python3 scripts/run_vo_regular_baseline.py
```

The transformer wrapper is:

```bash
python3 scripts/run_transformer_baseline.py
```

The transformer engine requires PyTorch in the active Python environment. The
local baseline run used:

```bash
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 by `start_tick` and
  `note_index`.
- `theme_descriptions` and `theme_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_key` should 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:

```text
symbol = (relative_pitch_class, duration_value)
```

where:

```text
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:

- `16th`
- `eighth`
- `quarter`
- `half`
- `dotted eighth`
- `dotted quarter`
- `eighth triplet`
- `16th 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:

```text
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:

```text
token stream = START, symbol_1, symbol_2, ...
symbol = (relative_pitch_class, duration_value)
```

Recommended first settings:

```text
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:

```bash
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:

1. Allowed symbol vocabulary.
2. Optional maximum repetition of the same relative pitch class.
3. Optional maximum number of chromatic relative pitch classes.
4. Optional accumulated duration / bar length target.
5. Boundary weights for initial and terminal relative degrees.

For the first note-count baseline, the weighted DFA state can be as small as:

```text
state = position
```

and the transition weight can be:

```text
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:

```text
state = (position, first_salient_relative_pc_or_none)
```

and apply the endpoint relation weight on the last transition:

```text
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:

```text
P(first_salient_degree)
```

End weights:

```text
P(last_degree)
```

Optional relation weights:

```text
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:

1. Load `themes` and `notes` from `audit/themes_audit.sqlite`.
2. Build key-relative sequences of `(relative_pitch_class, duration_value)`.
3. Train a max-order Markov count model.
4. Generate samples of fixed note length with endpoint degree soft weights.
5. Convert generated samples to MIDI in a chosen output key.
6. 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:

```text
outputs/vo_regular_baseline/
  generated_*.mid
  report.md
```

Suggested transformer outputs:

```text
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.