Soloing & Measures
ci / checks (push) Successful in 1h16m41s

This commit is contained in:
khodowany
2026-10-01 14:05:51 -05:00
parent a7b64c5361
commit 28fc67fba1
21 changed files with 1231 additions and 168 deletions
+9 -18
View File
@@ -130,7 +130,7 @@ def anchor_backbones(identifier, start, end, release, register, choices, bar, se
if terminal_start <= opening + fractions.Fraction(1, 2):
moments = []
moments.append((terminal_start, 'terminal'))
states = [(0.0, ())]
states = [(0.0, (), intention) for intention in ('rising', 'falling', 'returning', 'repeated')]
for index, (time, role) in enumerate(moments):
choice = next(c for c in active if c.span.start <= time < c.span.end)
chord_roles = generator.solo.harmonic.roles(choice.span.chord)
@@ -141,17 +141,18 @@ def anchor_backbones(identifier, start, end, release, register, choices, bar, se
pitches = [p for p in pitches if register[0] <= p.number() <= register[1] and p.number() % 12 in eligible and generator.fretboard.single_positions_for_pitch(fractions.Fraction(p.number()))]
desired = register[0] + (5 if index == 0 else 8 if role == 'terminal' else 11)
following = []
for cost, anchors in states:
for cost, anchors, intention in states:
for pitch in pitches:
jump = abs(pitch.number() - anchors[-1].pitch.number()) if anchors else 0
direction = 1 if role != 'terminal' else -1
direction = 1 if intention == 'rising' or intention == 'returning' and role != 'terminal' else -1 if intention != 'repeated' else 0
wrong_way = bool(anchors and (pitch.number() - anchors[-1].pitch.number()) * direction < 0)
preferred = target_pitches(choice.span.chord, role == 'terminal')
score = cost + jump / 12 + 0.3 * wrong_way + abs(pitch.number() - desired) / 18 + 0.3 * (pitch.number() % 12 not in chord_roles.basic) + 0.2 * (role == 'terminal' and pitch.number() % 12 not in preferred)
destination_motion = 0 if intention == 'repeated' else 4
score = cost + abs(jump - destination_motion) / 12 + 0.8 * wrong_way + abs(pitch.number() - desired) / 36 + 0.3 * (pitch.number() % 12 not in chord_roles.basic) + 0.2 * (role == 'terminal' and pitch.number() % 12 not in preferred)
anchor = generator.solo.types.Anchor(f'{identifier}_anchor_{index + 1}', time, latest, pitch, frozenset(eligible), register, duration, role, generator.harmony.symbol(choice.span.chord), False, finish if role == 'terminal' else None)
following.append((score, (*anchors, anchor)))
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[1][0].pitch.number(), item[1][-1].pitch.number()), generator.solo.policy.DEFAULT.alternatives))
return tuple(anchors for _, anchors in states)
following.append((score, (*anchors, anchor), intention))
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, ...]:
boundaries = [fractions.Fraction()]
@@ -207,23 +208,13 @@ def plans(analysis: generator.solo.analysis.Analysis, measures: list[dict], choi
if slot < 2 and slot not in inventions:
inventions[slot] = new_motif(sequences, bar, choices=local, register=register, seed=passage_seed, variant=slot)
motif = head if slot == 2 else inventions[slot]
gestures, cursor = [], start
while cursor < finish - release:
gesture_end = min(cursor + bar, finish - release)
closing = gesture_end == finish - release
changed = any(c.span.start == cursor and c.scale and prior.scale and c.scale.pitches != prior.scale.pitches for prior, c in zip(choices, choices[1:]))
relationship = 'statement' if not gestures else 'close' if closing else 'repeat_with_harmonic_adaptation' if changed else 'answer' if cursor >= start + (finish - start) / 2 else 'extend'
treatment = 'opening_reference' if not gestures and motif.origin == 'melody' else 'invented' if not gestures else 'rhythmic_reference' if relationship == 'repeat_with_harmonic_adaptation' else 'answer' if relationship in ('answer', 'close') else 'contour_reference'
gestures.append(generator.solo.types.Gesture(f'{passage_id}_gesture_{len(gestures) + 1}', cursor, gesture_end, relationship, treatment))
cursor = gesture_end
phrase_start = start
for phrase_stop in phrase_stops:
phrase_end = boundaries[phrase_stop]
terminal = phrase_end == finish
treatment = 'opening_reference' if motif.origin == 'melody' else 'invented'
local_gestures = tuple(g for g in gestures if g.start < phrase_end and g.end > phrase_start)
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, local_gestures))
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
index = stop
return tuple(result)