"""Compact reports of actual selected phrases, not duplicated draft ancestry.""" import collections import dataclasses, fractions, json import generator.solo.harmonic import generator.solo.placement import generator.vocabulary def plain(value): if dataclasses.is_dataclass(value): return plain(dataclasses.asdict(value)) if isinstance(value, dict): return {k: plain(v) for k, v in value.items()} if isinstance(value, (tuple, list, set, frozenset)): return [plain(v) for v in value] if isinstance(value, fractions.Fraction): return str(value) return value def structure(solo, *, composition=None): composition = composition or generator.vocabulary.current() harmony = generator.solo.harmonic.Harmony(solo.harmony, composition=composition) contracts = tuple(c for p in solo.phrases for c in p.expressions) checked = harmony.evaluate(solo.notes(), until=solo.end, complete=True, expressions=contracts) phrases = [] for p in solo.phrases: entry = {'id': p.id, 'start': str(p.start), 'end': str(p.end), 'purpose': p.purpose, 'direction': p.direction, 'ending': p.ending, 'theme': p.theme, 'notes': plain(p.notes), 'rhythm_groups': plain(p.groups), 'sources': p.sources, 'identifying_events': plain(p.identity), 'evaluation': plain(p.evaluation), 'score': p.score, 'expressions': plain(p.expressions)} phrases.append(entry) ns = solo.notes() duration = solo.end - solo.start return {'schema': 'composer-report-3', 'selection_trace': plain({k: v for k, v in solo.diagnostics.items() if not k.endswith('_seconds')}), 'instrument_profile': plain(solo.profile), 'phrases': phrases, 'expressions': plain(contracts), 'fretboard': fretboard_report(solo), 'guitar_placement': generator.solo.placement.describe(solo.notes(), solo.tempo), 'phrasing': {'attacks': len(ns), 'allocation_beats': str(duration), 'attacks_per_four_beats': 4 * len(ns) / float(duration) if duration else 0, 'occupancy': sum(float(n.end - n.start) for n in ns) / float(duration) if duration else 0}, 'harmonic_roles': harmony.describe(ns, expressions=contracts), 'harmonic_policy': 'contextual (shared by authored and predicted music)', 'harmonic_valid': checked.valid, 'harmonic_error': checked.error, 'harmonic_exposure': checked.exposure, 'unresolved_treatments': plain(checked.pending)} def describe(solo, measure_offset=0, time_offset=0, *, detail=None, composition=None): composition = composition or generator.vocabulary.current() detail = detail or structure(solo, composition=composition) lines = [f'{solo.diagnostics.get("construction", "solo")}: {solo.part}', 'Times are exact quarter beats from the chorus start; negative times are allocated lead-ins. Pitches sound one octave below guitar notation.', 'Approximate fingering costs are not a biomechanical guarantee.', json.dumps(detail['phrasing'])] for p in solo.phrases: lines.append(f'{p.id}: {p.start}–{p.end}; {p.purpose}, {p.direction}, {p.ending}; theme {p.theme}; score {p.score:.4f}') for n in p.notes: lines.append(f' {n.start}–{n.end}: {n.pitch.label()}, {n.role}; string {n.position.string_number}, fret {n.position.fret}') for c in p.expressions: lines.append(f' {c.kind}/{c.rule} → {c.target.pitch.label()} at {c.target.start}, held to {c.target.end}') return '\n'.join(lines) + '\n' def fretboard_report(solo): notes = solo.notes() attacks = collections.Counter() durations = collections.Counter() forced = optional = 0 for note in notes: string = note.position.string_number attacks[string] += 1 durations[string] += float(note.end - note.start) if string == 1: if len(generator.solo.placement.positions(note.pitch.number(), solo.profile)) == 1: forced += 1 else: optional += 1 total = sum(durations.values()) return {'attacks_by_string': dict(sorted(attacks.items())), 'duration_fraction_by_string': {s: d / max(.001, total) for s, d in sorted(durations.items())}, 'first_string_forced': forced, 'first_string_optional': optional, 'range_midi': [min(n.pitch.number() for n in notes), max(n.pitch.number() for n in notes)] if notes else [], 'cross_bar_sounding_events': sum(any(n.start < t < n.end for t in {c.start for c in solo.harmony if c.beat == 1}) for n in notes)}