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
+23 -21
View File
@@ -17,10 +17,10 @@ import tempfile
import time
from fractions import Fraction as F
from generator import accompaniment, comping, fretboard, model, musicxml, parts, rhythm, score, vocabulary, resources as catalogs
from generator import accompaniment, comping, fretboard, model, musicxml, parts, score, vocabulary
from generator.arrange import form, generate, pickup
from generator.solo import analysis, composer, notation, report, sequences
from generator.scales import library as scales
from generator.solo import analysis, composer, notation, report, learned
from generator.solo import authored as repertoire
VERSION = 'independent-roles-1'
ROLES = ('acoustic_accompaniment', 'electric_accompaniment', 'electric_melody', 'vocal_melody', 'acoustic_solo', 'electric_solo')
@@ -78,7 +78,7 @@ def windows(document, part):
def dependencies(role, resources):
names = ('solo', 'harmony', 'lead_ins', 'rhythms') if role.endswith('_solo') else ('rhythms',) if role.endswith('_accompaniment') else ()
names = ('harmony', 'lead_ins') if role.endswith('_solo') else ('accompaniment',) if role.endswith('_accompaniment') else ()
return {'roles': {'role': resources['roles']['roles'][role], 'difficulty': resources['roles']['difficulty']},
**{name: resources[name] for name in names}}
@@ -88,8 +88,8 @@ def _code_key(role):
paths = [root / n for n in ('production.py', 'model.py', 'harmony.py', 'pitch.py', 'fretboard.py', 'parts.py', 'score.py', 'vocabulary.py')]
paths += list((root/'arrange').glob('*.py'))
if role.endswith('_solo'):
paths += [p for p in (root/'solo').glob('*.py') if 'audition' not in p.name and p.name != 'references.py']
paths += list((root/'scales').glob('*.py')) + [root/'rhythm.py', root/'resources.py', root/'musicxml.py']
paths += list((root/'solo').glob('*.py'))
paths += [root/'harmonic_time.py', root/'musicxml.py',root/'training_io.py']
elif role.endswith('_accompaniment'):
paths += [root/n for n in ('comping.py', 'accompaniment.py', 'guitar.py')]
elif role == 'electric_melody':
@@ -98,12 +98,11 @@ def _code_key(role):
def _resource_key(directory, role):
names = ('scales', 'sequences', 'rhythms') if role.endswith('_solo') else ()
return {name: vocabulary.fingerprint([(p.relative_to(directory/name).as_posix(), hashlib.sha256(p.read_bytes()).hexdigest()) for p in sorted((directory/name).rglob('*.xml'))]) for name in names}
return {'model': hashlib.sha256((directory/'models'/'solo.json').read_bytes()).hexdigest()} if role.endswith('_solo') else {}
@functools.lru_cache(maxsize=4)
def _solo_libraries(directory, fingerprints, cache_dir=None):
return catalogs.load(directory,dict(fingerprints),cache_dir)
def _solo_model(directory, fingerprint):
return learned.Model.load(Path(directory)/'models'/'solo.json')
def _mask(document, allowed):
@@ -153,7 +152,7 @@ def generate_role(source, role, data_dir, settings=None, cache_dir=None):
with composition(data_dir,role,cache_dir) as reads:
result=_generate_role(source,role,data_dir,settings,cache_dir)
# Observed reads are diagnostics, never cache identity or musical seeds.
result['trace']={**result['trace'],'input_boundary':'data, generator code, Python runtime, own role cache, content-addressed immutable catalogs'}
result['trace']={**result['trace'],'input_boundary':'production data (including explicit authored repertoire), generator code, Python runtime, own role cache'}
result['observed_reads']=sorted(reads)
result['seconds']=time.monotonic()-started
return result
@@ -163,10 +162,10 @@ def _generate_role(source, role, data_dir, settings=None, cache_dir=None):
if role not in ROLES: raise ValueError('Unknown generation role: '+role)
data_dir=Path(data_dir); resources=vocabulary.load(data_dir/'composition')
settings=settings or {}
from generator.solo.policy import DEFAULT
algorithm=VERSION+'/'+DEFAULT.composition_version if role.endswith('_solo') else VERSION
registered=repertoire.lookup(source,role.removesuffix('_solo'),data_dir) if role.endswith('_solo') and not settings.get('predict',False) else None
algorithm=VERSION+'/'+learned.VERSION if role.endswith('_solo') else VERSION
inputs={'version':algorithm,'source':source.musical_key,'role':role,'settings':settings,
'vocabulary':dependencies(role,resources),'catalogs':_resource_key(data_dir,role),'code':_code_key(role)}
'vocabulary':dependencies(role,resources),'catalogs':({'authored':registered['sha256'],'id':registered['id'],'pitch_convention':registered['pitch_convention']} if registered else _resource_key(data_dir,role)),'code':_code_key(role)}
# Source notation/lyrics also invalidate cached score representation, never the musical seed.
key=vocabulary.fingerprint({'inputs':inputs,'notation':source.document_json})
path=Path(cache_dir)/role/(key+'.json') if cache_dir else None
@@ -185,7 +184,7 @@ def _generate_role(source, role, data_dir, settings=None, cache_dir=None):
with vocabulary.using(resources,role,settings|{'choice_seed':seed}):
if role.endswith('_solo'):
result=generate._arrange_document(document,part,{},owned={'slash'})
from generator.solo.arrangement import regions,annotate
from generator.solo.arrangement import regions
si=3 if part=='acoustic' else 4
first,count,offset,length=regions(document)[si]
# The template/harmony comes directly from source form, not another generated part.
@@ -202,17 +201,20 @@ def _generate_role(source, role, data_dir, settings=None, cache_dir=None):
template.insert(0,copy.deepcopy(result['staves'][0]['measures'][first-1]))
notation_first-=1
initial=accompaniment.carried_chords(result['staves'][0]['measures'])[first-1]
scale_library,sequence_library,rhythm_library=_solo_libraries(data_dir,tuple(sorted(inputs['catalogs'].items())),cache_dir)
solo=composer.compose(analysis.analyze(document),template,part,end,
initial=initial,scales=scale_library,sequences=sequence_library,rhythms=rhythm_library,
expressions_enabled=vocabulary.role().get('expressions',True),start=start)
if registered:
solo=repertoire.realize(registered,analysis.analyze(document),template,part,start,end,initial)
else:
solo_model=_solo_model(str(data_dir),inputs['catalogs']['model'])
solo=composer.compose(analysis.analyze(document),template,part,end,
initial=initial,learned_model=solo_model,
expressions_enabled=vocabulary.role().get('expressions',True),start=start)
composition_timings={k:v for k,v in solo.diagnostics.items() if k.endswith('_seconds')}
relative=tuple(dataclasses.replace(n,start=n.start-start,end=n.end-start) for n in solo.notes())
output=notation.measures(result['staves'][0]['measures'][notation_first:first+count],relative,'solo_'+part)
notation.replace_region(result,notation_first,output);annotate(result,solo,offset)
notation.replace_region(result,notation_first,output)
details=report.structure(solo)
details['allocation']={'start':str(start),'end':str(end),'lead_in_measures':first-notation_first}
solo_report=report.describe(solo,scale_library,first,offset,detail=details)
solo_report=report.describe(solo,first,offset,detail=details)
elif role.endswith('_accompaniment'):
canonical=comping.Canonical(document,part,seed)
result=generate._arrange_document(document,part,{},owned={'accompaniment'},accompaniment=canonical)