@@ -26,6 +26,10 @@ class ScoreIndex:
|
||||
self.staff = document['staves'][0]
|
||||
self.measures = []
|
||||
self.events = []
|
||||
self.events_by_onset = {}
|
||||
self.attributes_by_onset = {}
|
||||
self.harmonies_by_onset = {}
|
||||
self.tempos_by_onset = {}
|
||||
self.by_id = {}
|
||||
self.tones = {}
|
||||
self.boundaries = {Fraction()}
|
||||
@@ -48,18 +52,46 @@ class ScoreIndex:
|
||||
for tone in note['pitches']:
|
||||
self.tones[tone['id']] = (event, tone)
|
||||
for field in ('attributes', 'harmonies', 'tempos'):
|
||||
self.columns.update(time + fraction(item['onset']) for item in measure.get(field, []))
|
||||
bucket = {'attributes': self.attributes_by_onset, 'harmonies': self.harmonies_by_onset, 'tempos': self.tempos_by_onset}[field]
|
||||
for index, item in enumerate(measure.get(field, [])):
|
||||
onset = time + fraction(item['onset'])
|
||||
self.columns.add(onset)
|
||||
bucket.setdefault(onset, []).append((measure, index, item))
|
||||
time = end
|
||||
self.end = time
|
||||
self.measure_starts = [item[0] for item in self.measures]
|
||||
self.events.sort(key=lambda e: (e.start, not bool(e.value.get('grace')), e.lane))
|
||||
self.event_starts = []
|
||||
self.event_max_ends = []
|
||||
for event in self.events:
|
||||
self.events_by_onset.setdefault(event.start, []).append(event)
|
||||
self.event_starts.append(event.start)
|
||||
self.event_max_ends.append(max(event.end, self.event_max_ends[-1] if self.event_max_ends else event.end))
|
||||
self.onsets = list(self.events_by_onset)
|
||||
self.column_times = sorted(self.columns)
|
||||
self.boundary_times = sorted(self.boundaries)
|
||||
self.lyric_lanes = list(dict.fromkeys((e.voice, verse['number']) for e in self.events for verse in e.value.get('lyrics', [])))
|
||||
self.accidentals = self.accidental_state()
|
||||
def measure_at(self, time: Fraction, before: bool = False) -> tuple:
|
||||
position = bisect_left(self.measure_starts, time) if before else bisect_right(self.measure_starts, time)
|
||||
return self.measures[max(0, position - 1)]
|
||||
def events_at(self, time: Fraction, include_sustained: bool = False) -> list[Event]:
|
||||
attacks = self.events_by_onset.get(time, [])
|
||||
if not include_sustained:
|
||||
return attacks
|
||||
sustained = []
|
||||
position = bisect_left(self.event_starts, time) - 1
|
||||
# Prefix maxima let system continuations stop searching once every earlier note has ended.
|
||||
while position >= 0 and self.event_max_ends[position] > time:
|
||||
event = self.events[position]
|
||||
if event.end > time:
|
||||
sustained.append(event)
|
||||
position -= 1
|
||||
return list(reversed(sustained)) + attacks
|
||||
def state(self, time: Fraction, before: bool = False) -> dict:
|
||||
if (time, before) in self._states:
|
||||
return self._states[time, before]
|
||||
position = bisect_left(self.measure_starts, time) if before else bisect_right(self.measure_starts, time)
|
||||
start, _, measure = self.measures[max(0, position - 1)]
|
||||
start, _, measure = self.measure_at(time, before)
|
||||
state = {key: measure[key] for key in ('key', 'meter', 'clef', 'transpose', 'staff_details')}
|
||||
for change in measure.get('attributes', []):
|
||||
onset = start + fraction(change['onset'])
|
||||
@@ -82,13 +114,13 @@ class ScoreIndex:
|
||||
for start, end, measure in self.measures:
|
||||
memory = {}
|
||||
key = None
|
||||
onsets = sorted({e.start for e in self.events if start <= e.start < end})
|
||||
onsets = self.onsets[bisect_left(self.onsets, start):bisect_left(self.onsets, end)]
|
||||
for onset in onsets:
|
||||
state = self.state(onset)
|
||||
if key != state['key']:
|
||||
key, memory = state['key'], {}
|
||||
simultaneous = {}
|
||||
for event in (e for e in self.events if e.start == onset):
|
||||
for event in self.events_by_onset[onset]:
|
||||
for tone in event.value['pitches']:
|
||||
hidden_head = isinstance(tone.get('notehead'), dict) and tone['notehead'].get('name') == 'none'
|
||||
if event.value.get('hidden') or tone.get('print', {}).get('print-object') == 'no' or hidden_head:
|
||||
@@ -108,7 +140,7 @@ class ScoreIndex:
|
||||
for identity, values in simultaneous.items():
|
||||
memory[identity] = next(iter(values)) if len(values) == 1 else None
|
||||
if any(len(values) > 1 for values in simultaneous.values()):
|
||||
for event in (e for e in self.events if e.start == onset):
|
||||
for event in self.events_by_onset[onset]:
|
||||
for tone in event.value['pitches']:
|
||||
if 'letter' in tone and len(simultaneous.get((tone['letter'], tone['octave']), ())) > 1:
|
||||
result[tone['id']] = True
|
||||
|
||||
Reference in New Issue
Block a user