This commit is contained in:
khodowany
2026-10-01 20:20:24 -05:00
parent f5861fcdf6
commit f2f371b951
20 changed files with 700 additions and 32 deletions
+6 -4
View File
@@ -152,7 +152,7 @@ def anchor_backbones(identifier, start, end, release, register, choices, bar, se
states = list(generator.solo.sequences.diverse(following, lambda item: (item[0], generator.solo.policy.stable_rank(seed, tuple(a.pitch for a in item[1]))), lambda item: (item[2], item[1][0].pitch.number()), generator.solo.policy.DEFAULT.anchor_lines))
return tuple(dict.fromkeys(anchors for _, anchors, _ in states))
def plans(analysis: generator.solo.analysis.Analysis, measures: list[dict], choices, end: fractions.Fraction, part: str, sequences: generator.solo.sequences.Library) -> tuple[generator.solo.types.PhrasePlan, ...]:
def plans(analysis: generator.solo.analysis.Analysis, measures: list[dict], choices, end: fractions.Fraction, part: str, sequences: generator.solo.sequences.Library, *, lead=False) -> tuple[generator.solo.types.PhrasePlan, ...]:
boundaries = [fractions.Fraction()]
for measure in measures:
boundaries.append(min(end, boundaries[-1] + generator.model.fraction(measure['duration'])))
@@ -187,13 +187,15 @@ def plans(analysis: generator.solo.analysis.Analysis, measures: list[dict], choi
release = fractions.Fraction(1, 2) if decision(passage_seed, 'breathing') % 3 == 0 else fractions.Fraction()
release = min(release, (finish - start) / 8)
envelope = activity_passages(start, finish, release, activity, decision(passage_seed, 'activity') % 3)
anchors = anchor_backbones(passage_id, start, finish, release, register, choices, bar, passage_seed)
anchors = () if lead else anchor_backbones(passage_id, start, finish, release, register, choices, bar, passage_seed)
# Prefer four-bar phrasing; extend when a sustained harmony crosses that seam.
middle = min(index + 4, stop)
before = next((c for c in reversed(choices) if c.span.start < boundaries[middle]), None)
after = next((c for c in choices if c.span.start <= boundaries[middle] < c.span.end), None)
extended = stop - index > 4 and same_harmony(before, after)
phrase_stops = [stop] if extended or middle == stop else [middle, stop]
# A bounded complete lead draft can express an extended phrase directly.
# Computational note groups below do not require additional phrase ends.
phrase_stops = [stop] if lead or extended or middle == stop else [middle, stop]
local = tuple(c for c in choices if c.span.start < boundaries[phrase_stops[0]] and c.span.end > start)
slot = decision(passage_seed, 'motif') % 3
if part == 'acoustic':
@@ -210,7 +212,7 @@ def plans(analysis: generator.solo.analysis.Analysis, measures: list[dict], choi
for phrase_stop in phrase_stops:
phrase_end = boundaries[phrase_stop]
terminal = phrase_end == finish
treatment = 'opening_reference' if motif.origin == 'melody' else 'invented'
treatment = 'lead' if lead else 'opening_reference' if motif.origin == 'melody' else 'invented'
landmarks = tuple(generator.solo.types.Landmark(a.earliest, a.latest, min(v[i].pitch.number() for v in anchors), max(v[i].pitch.number() for v in anchors), 0, a.role) for i, a in enumerate(anchors[0])) if anchors and anchors[0] else ()
result.append(generator.solo.types.PhrasePlan(f'{part}_phrase_{len(result) + 1}', phrase_start, phrase_end, motif, 'statement' if phrase_start == start else 'continue', treatment, None, register, activity, release if terminal else fractions.Fraction(), envelope, landmarks, 'breath' if terminal and release else 'sustain' if terminal else 'connected', 'closed' if terminal else 'open', passage_seed, passage_id, start, finish, anchors))
phrase_start = phrase_end