Source code for abjad.verticalmoment

from . import enumerate as _enumerate
from . import iterate as _iterate
from . import parentage as _parentage
from . import score as _score
from . import select as _select
from . import sequence as _sequence


[docs]class VerticalMoment: r''' Vertical moment. .. container:: example >>> score = abjad.Score() >>> staff_group = abjad.StaffGroup() >>> staff_group.lilypond_type = 'PianoStaff' >>> staff_group.append(abjad.Staff("c'4 e'4 d'4 f'4")) >>> staff_group.append(abjad.Staff(r"""\clef "bass" g2 f2""")) >>> score.append(staff_group) >>> abjad.show(score) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(score) >>> print(string) \new Score << \new PianoStaff << \new Staff { c'4 e'4 d'4 f'4 } \new Staff { \clef "bass" g2 f2 } >> >> >>> for moment in abjad.iterate_vertical_moments(score): ... moment ... VerticalMoment(0, <<2>>) VerticalMoment(1/4, <<2>>) VerticalMoment(1/2, <<2>>) VerticalMoment(3/4, <<2>>) ''' ### CLASS VARIABLES ### __slots__ = ("_components", "_governors", "_offset") ### INITIALIZER ### def __init__(self, components=None, offset=None): self._components = components or () self._offset = offset ### SPECIAL METHODS ###
[docs] def __eq__(self, argument) -> bool: """ Is true when ``argument`` is a vertical moment with the same components as this vertical moment. """ if isinstance(argument, type(self)): if len(self) == len(argument): for c, d in zip(self.components, argument.components): if c is not d: return False else: return True return False
[docs] def __hash__(self): """ Hases vertical moment. .. container:: example Vertical moments can be hashed: >>> staff = abjad.Staff("c'8 d'8 e'8 f'8") >>> vms = [] >>> vms.extend(abjad.iterate_vertical_moments(staff)) >>> vms.extend(abjad.iterate_vertical_moments(staff)) >>> assert len(vms) == 8 >>> assert len(set(vms)) == 4 Redefined in tandem with __eq__. """ if self.components: return hash(tuple([id(_) for _ in self.components])) return 0
[docs] def __len__(self): r""" Length of vertical moment. .. container:: example >>> score = abjad.Score( ... r''' ... \new Staff { ... \times 4/3 { ... d''8 ... c''8 ... b'8 ... } ... } ... \new PianoStaff << ... \new Staff { ... a'4 ... g'4 ... } ... \new Staff { ... \clef "bass" ... f'8 ... e'8 ... d'8 ... c'8 ... } ... >> ... ''' ... ) >>> abjad.show(score) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(score) >>> print(string) \new Score << \new Staff { \tweak text #tuplet-number::calc-fraction-text \times 4/3 { d''8 c''8 b'8 } } \new PianoStaff << \new Staff { a'4 g'4 } \new Staff { \clef "bass" f'8 e'8 d'8 c'8 } >> >> >>> for moment in abjad.iterate_vertical_moments(score): ... print(moment, len(moment)) ... VerticalMoment(0, <<3>>) 9 VerticalMoment(1/8, <<3>>) 9 VerticalMoment(1/6, <<3>>) 9 VerticalMoment(1/4, <<3>>) 9 VerticalMoment(1/3, <<3>>) 9 VerticalMoment(3/8, <<3>>) 9 Defined equal to the number of components in vertical moment. Returns nonnegative integer. """ return len(self.components)
[docs] def __repr__(self): """ Gets interpreter representation of vertical moment. Returns string. """ if not self.components: return f"{type(self).__name__}()" length = len(self.leaves) result = f"{type(self).__name__}({str(self.offset)}, <<{length}>>)" return result
### PUBLIC PROPERTIES ### @property def attack_count(self) -> int: r""" Positive integer number of pitch carriers starting at vertical moment. .. container:: example >>> score = abjad.Score( ... r''' ... \new Staff { ... \times 4/3 { ... d''8 ... c''8 ... b'8 ... } ... } ... \new PianoStaff << ... \new Staff { ... a'4 ... g'4 ... } ... \new Staff { ... \clef "bass" ... f'8 ... e'8 ... d'8 ... c'8 ... } ... >> ... ''' ... ) >>> abjad.show(score) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(score) >>> print(string) \new Score << \new Staff { \tweak text #tuplet-number::calc-fraction-text \times 4/3 { d''8 c''8 b'8 } } \new PianoStaff << \new Staff { a'4 g'4 } \new Staff { \clef "bass" f'8 e'8 d'8 c'8 } >> >> >>> for moment in abjad.iterate_vertical_moments(score): ... print(moment, moment.attack_count) ... VerticalMoment(0, <<3>>) 3 VerticalMoment(1/8, <<3>>) 1 VerticalMoment(1/6, <<3>>) 1 VerticalMoment(1/4, <<3>>) 2 VerticalMoment(1/3, <<3>>) 1 VerticalMoment(3/8, <<3>>) 1 """ leaves = [] for leaf in self.start_leaves: if isinstance(leaf, _score.Note | _score.Chord): leaves.append(leaf) return len(leaves) @property def components(self): """ Tuple of zero or more components happening at vertical moment. It is always the case that ``self.components = self.overlap_components + self.start_components``. """ return self._components @property def governors(self): """ Tuple of one or more containers in which vertical moment is evaluated. """ return self._governors @property def leaves(self) -> list[_score.Leaf]: r""" Tuple of zero or more leaves at vertical moment. .. container:: example >>> score = abjad.Score( ... r''' ... \new Staff { ... \times 4/3 { ... d''8 ... c''8 ... b'8 ... } ... } ... \new PianoStaff << ... \new Staff { ... a'4 ... g'4 ... } ... \new Staff { ... \clef "bass" ... f'8 ... e'8 ... d'8 ... c'8 ... } ... >> ... ''' ... ) >>> abjad.show(score) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(score) >>> print(string) \new Score << \new Staff { \tweak text #tuplet-number::calc-fraction-text \times 4/3 { d''8 c''8 b'8 } } \new PianoStaff << \new Staff { a'4 g'4 } \new Staff { \clef "bass" f'8 e'8 d'8 c'8 } >> >> >>> for moment in abjad.iterate_vertical_moments(score): ... print(moment.offset, moment.leaves) ... 0 [Note("d''8"), Note("a'4"), Note("f'8")] 1/8 [Note("d''8"), Note("a'4"), Note("e'8")] 1/6 [Note("c''8"), Note("a'4"), Note("e'8")] 1/4 [Note("c''8"), Note("g'4"), Note("d'8")] 1/3 [Note("b'8"), Note("g'4"), Note("d'8")] 3/8 [Note("b'8"), Note("g'4"), Note("c'8")] """ result = [] for component in self.components: if isinstance(component, _score.Leaf): result.append(component) return result @property def notes(self): """ Tuple of zero or more notes at vertical moment. """ result = [] for component in self.components: if isinstance(component, _score.Note): result.append(component) result = tuple(result) return result @property def notes_and_chords(self): """ Tuple of zero or more notes and chords at vertical moment. """ result = [] prototype = (_score.Chord, _score.Note) for component in self.components: if isinstance(component, prototype): result.append(component) result = tuple(result) return result @property def offset(self): """ Rational-valued score offset at which vertical moment is evaluated. """ return self._offset @property def overlap_components(self): """ Tuple of components in vertical moment starting before vertical moment, ordered by score index. """ result = [] for component in self.components: if component.start < self.offset: result.append(component) result = tuple(result) return result @property def overlap_leaves(self): """ Tuple of leaves in vertical moment starting before vertical moment, ordered by score index. """ result = [x for x in self.overlap_components if isinstance(x, _score.Leaf)] result = tuple(result) return result @property def overlap_notes(self): """ Tuple of notes in vertical moment starting before vertical moment, ordered by score index. """ result = self.overlap_components result = [_ for _ in result if isinstance(_, _score.Note)] result = tuple(result) return result @property def start_components(self): """ Tuple of components in vertical moment starting with at vertical moment, ordered by score index. """ result = [] for component in self.components: if component._get_timespan().start_offset == self.offset: result.append(component) result = tuple(result) return result @property def start_leaves(self): """ Tuple of leaves in vertical moment starting with vertical moment, ordered by score index. """ result = [x for x in self.start_components if isinstance(x, _score.Leaf)] result = tuple(result) return result @property def start_notes(self): """ Tuple of notes in vertical moment starting with vertical moment, ordered by score index. """ result = [x for x in self.start_components if isinstance(x, _score.Note)] result = tuple(result) return result
### FUNCTIONS ###
[docs]def iterate_vertical_moments(components, reverse=None): r''' Iterates vertical moments. .. container:: example Iterates vertical moments: >>> score = abjad.Score([]) >>> staff = abjad.Staff(r"\times 4/3 { d''8 c''8 b'8 }") >>> score.append(staff) >>> staff_group = abjad.StaffGroup([]) >>> staff_group.lilypond_type = 'PianoStaff' >>> staff_group.append(abjad.Staff("a'4 g'4")) >>> staff_group.append(abjad.Staff(r"""\clef "bass" f'8 e'8 d'8 c'8""")) >>> score.append(staff_group) >>> abjad.show(score) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(score) >>> print(string) \new Score << \new Staff { \tweak text #tuplet-number::calc-fraction-text \times 4/3 { d''8 c''8 b'8 } } \new PianoStaff << \new Staff { a'4 g'4 } \new Staff { \clef "bass" f'8 e'8 d'8 c'8 } >> >> >>> for vertical_moment in abjad.iterate_vertical_moments(score): ... vertical_moment.leaves ... [Note("d''8"), Note("a'4"), Note("f'8")] [Note("d''8"), Note("a'4"), Note("e'8")] [Note("c''8"), Note("a'4"), Note("e'8")] [Note("c''8"), Note("g'4"), Note("d'8")] [Note("b'8"), Note("g'4"), Note("d'8")] [Note("b'8"), Note("g'4"), Note("c'8")] >>> for vertical_moment in abjad.iterate_vertical_moments(staff_group): ... vertical_moment.leaves ... [Note("a'4"), Note("f'8")] [Note("a'4"), Note("e'8")] [Note("g'4"), Note("d'8")] [Note("g'4"), Note("c'8")] .. container:: example Iterates vertical moments in reverse: >>> score = abjad.Score([]) >>> staff = abjad.Staff(r"\times 4/3 { d''8 c''8 b'8 }") >>> score.append(staff) >>> staff_group = abjad.StaffGroup([]) >>> staff_group.lilypond_type = 'PianoStaff' >>> staff_group.append(abjad.Staff("a'4 g'4")) >>> staff_group.append(abjad.Staff(r"""\clef "bass" f'8 e'8 d'8 c'8""")) >>> score.append(staff_group) >>> abjad.show(score) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(score) >>> print(string) \new Score << \new Staff { \tweak text #tuplet-number::calc-fraction-text \times 4/3 { d''8 c''8 b'8 } } \new PianoStaff << \new Staff { a'4 g'4 } \new Staff { \clef "bass" f'8 e'8 d'8 c'8 } >> >> >>> for vertical_moment in abjad.iterate_vertical_moments(score, reverse=True): ... vertical_moment.leaves ... [Note("b'8"), Note("g'4"), Note("c'8")] [Note("b'8"), Note("g'4"), Note("d'8")] [Note("c''8"), Note("g'4"), Note("d'8")] [Note("c''8"), Note("a'4"), Note("e'8")] [Note("d''8"), Note("a'4"), Note("e'8")] [Note("d''8"), Note("a'4"), Note("f'8")] >>> for vertical_moment in abjad.iterate_vertical_moments( ... staff_group, ... reverse=True, ... ): ... vertical_moment.leaves ... [Note("g'4"), Note("c'8")] [Note("g'4"), Note("d'8")] [Note("a'4"), Note("e'8")] [Note("a'4"), Note("f'8")] Returns tuple. ''' moments = [] components = _select.components(components) components.sort(key=lambda _: _._get_timespan().start_offset) offset_to_components = dict() for component in components: start_offset = component._get_timespan().start_offset if start_offset not in offset_to_components: offset_to_components[start_offset] = [] # TODO: optimize with bisect for component in components: inserted = False timespan = component._get_timespan() for offset, list_ in offset_to_components.items(): if ( timespan.start_offset <= offset < timespan.stop_offset and component not in list_ ): list_.append(component) inserted = True elif inserted is True: break moments = [] for offset, list_ in offset_to_components.items(): list_.sort(key=lambda _: _parentage.Parentage(_).score_index()) moment = VerticalMoment(components=list_, offset=offset) moments.append(moment) if reverse is True: moments.reverse() return tuple(moments)
[docs]def iterate_leaf_pairs(components): r""" Iterates leaf pairs. .. container:: example >>> score = abjad.Score() >>> score.append(abjad.Staff("c'8 d'8 e'8 f'8 g'4")) >>> score.append(abjad.Staff("c4 a,4 g,4")) >>> abjad.attach(abjad.Clef('bass'), score[1][0]) >>> abjad.show(score) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(score) >>> print(string) \new Score << \new Staff { c'8 d'8 e'8 f'8 g'4 } \new Staff { \clef "bass" c4 a,4 g,4 } >> >>> for leaf_pair in abjad.iterate_leaf_pairs(score): ... leaf_pair [Note("c'8"), Note('c4')] [Note("c'8"), Note("d'8")] [Note('c4'), Note("d'8")] [Note("d'8"), Note("e'8")] [Note("d'8"), Note('a,4')] [Note('c4'), Note("e'8")] [Note('c4'), Note('a,4')] [Note("e'8"), Note('a,4')] [Note("e'8"), Note("f'8")] [Note('a,4'), Note("f'8")] [Note("f'8"), Note("g'4")] [Note("f'8"), Note('g,4')] [Note('a,4'), Note("g'4")] [Note('a,4'), Note('g,4')] [Note("g'4"), Note('g,4')] Iterates leaf pairs left-to-right and top-to-bottom. Returns generator. """ vertical_moments = iterate_vertical_moments(components) for moment_1, moment_2 in _sequence.nwise(vertical_moments): for pair in _enumerate.yield_pairs(moment_1.start_leaves): yield list(pair) sequences = [moment_1.leaves, moment_2.start_leaves] for pair in _enumerate.outer_product(sequences): yield list(pair) else: for pair in _enumerate.yield_pairs(moment_2.start_leaves): yield list(pair)
[docs]def iterate_pitch_pairs(components): r""" Iterates pitch pairs. .. container:: example Iterates note pitch pairs: >>> score = abjad.Score() >>> score.append(abjad.Staff("c'8 d' e' f' g'4")) >>> score.append(abjad.Staff("c4 a, g,")) >>> abjad.attach(abjad.Clef('bass'), score[1][0]) >>> abjad.show(score) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(score) >>> print(string) \new Score << \new Staff { c'8 d'8 e'8 f'8 g'4 } \new Staff { \clef "bass" c4 a,4 g,4 } >> >>> for pair in abjad.iterate_pitch_pairs(score): ... pair (NamedPitch("c'"), NamedPitch('c')) (NamedPitch("c'"), NamedPitch("d'")) (NamedPitch('c'), NamedPitch("d'")) (NamedPitch("d'"), NamedPitch("e'")) (NamedPitch("d'"), NamedPitch('a,')) (NamedPitch('c'), NamedPitch("e'")) (NamedPitch('c'), NamedPitch('a,')) (NamedPitch("e'"), NamedPitch('a,')) (NamedPitch("e'"), NamedPitch("f'")) (NamedPitch('a,'), NamedPitch("f'")) (NamedPitch("f'"), NamedPitch("g'")) (NamedPitch("f'"), NamedPitch('g,')) (NamedPitch('a,'), NamedPitch("g'")) (NamedPitch('a,'), NamedPitch('g,')) (NamedPitch("g'"), NamedPitch('g,')) .. container:: example Iterates chord pitch pairs: >>> staff = abjad.Staff("<c' d' e'>4 <f'' g''>4") .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { <c' d' e'>4 <f'' g''>4 } >>> for pair in abjad.iterate_pitch_pairs(staff): ... pair ... (NamedPitch("c'"), NamedPitch("d'")) (NamedPitch("c'"), NamedPitch("e'")) (NamedPitch("d'"), NamedPitch("e'")) (NamedPitch("c'"), NamedPitch("f''")) (NamedPitch("c'"), NamedPitch("g''")) (NamedPitch("d'"), NamedPitch("f''")) (NamedPitch("d'"), NamedPitch("g''")) (NamedPitch("e'"), NamedPitch("f''")) (NamedPitch("e'"), NamedPitch("g''")) (NamedPitch("f''"), NamedPitch("g''")) Returns generator. """ for leaf_pair in iterate_leaf_pairs(components): pitches = sorted(_iterate.pitches(leaf_pair[0])) for pair in _enumerate.yield_pairs(pitches): yield tuple(pair) if isinstance(leaf_pair, set): pitches = sorted(_iterate.pitches(leaf_pair)) for pair in _enumerate.yield_pairs(pitches): yield tuple(pair) else: pitches_1 = sorted(_iterate.pitches(leaf_pair[0])) pitches_2 = sorted(_iterate.pitches(leaf_pair[1])) sequences = [pitches_1, pitches_2] for pair in _enumerate.outer_product(sequences): yield tuple(pair) pitches = sorted(_iterate.pitches(leaf_pair[1])) for pair in _enumerate.yield_pairs(pitches): yield tuple(pair)