+26
-9
@@ -110,10 +110,10 @@ def combine(document: dict) -> None:
|
||||
b = fragments[end][0] if end in fragments else originals[end]
|
||||
a['tie_to'], b['tie_from'] = b['id'], a['id']
|
||||
|
||||
def validate_comping(document: dict) -> None:
|
||||
"""Check complete sounding events, not barline or notation fragments."""
|
||||
def sounding_lanes(measures):
|
||||
"""Recover complete string-lane events, preserving ties versus reattacks."""
|
||||
tones, starts, cursor = {}, {}, fractions.Fraction()
|
||||
for measure in document['staves'][0]['measures']:
|
||||
for measure in measures:
|
||||
for note in generator.score.measure_notes(measure):
|
||||
for pitch in note['pitches']:
|
||||
tones[pitch['id']] = pitch
|
||||
@@ -122,6 +122,8 @@ def validate_comping(document: dict) -> None:
|
||||
groups = {}
|
||||
for identifier, pitch in tones.items():
|
||||
if pitch.get('tie_from') in tones:
|
||||
if tones[pitch['tie_from']].get('tie_to') != identifier:
|
||||
raise ValueError('broken acoustic sustain link')
|
||||
continue
|
||||
start, tail = starts[identifier], pitch
|
||||
seen = {identifier}
|
||||
@@ -129,20 +131,35 @@ def validate_comping(document: dict) -> None:
|
||||
target = tail['tie_to']
|
||||
if target in seen:
|
||||
raise ValueError('cyclic acoustic sustain')
|
||||
continuation = tones[target]
|
||||
if continuation.get('tie_from') != tail['id'] or starts[target] != starts[tail['id']] + generator.model.fraction(tail['duration']):
|
||||
raise ValueError('broken acoustic sustain link or timing')
|
||||
if generator.model.pitch_number(continuation) != generator.model.pitch_number(tail) or continuation.get('tablature') != tail.get('tablature'):
|
||||
raise ValueError('acoustic sustain changes pitch or position')
|
||||
seen.add(target)
|
||||
tail = tones[target]
|
||||
tail = continuation
|
||||
end = starts[tail['id']] + generator.model.fraction(tail['duration'])
|
||||
string = pitch.get('tablature', {}).get('string')
|
||||
if string is None:
|
||||
raise ValueError('acoustic comping requires placed notes')
|
||||
groups.setdefault((start, end), set()).add('bass' if string >= 5 else 'upper')
|
||||
return groups
|
||||
|
||||
def validate_assignment(measures, events):
|
||||
expected = {}
|
||||
for e in events:
|
||||
lanes = ['bass', 'upper'] if e.lane == 'combined' else [e.lane]
|
||||
expected.setdefault((e.start, e.end), set()).update(lanes)
|
||||
if sounding_lanes(measures) != expected:
|
||||
raise ValueError('rendered acoustic attacks disagree with the selected bass/upper assignment')
|
||||
|
||||
def validate_comping(document: dict) -> None:
|
||||
"""Check complete sounding events, not barline or notation fragments."""
|
||||
groups = sounding_lanes(document['staves'][0]['measures'])
|
||||
events = []
|
||||
for (start, end), lanes in groups.items():
|
||||
if len(lanes) == 2:
|
||||
if end - start < 1 and end - start != fractions.Fraction(1, 2):
|
||||
raise ValueError('combined acoustic attacks require quarter/eighth notes or sustains')
|
||||
elif end - start not in (fractions.Fraction(1, 2), 1):
|
||||
raise ValueError('separate acoustic comping requires quarter/eighth notes')
|
||||
if end <= start or (end - start < 1 and end - start != fractions.Fraction(1, 2)):
|
||||
raise ValueError('acoustic comping requires quarter/eighth notes or authored sustains')
|
||||
events.append((start, end))
|
||||
events.sort()
|
||||
# Distinct lanes can overlap. The physical validator still checks sounding
|
||||
|
||||
Reference in New Issue
Block a user