Training Model

This commit is contained in:
khodowany
2026-10-05 19:44:33 -05:00
parent db7966a430
commit e7180e557b
6603 changed files with 8919 additions and 523063 deletions
+63
View File
@@ -0,0 +1,63 @@
"""Explicit registered repertoire. Authored playback is not model prediction.
Matching is by canonical source music and role, never title or filename. This
path intentionally reads the registered solo, following the user's exact-reuse
policy. Unknown song/role pairs go to the learned composer.
"""
from __future__ import annotations
import dataclasses
import hashlib
import json
from pathlib import Path
from fractions import Fraction as F
from generator import musicxml,parts,training_io,harmonic_time
from generator.solo import analysis,types,placement,profiles
def lookup(source,role,data_dir):
from generator.production import SourceAnalysis
root=Path(data_dir);manifest=root/'training/manifest.json'
if not manifest.exists():return None
value=json.loads(manifest.read_text())
if value.get('version')!=1:raise ValueError('Unsupported training manifest')
matches=[]
for entry in value['examples']:
if entry['slot']!=(1 if role=='acoustic' else 2):continue
path=(manifest.parent/entry['source']).resolve()
if not path.is_relative_to((root/'songs').resolve()):raise ValueError('Registered sources must be in data/songs')
doc=parts.prepare_document(musicxml.load_musicxml(path))
if SourceAnalysis.read(doc).musical_key==source.musical_key:
target=(manifest.parent/entry['solo']).resolve()
if not target.is_relative_to(manifest.parent.resolve()):raise ValueError('Registered solo must be inside data/training')
evidence=training_io.read(target,role,pitch_convention=entry['pitch_convention'])
training_io.validate_harmony(doc,evidence,entry['id'])
matches.append({**entry,'path':target,'sha256':hashlib.sha256(target.read_bytes()).hexdigest()})
if len(matches)>1:raise ValueError('Multiple authored solos registered for the same source music and role')
return matches[0] if matches else None
def realize(entry,source_analysis,measures,part,start,end,initial=None):
evidence=training_io.read(entry['path'],part,pitch_convention=entry['pitch_convention'])
if evidence['discrepancies']:raise ValueError(entry['id']+': invalid authored timing/ties: '+str(evidence['discrepancies']))
expected=[(a,b-a) for a,b in zip(source_analysis.boundaries,source_analysis.boundaries[1:])]
if [(F(m['start']),F(m['duration'])) for m in evidence['measures']]!=expected:raise ValueError(entry['id']+': source/solo forms differ; register and retrain the correct pair')
notes=[]
for i,event in enumerate(evidence['sounding_events']):
a,b=F(event['start'])-source_analysis.body_offset,F(event['end'])-source_analysis.body_offset
if a<start or b>end:raise ValueError(f'{entry["id"]}: authored event {a}–{b} conflicts with arrangement window {start}–{end}; no clipping applied')
p=event['pitch'];number=F(event['sounding_midi'])
if number.denominator!=1:raise ValueError('Microtonal guitar solo is unsupported')
pitch=types.Pitch(p['step'].lower(),F(p['alter']),p['octave']+(int(number)-int(F(event['encoded_midi'])))//12)
notes.append(types.Note(a,b,pitch,('authored:'+entry['id'],),'authored',event_id=f'authored_{i}'))
profile=profiles.for_part(part,source_analysis)
illegal=next((n for n in notes if not placement.positions(n.pitch.number(),profile)),None)
if illegal:raise ValueError(f'{entry["id"]}: {illegal.pitch.label()} at {illegal.start} is outside strings 1–6/frets 1–12; authored solo left unchanged')
placed=placement.place(tuple(notes),source_analysis.tempo,profile=profile)
if placed is None:raise ValueError(entry['id']+': no legal complete guitar placement')
spans=tuple(dataclasses.replace(s,start=s.start+start,end=s.end+start) for s in harmonic_time.read({'measures':measures},initial=initial))
phrase=types.Phrase('authored',start,end,placed[1],'authored','authored','handoff',theme=entry['id'])
return types.Solo(part,spans,(phrase,),source_analysis.tempo,end,
{'construction':'authored_repertoire','authored_id':entry['id'],'authored_sha256':entry['sha256'],
'pitch_convention':entry['pitch_convention'],'exact_sounding_events':True,
'note':'Authored music is preserved, not reclassified as generated harmonic exceptions.',
'annotations':evidence['annotations']},profile,start)