"""Exact notated attacks used to compare arrangements; no audio rendering.""" import fractions import generator.model import generator.pitch def timeline(document): events, linked = [], {} absolute = fractions.Fraction() for measure in document['staves'][0]['measures']: for voice in measure['voices']: state = {key: measure[key] for key in ('transpose', 'clef', 'staff_details')} changes = iter(sorted(measure.get('attributes', []), key=lambda a: generator.model.fraction(a['onset']))) change = next(changes, None) for note in voice['notes']: onset = generator.model.fraction(note['onset']) while change is not None and generator.model.fraction(change['onset']) <= onset: state.update({key: change[key] for key in state if key in change}) change = next(changes, None) if note.get('cue') or note.get('grace'): continue for pitch in note['pitches']: if pitch.get('cue'): continue length = generator.model.fraction(pitch['duration']) value = generator.pitch.sounding_pitch(pitch, state) if pitch.get('tie_from'): previous = linked[pitch['tie_from']] assert generator.model.fraction(previous['onset']) + generator.model.fraction(previous['duration']) == absolute + onset assert generator.model.fraction(previous['pitch']) == value previous['duration'] = generator.model.rational(generator.model.fraction(previous['duration']) + length) linked[pitch['id']] = previous else: event = dict(id=pitch['id'], pitch=generator.model.rational(value), onset=generator.model.rational(absolute + onset), duration=generator.model.rational(length)) events.append(event) linked[pitch['id']] = event absolute += generator.model.fraction(measure['duration']) events.sort(key=lambda e: (generator.model.fraction(e['onset']), e['id'])) return dict(duration=generator.model.rational(absolute), events=events)