Simplification
This commit is contained in:
@@ -327,6 +327,23 @@ def audition_pair(actual,expected,role,assets,output,cache,baseline=None):
|
||||
'listening_status':'Rendered and signal-checked, not subjectively auditioned.'}
|
||||
|
||||
|
||||
def candidate_clips(candidates,role,tempo,output,cache):
|
||||
"""Render the already completed source-only alternatives, never reselect."""
|
||||
from generator import playback
|
||||
clips=[]
|
||||
for index,candidate in enumerate(candidates,1):
|
||||
origin,end=F(candidate['start']),F(candidate['end'])
|
||||
audio=render_events([(F(a)-origin,F(b)-origin,F(p)) for a,b,p in candidate['events']],end-origin,tempo,output,cache)
|
||||
peak=.65*max(map(abs,audio),default=0)
|
||||
if peak>=1:raise ValueError('Candidate excerpt would clip at comparison gain')
|
||||
name='audio/'+role.replace('/','_')+f'_candidate_{index}.wav'
|
||||
playback.write_pcm(output/name,(.65*v for v in audio))
|
||||
selected=candidate['selected_in_final_solo']
|
||||
clips.append({'file':name,'version':f'Completed opening {index}'+(' — selected in final solo' if selected else ' — alternative'),
|
||||
'context':'source-only candidate, solo','peak':peak})
|
||||
return clips
|
||||
|
||||
|
||||
def run(generated_dir,reference_paths,starts,pitch_convention,output,assets=None,baseline_dir=None):
|
||||
generated_dir,output=Path(generated_dir),Path(output)
|
||||
manifest=json.loads((generated_dir/'manifest.solo').read_text())
|
||||
@@ -348,6 +365,9 @@ def run(generated_dir,reference_paths,starts,pitch_convention,output,assets=None
|
||||
if assets:review['audition']=audition_pair(actual,expected,role,assets,output,tone_cache,baseline)
|
||||
review['production_trace']=item['trace']
|
||||
review['production_phrases']=item.get('phrase_report',{})
|
||||
candidates=review['production_phrases'].get('selection_trace',{}).get('opening_candidates',[])
|
||||
if assets and candidates:
|
||||
review['audition']['clips']+=candidate_clips(candidates,role,F(review['audition']['tempo']),output,tone_cache)
|
||||
# Preserve the real role cutoff; reference material beyond it is not trimmed.
|
||||
phrases=review['production_phrases'].get('phrases',[])
|
||||
if phrases:
|
||||
@@ -384,6 +404,12 @@ def run(generated_dir,reference_paths,starts,pitch_convention,output,assets=None
|
||||
for purpose,counts in sorted(funnel.items()):
|
||||
rows.append('<tr><td>'+html.escape(purpose)+'</td>'+''.join('<td>'+str(counts.get(k,0))+'</td>' for k in stages)+'</tr>')
|
||||
rows.append('</table></details>')
|
||||
candidates=r['production_phrases'].get('selection_trace',{}).get('opening_candidates',[])
|
||||
if candidates:
|
||||
rows.append('<details open><summary>Completed source-only opening candidates</summary><p>All candidates were composed without the references. Selection accounts for later phrases; the final choice need not have the cheapest opening alone.</p><table><tr><th>Candidate</th><th>Final choice</th><th>Purpose</th><th>Score contributions</th></tr>')
|
||||
for index,c in enumerate(candidates,1):
|
||||
rows.append('<tr>'+''.join('<td>'+html.escape(str(v))+'</td>' for v in (index,c['selected_in_final_solo'],c['purpose'],c['evaluation']['contributions']))+'</tr>')
|
||||
rows.append('</table></details>')
|
||||
for clip in r.get('audition',{}).get('clips',[]):
|
||||
rows.append('<p>'+html.escape(clip['version']+' / '+clip['context'])+'<br><audio controls preload="none" src="'+html.escape(clip['file'])+'"></audio></p>')
|
||||
rows.append('<details><summary>Exact note differences</summary><table><tr><th>Beat relative to form</th><th>Generated</th><th>Reference</th></tr>')
|
||||
|
||||
Reference in New Issue
Block a user