This commit is contained in:
khodowany
2026-10-01 16:41:18 -05:00
parent 28fc67fba1
commit f5861fcdf6
22 changed files with 1473 additions and 122 deletions
+36 -3
View File
@@ -28,6 +28,8 @@ import generator.solo.arrangement
import generator.solo.report
import generator.solo.sequences
import generator.solo.realization
import generator.solo.references
import generator.solo.harmonic
STAGES = ('baseline', 'A', 'B', 'C', 'D')
@@ -81,12 +83,21 @@ def capture(root, stage, libraries=None, contexts=None):
if destination.exists():
raise ValueError(f'{destination}: snapshot already exists; choose a new output root')
rhythms, scales, sequences = libraries or resources()
result = {'stage': stage, 'implementation': fingerprint(), 'songs': {}}
reference_library = generator.solo.references.load_library(pathlib.Path('data/lead_phrases'))
result = {'stage': stage, 'implementation': fingerprint(), 'reference_versions': [r.version for r in reference_library], 'songs': {}}
try:
result['policy'] = importlib.import_module('generator.solo.policy').describe()
except ModuleNotFoundError:
result['policy'] = {'baseline': True}
for path in generator.parts.source_paths(pathlib.Path('data/songs')):
source_hash = hashlib.sha256(path.read_bytes()).hexdigest()
checkpoint = root / f'.{stage}_{path.stem}_checkpoint.json'
if checkpoint.exists():
saved = json.loads(checkpoint.read_text())
if saved['implementation'] == result['implementation'] and saved.get('reference_versions') == result['reference_versions'] and saved['song']['source_sha256'] == source_hash:
result['songs'][path.stem] = saved['song']
result.setdefault('resumed_songs', []).append(path.stem)
continue
started = time.monotonic()
document = generator.parts.prepare_document(generator.musicxml.load_musicxml(path))
plans = generator.arrange.generate.plan_arrangement(document, rhythms)
@@ -96,7 +107,7 @@ def capture(root, stage, libraries=None, contexts=None):
if shared:
for context in shared.values():
context.diagnostics = generator.solo.realization.SearchContext().diagnostics
generator.solo.arrangement.apply(document, parts, rhythms=rhythms, scales=scales, sequences=sequences, solos=solos, revise=stage != 'phrase', contexts=shared)
generator.solo.arrangement.apply(document, parts, rhythms=rhythms, scales=scales, sequences=sequences, solos=solos, revise=stage != 'phrase', contexts=shared, references=reference_library)
song = {'source_sha256': hashlib.sha256(path.read_bytes()).hexdigest(), 'parts': parts, 'solos': {}, 'elapsed_seconds': time.monotonic() - started}
regions = generator.solo.arrangement.regions(document)
for solo, index in zip(solos, (3, 4)):
@@ -123,8 +134,30 @@ def capture(root, stage, libraries=None, contexts=None):
generator.fretboard.validate_document(view)
drafts.append({'id': phrase.plan.id, 'draft_id': phrase.draft.id, 'first': begin, 'count': stop - begin, 'document': view})
song['solos'][solo.part]['draft_documents'] = drafts
print(path.stem, solo.part, json.dumps(song['solos'][solo.part]['metrics']), flush=True)
stages = {}
for stage_name in ('initial', 'refined'):
if not all(getattr(p, stage_name) for p in solo.phrases):
continue
notes = tuple(n for p in solo.phrases for n in getattr(p, stage_name).notes)
placed = generator.solo.placement.place(notes, solo.tempo)
if placed is None:
raise ValueError(stage_name + ': selected theme cannot be placed')
sounded = generator.solo.types.sounding_notes(placed[1])
contracts = tuple(dict.fromkeys(t for p in solo.phrases for t in p.state.treatments))
checked = generator.solo.harmonic.Harmony(solo.choices).evaluate(sounded, contracts, until=solo.end, complete=True)
if not checked.valid:
raise ValueError(stage_name + ': ' + checked.error)
view = excerpt(parts[solo.part], first, count)
foreground = generator.solo.notation.measures(view['staves'][0]['measures'], placed[1], stage_name + '_' + solo.part)
generator.solo.notation.replace_region(view, 0, foreground)
generator.model.validate_document(view)
generator.fretboard.validate_document(view)
stages[stage_name] = {'document': view, 'attacks': len(sounded), 'occupancy': float(sum(n.end - n.start for n in sounded) / solo.end), 'harmonic_exposure': checked.exposure}
song['solos'][solo.part]['lead_stages'] = stages
print(path.stem, solo.part, json.dumps({k: v for k, v in song['solos'][solo.part]['metrics'].items() if k != 'search'}), flush=True)
result['songs'][path.stem] = song
root.mkdir(parents=True, exist_ok=True)
checkpoint.write_text(json.dumps({'implementation': result['implementation'], 'reference_versions': result['reference_versions'], 'song': song}) + '\n')
destination.mkdir(parents=True)
(destination / 'snapshot.json').write_text(json.dumps(result, indent=2) + '\n')