This commit is contained in:
khodowany
2026-10-02 00:35:31 -05:00
parent f2f371b951
commit a0bd19b3ba
25 changed files with 1421 additions and 586 deletions
+3 -29
View File
@@ -99,32 +99,6 @@ class Harmony:
for treatment in treatments:
if treatment.start >= until:
continue
if treatment.kind == 'chromatic_approach':
approach = next((n for n in notes if n.event_id == treatment.event), None)
if (approach is None or approach.role == 'quote' or approach.pitch.number() != treatment.pitch
or approach.start != treatment.start or approach.end != treatment.end
or approach.end - approach.start != fractions.Fraction(1, 2)
or approach.start % 1 != fractions.Fraction(1, 2)
or treatment.resolution is None or abs(treatment.pitch - treatment.resolution) != 1
or treatment.deadline != approach.end or len(self.segments(approach)) != 1):
return Result(False, 0, error=f'{treatment.id}: invalid chromatic approach event or timing')
span = self.at(approach.start)
if not span or not span.scale or treatment.pitch % 12 in span.scale.pitches:
return Result(False, 0, error=f'{treatment.id}: approach requires mapped harmony and an out-of-collection pitch')
resolution = next((n for n in notes if n.start == approach.end), None)
if resolution:
destination = self.at(resolution.start)
if (resolution.event_id != treatment.resolution_event or resolution.continuation
or resolution.pitch.number() != treatment.resolution or not destination or not destination.scale
or treatment.resolution % 12 not in destination.roles.basic
or treatment.resolution % 12 not in generator.solo.types.permitted(self.choices, resolution.start, resolution.end)):
return Result(False, 0, error=f'{treatment.id}: chromatic approach has no immediate chord-tone resolution')
elif complete or until > approach.end:
return Result(False, 0, error=f'{treatment.id}: unresolved chromatic approach')
else:
pending.append(treatment)
valid_contracts.append(treatment)
continue
if treatment.kind == 'color':
spans = [s for s in self.spans if s.start < treatment.end and s.end > treatment.start]
if not spans or any(treatment.pitch % 12 not in s.roles.basic | s.roles.specified for s in spans):
@@ -155,8 +129,8 @@ class Harmony:
sounding += float(duration)
covered = note.start
for span, start, end in self.segments(note):
contract = next((t for t in valid_contracts if t.pitch == number and t.start <= start and t.end >= end and (t.kind != 'chromatic_approach' or t.event == note.event_id)), None)
if start > covered or not span.scale or number % 12 not in span.scale.pitches and not (contract and contract.kind == 'chromatic_approach'):
contract = next((t for t in valid_contracts if t.pitch == number and t.start <= start and t.end >= end), None)
if start > covered or not span.scale or number % 12 not in span.scale.pitches:
return Result(False, 0, error=f'{note.pitch.label()} at {start}-{end}: outside mapped collection or missing harmony')
covered = end
function = self.function(number, span)
@@ -176,7 +150,7 @@ class Harmony:
for index, note in enumerate(notes):
for span, start, end in self.segments(note):
contracts = [t for t in treatments if t.pitch == note.pitch.number() and t.start <= start and t.end >= end]
result.append({'start': str(start), 'end': str(end), 'attack': str(note.start), 'sounding_duration': str(note.end - note.start), 'pitch': note.pitch.label(), 'chord': span.chord, 'function': self.function(note.pitch.number(), span), 'motion': 'chromatic_approach' if any(t.kind == 'chromatic_approach' for t in contracts) else self.connected_motion(notes, index), 'protected': note.role == 'quote', 'treatments': [t.id for t in contracts]})
result.append({'start': str(start), 'end': str(end), 'attack': str(note.start), 'sounding_duration': str(note.end - note.start), 'pitch': note.pitch.label(), 'chord': span.chord, 'function': self.function(note.pitch.number(), span), 'motion': self.connected_motion(notes, index), 'protected': note.role == 'quote', 'treatments': [t.id for t in contracts]})
return result
def anchor_note(anchor, notes, *, until=None):