1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Name:         base.py
4# Purpose:      Music21 base classes and important utilities
5#
6# Authors:      Michael Scott Cuthbert
7#               Christopher Ariza
8#
9# Copyright:    Copyright © 2006-2021 Michael Scott Cuthbert and the music21
10#               Project
11# License:      BSD, see license.txt
12# -----------------------------------------------------------------------------
13'''
14`music21.base` is what you get in `music21` if you type ``import music21``. It
15contains all the most low-level objects that also appear in the music21 module
16(i.e., music21.base.Music21Object is the same as music21.Music21Object).
17
18Music21 base classes for :class:`~music21.stream.Stream` objects and all
19elements contained within them including Notes, etc. Additional objects for
20defining and manipulating elements are included.
21
22The namespace of this file, as all base.py files, is loaded into the package
23that contains this file via __init__.py. Everything in this file is thus
24available after importing `music21`.
25
26>>> import music21
27>>> music21.Music21Object
28<class 'music21.base.Music21Object'>
29
30>>> music21.VERSION_STR
31'7.1.0'
32
33Alternatively, after doing a complete import, these classes are available
34under the module "base":
35
36>>> base.Music21Object
37<class 'music21.base.Music21Object'>
38'''
39import copy
40import sys
41import types
42import unittest
43
44from collections import namedtuple
45from importlib.util import find_spec
46
47# for type annotation only
48import fractions
49from typing import (
50    Any,
51    Dict,
52    FrozenSet,
53    Iterable,
54    List,
55    Optional,
56    Union,
57    Tuple,
58    Type,
59    TypeVar,
60)
61
62from music21 import common
63from music21.common.enums import ElementSearch, OffsetSpecial
64from music21.common.numberTools import opFrac
65from music21.common.types import OffsetQL, OffsetQLIn
66from music21 import environment
67from music21 import editorial
68from music21 import defaults
69from music21.derivation import Derivation
70from music21 import duration
71from music21 import prebase
72from music21 import sites
73from music21 import style  # pylint: disable=unused-import
74from music21.sites import SitesException
75from music21.sorting import SortTuple, ZeroSortTupleLow, ZeroSortTupleHigh
76# needed for temporal manipulations; not music21 objects
77from music21 import tie
78from music21 import exceptions21
79from music21._version import __version__, __version_info__
80from music21.test.testRunner import mainTest
81
82
83# This should actually be bound to Music21Object, but cannot import here.
84_M21T = TypeVar('_M21T', bound=prebase.ProtoM21Object)
85
86# all other music21 modules below...
87
88# -----------------------------------------------------------------------------
89# version string and tuple must be the same
90
91VERSION = __version_info__
92VERSION_STR = __version__
93# -----------------------------------------------------------------------------
94__all__ = [
95    'Music21Exception',
96    'SitesException',
97    'Music21ObjectException',
98    'ElementException',
99
100    'Groups',
101    'Music21Object',
102    'ElementWrapper',
103
104    'VERSION',
105    'VERSION_STR',
106    'mainTest',
107]
108
109# N.B. for PyDev "all" import working, we need to list this
110#       separately in "music21/__init__.py"
111# so make sure to update in both places
112
113# ----all exceptions are in the exceptions21 package.
114
115Music21Exception = exceptions21.Music21Exception
116
117
118# ?? pylint does not think that this was used...
119
120_MOD = 'base'
121environLocal = environment.Environment(_MOD)
122
123_missingImport = []
124for modName in ('matplotlib', 'numpy'):
125    loader = find_spec(modName)
126    if loader is None:  # pragma: no cover
127        _missingImport.append(modName)
128
129del find_spec
130del modName
131
132if _missingImport:  # pragma: no cover
133    if environLocal['warnings'] in (1, '1', True):
134        environLocal.warn(common.getMissingImportStr(_missingImport),
135                          header='music21:')
136
137
138class Music21ObjectException(exceptions21.Music21Exception):
139    pass
140
141
142class ElementException(exceptions21.Music21Exception):
143    pass
144
145
146# -----------------------------------------------------------------------------
147# for contextSites searches...
148ContextTuple = namedtuple('ContextTuple', 'site offset recurseType')
149
150
151# pseudo class for returning splitAtX() type commands.
152class _SplitTuple(tuple):
153    '''
154    >>> st = base._SplitTuple([1, 2])
155    >>> st.spannerList = [3]
156    >>> st
157    (1, 2)
158    >>> st.spannerList
159    [3]
160    >>> a, b = st
161    >>> a
162    1
163    >>> b
164    2
165    >>> st.__class__
166    <class 'music21.base._SplitTuple'>
167    '''
168    def __new__(cls, tupEls):
169        # noinspection PyTypeChecker
170        return super(_SplitTuple, cls).__new__(cls, tuple(tupEls))
171
172    def __init__(self, tupEls):  # pylint: disable=super-init-not-called
173        self.spannerList = []
174
175# -----------------------------------------------------------------------------
176# make subclass of set once that is defined properly
177
178
179class Groups(list):  # no need to inherit from slotted object
180    '''
181    Groups is a list (subclass) of strings used to identify
182    associations that an element might have.
183
184    The Groups object enforces that all elements must be strings, and that
185    the same element cannot be provided more than once.
186
187    NOTE: In the future, spaces will not be allowed in group names.
188
189
190    >>> g = Groups()
191    >>> g.append('hello')
192    >>> g[0]
193    'hello'
194
195    >>> g.append('hello')  # not added as already present
196    >>> len(g)
197    1
198
199    >>> g
200    ['hello']
201
202    >>> g.append(5)
203    Traceback (most recent call last):
204    music21.exceptions21.GroupException: Only strings can be used as group names, not 5
205    '''
206    # could be made into a set instance, but actually
207    # timing: a subclassed list and a set are almost the same speed
208    # and set does not allow calling by number
209
210    # this speeds up creation slightly...
211    __slots__ = ()
212
213    def _validName(self, value: str):
214        if not isinstance(value, str):
215            raise exceptions21.GroupException('Only strings can be used as group names, '
216                                              + f'not {value!r}')
217        # if ' ' in value:
218        #     raise exceptions21.GroupException('Spaces are not allowed as group names')
219
220    def append(self, value: Union[int, str]) -> None:
221        self._validName(value)
222        if not list.__contains__(self, value):
223            list.append(self, value)
224
225    def __setitem__(self, i: int, y: Union[int, str]):
226        self._validName(y)
227        super().__setitem__(i, y)
228
229    def __eq__(self, other: 'Groups'):
230        '''
231        Test Group equality. In normal lists, order matters; here it does not. More like a set.
232
233        >>> a = base.Groups()
234        >>> a.append('red')
235        >>> a.append('green')
236        >>> a
237        ['red', 'green']
238
239        >>> b = base.Groups()
240        >>> b.append('green')
241        >>> a == b
242        False
243
244        >>> b.append('reD')  # case insensitive
245        >>> a == b
246        True
247
248        >>> a == ['red', 'green']  # need both to be groups
249        False
250
251        >>> c = base.Groups()
252        >>> c.append('black')
253        >>> c.append('tuba')
254        >>> a == c
255        False
256        '''
257        if not isinstance(other, Groups):
258            return False
259
260        if len(self) != len(other):
261            return False
262        sls = sorted(self)
263        slo = sorted(other)
264
265        for x in range(len(sls)):
266            if sls[x].lower() != slo[x].lower():
267                return False
268        return True
269
270
271# -----------------------------------------------------------------------------
272
273
274class Music21Object(prebase.ProtoM21Object):
275    '''
276    Base class for all music21 objects.
277
278    All music21 objects have these pieces of information:
279
280    1.  id: identification string unique to the objects container (optional).
281        Defaults to the `id()` of the element.
282    2.  groups: a Groups object: which is a list of strings identifying
283        internal sub-collections (voices, parts, selections) to which this
284        element belongs
285    3.  duration: Duration object representing the length of the object
286    4.  activeSite: a reference to the currently active Stream or None
287    5.  offset: a floating point value, generally in quarter lengths,
288        specifying the position of the object in a site.
289    6.  priority: int representing the position of an object among all
290        objects at the same offset.
291    7.  sites: a :class:`~music21.base.Sites` object that stores all
292        the Streams and Contexts that an
293        object is in.
294    8.  derivation: a :class:`~music21.derivation.Derivation` object, or None, that shows
295        where the object came from.
296    9.  style: a :class:`~music21.style.Style` object, that contains Style information
297        automatically created if it doesn't exist, so check `.hasStyleInformation` first
298        if that is not desired.
299    10. editorial: a :class:`~music21.editorial.Editorial` object
300
301
302
303    Each of these may be passed in as a named keyword to any music21 object.
304
305    Some of these may be intercepted by the subclassing object (e.g., duration
306    within Note)
307    '''
308
309    classSortOrder: Union[int, float] = 20  # default classSortOrder
310    # these values permit fast class comparisons for performance critical cases
311    isStream = False
312
313    _styleClass: Type[style.Style] = style.Style
314
315    # define order to present names in documentation; use strings
316    _DOC_ORDER = []
317
318    # documentation for all attributes (not properties or methods)
319    _DOC_ATTR = {
320        'id': '''A unique identification string; not to be confused with the
321            default `.id()` method. However, if not set, will return
322            the `id()` number''',
323        'groups': '''An instance of a :class:`~music21.base.Group`
324            object which describes
325            arbitrary `Groups` that this object belongs to.''',
326        'isStream': '''Boolean value for quickly identifying
327            :class:`~music21.stream.Stream` objects (False by default).''',
328        'classSortOrder': '''Property which returns an number (int or otherwise)
329            depending on the class of the Music21Object that
330            represents a priority for an object based on its class alone --
331            used as a tie for stream sorting in case two objects have the
332            same offset and priority.  Lower numbers are sorted to the left
333            of higher numbers.  For instance, Clef, KeySignature, TimeSignature
334            all come (in that order) before Note.
335
336            All undefined classes have classSortOrder of 20 -- same as note.Note
337
338            >>> m21o = base.Music21Object()
339            >>> m21o.classSortOrder
340            20
341
342            >>> tc = clef.TrebleClef()
343            >>> tc.classSortOrder
344            0
345
346            >>> ks = key.KeySignature(3)
347            >>> ks.classSortOrder
348            2
349
350
351            New classes can define their own default classSortOrder
352
353            >>> class ExampleClass(base.Music21Object):
354            ...     classSortOrder = 5
355            ...
356            >>> ec1 = ExampleClass()
357            >>> ec1.classSortOrder
358            5
359            ''',
360    }
361
362    def __init__(self, *arguments, **keywords):
363        # do not call super().__init__() since it just wastes time
364        # None is stored as the internal location of an obj w/o any sites
365        self._activeSite: Optional['music21.stream.Stream'] = None
366        # offset when no activeSite is available
367        self._naiveOffset: Union[float, fractions.Fraction] = 0.0
368
369        # offset when activeSite is already garbage collected/dead,
370        # as in short-lived sites
371        # like .getElementsByClass().stream()
372        self._activeSiteStoredOffset: Union[float, fractions.Fraction, None] = None
373
374        # store a derivation object to track derivations from other Streams
375        # pass a reference to this object
376        self._derivation: Optional[Derivation] = None
377
378        self._style: Optional[style.Style] = None
379        self._editorial = None
380
381        # private duration storage; managed by property
382        self._duration: Optional[duration.Duration] = None
383        self._priority = 0  # default is zero
384
385        # store cached values here:
386        self._cache: Dict[str, Any] = {}
387
388
389        if 'id' in keywords:
390            self.id = keywords['id']
391        else:
392            self.id = id(self)
393
394        if 'groups' in keywords and keywords['groups'] is not None:
395            self.groups = keywords['groups']
396        else:
397            self.groups = Groups()
398
399        if 'sites' in keywords:
400            self.sites = keywords['sites']
401        else:
402            self.sites = sites.Sites()
403
404        # a duration object is not created until the .duration property is
405        # accessed with _getDuration(); this is a performance optimization
406        if 'duration' in keywords:
407            self.duration = keywords['duration']
408        if 'activeSite' in keywords:
409            self.activeSite = keywords['activeSite']
410        if 'style' in keywords:
411            self.style = keywords['style']
412        if 'editorial' in keywords:
413            self.editorial = keywords['editorial']
414
415    def mergeAttributes(self, other: 'Music21Object') -> None:
416        '''
417        Merge all elementary, static attributes. Namely,
418        `id` and `groups` attributes from another music21 object.
419        Can be useful for copy-like operations.
420
421        >>> m1 = base.Music21Object()
422        >>> m2 = base.Music21Object()
423        >>> m1.id = 'music21Object1'
424        >>> m1.groups.append('group1')
425        >>> m2.mergeAttributes(m1)
426        >>> m2.id
427        'music21Object1'
428        >>> 'group1' in m2.groups
429        True
430        '''
431        if other.id != id(other):
432            self.id = other.id
433        self.groups = copy.deepcopy(other.groups)
434
435    # PyCharm 2019 does not know that copy.deepcopy can take a memo argument
436    # noinspection PyArgumentList
437    def _deepcopySubclassable(self: _M21T,
438                              memo=None,
439                              ignoreAttributes=None,
440                              removeFromIgnore=None) -> _M21T:
441        '''
442        Subclassable __deepcopy__ helper so that the same attributes
443        do not need to be called
444        for each Music21Object subclass.
445
446        ignoreAttributes is a set of attributes not to copy via
447        the default deepcopy style.
448        More can be passed to it.
449
450        removeFromIgnore can be a set of attributes to remove
451        from ignoreAttributes of a superclass.
452
453        TODO: move to class attributes to cache.
454        '''
455        defaultIgnoreSet = {'_derivation', '_activeSite', 'id',
456                            'sites', '_duration', '_style', '_cache'}
457        if ignoreAttributes is None:
458            ignoreAttributes = defaultIgnoreSet
459        else:
460            ignoreAttributes = ignoreAttributes | defaultIgnoreSet
461
462        if removeFromIgnore is not None:
463            ignoreAttributes = ignoreAttributes - removeFromIgnore
464
465        # call class to get a new, empty instance
466        # TODO: this creates an extra duration object for notes... optimize...
467        new = self.__class__()
468        # environLocal.printDebug(['Music21Object.__deepcopy__', self, id(self)])
469        # for name in dir(self):
470        if '_duration' in ignoreAttributes:
471            # this can be done much faster in most cases...
472            d = self._duration
473            if d is not None:
474                clientStore = self._duration._client
475                self._duration._client = None
476                newValue = copy.deepcopy(self._duration, memo)
477                self._duration._client = clientStore
478                newValue.client = new
479                setattr(new, '_duration', newValue)
480
481        if '_derivation' in ignoreAttributes:
482            # was: keep the old ancestor but need to update the client
483            # 2.1 : NO, add a derivation of __deepcopy__ to the client
484            newDerivation = Derivation(client=new)
485            newDerivation.origin = self
486            newDerivation.method = '__deepcopy__'
487            setattr(new, '_derivation', newDerivation)
488
489        if '_activeSite' in ignoreAttributes:
490            # TODO: Fix this so as not to allow incorrect _activeSite (???)
491            # keep a reference, not a deepcopy
492            # do not use property: .activeSite; set to same weakref obj
493            # TODO: restore jan 2020 (was Jan 2018)
494            #            setattr(new, '_activeSite', None)
495            setattr(new, '_activeSite', self._activeSite)
496
497        if 'id' in ignoreAttributes:
498            value = getattr(self, 'id')
499            if value != id(self) or (
500                common.isNum(value)
501                and value < defaults.minIdNumberToConsiderMemoryLocation
502            ):
503                newValue = value
504                setattr(new, 'id', newValue)
505        if 'sites' in ignoreAttributes:
506            # we make a copy of the sites value even though it is obsolete because
507            # the spanners will need to be preserved and then set to the new value
508            # elsewhere.  The purgeOrphans call later will remove all but
509            # spanners and variants.
510            value = getattr(self, 'sites')
511            # this calls __deepcopy__ in Sites
512            newValue = copy.deepcopy(value, memo)
513            setattr(new, 'sites', newValue)
514        if '_style' in ignoreAttributes:
515            value = getattr(self, '_style', None)
516            if value is not None:
517                newValue = copy.deepcopy(value, memo)
518                setattr(new, '_style', newValue)
519
520        for name in self.__dict__:
521            if name.startswith('__'):
522                continue
523            if name in ignoreAttributes:
524                continue
525
526            attrValue = getattr(self, name)
527            # attributes that do not require special handling
528            try:
529                deeplyCopiedObject = copy.deepcopy(attrValue, memo)
530                setattr(new, name, deeplyCopiedObject)
531            except TypeError as te:  # pragma: no cover
532                if not isinstance(attrValue, Music21Object):
533                    # shallow copy then...
534                    try:
535                        shallowlyCopiedObject = copy.copy(attrValue)
536                        setattr(new, name, shallowlyCopiedObject)
537                        environLocal.printDebug(
538                            '__deepcopy__: Could not deepcopy '
539                            + f'{name} in {self}, not a Music21Object'
540                            + 'so making a shallow copy')
541                    except TypeError:
542                        # just link...
543                        environLocal.printDebug(
544                            '__deepcopy__: Could not copy (deep or shallow) '
545                            + f'{name} in {self}, not a Music21Object so just making a link'
546                        )
547                        setattr(new, name, attrValue)
548                else:  # raise error for our own problem.  # pragma: no cover
549                    raise Music21Exception(
550                        '__deepcopy__: Cannot deepcopy Music21Object '
551                        + f'{name} probably because it requires a default value in instantiation.'
552                    ) from te
553
554        return new
555
556    def __deepcopy__(self: _M21T, memo: Optional[Dict[int, Any]] = None) -> _M21T:
557        '''
558        Helper method to copy.py's deepcopy function.  Call it from there.
559
560        memo=None is the default as specified in copy.py
561
562        Problem: if an attribute is defined with an underscore (_priority) but
563        is also made available through a property (e.g. priority)  using dir(self)
564        results in the copy happening twice. Thus, __dict__.keys() is used.
565
566        >>> from copy import deepcopy
567        >>> n = note.Note('A')
568        >>> n.offset = 1.0
569        >>> n.groups.append('flute')
570        >>> n.groups
571        ['flute']
572
573        >>> idN = n.id
574        >>> idN > 10000  # pointer
575        True
576
577        >>> b = deepcopy(n)
578        >>> b.offset = 2.0
579
580        >>> n is b
581        False
582        >>> b.id != n.id
583        True
584        >>> n.pitch.accidental = '-'
585        >>> b.name
586        'A'
587        >>> n.offset
588        1.0
589        >>> b.offset
590        2.0
591        >>> n.groups[0] = 'bassoon'
592        >>> ('flute' in n.groups, 'flute' in b.groups)
593        (False, True)
594        '''
595        # environLocal.printDebug(['calling Music21Object.__deepcopy__', self])
596        new = self._deepcopySubclassable(memo)
597        # must do this after copying
598        new.purgeOrphans()
599        # environLocal.printDebug([self, 'end deepcopy', 'self._activeSite', self._activeSite])
600        return new
601
602    def __getstate__(self) -> Dict[str, Any]:
603        state = self.__dict__.copy()
604        state['_derivation'] = None
605        state['_activeSite'] = None
606        return state
607
608    def __setstate__(self, state: Dict[str, Any]):
609        # defining self.__dict__ upon initialization currently breaks everything
610        self.__dict__ = state  # pylint: disable=attribute-defined-outside-init
611
612    # --------------------------------------------------------------------------
613
614    @property
615    def hasEditorialInformation(self):
616        '''
617        Returns True if there is a :class:`~music21.editorial.Editorial` object
618        already associated with this object, False otherwise.
619
620        Calling .style on an object will always create a new
621        Style object, so even though a new Style object isn't too expensive
622        to create, this property helps to prevent creating new Styles more than
623        necessary.
624
625        >>> mObj = base.Music21Object()
626        >>> mObj.hasEditorialInformation
627        False
628        >>> mObj.editorial
629        <music21.editorial.Editorial {}>
630        >>> mObj.hasEditorialInformation
631        True
632        '''
633        return not (self._editorial is None)
634
635    @property
636    def editorial(self) -> 'music21.editorial.Editorial':
637        '''
638        a :class:`~music21.editorial.Editorial` object that stores editorial information
639        (comments, footnotes, harmonic information, ficta).
640
641        Created automatically as needed:
642
643        >>> n = note.Note('C4')
644        >>> n.editorial
645        <music21.editorial.Editorial {}>
646        >>> n.editorial.ficta = pitch.Accidental('sharp')
647        >>> n.editorial.ficta
648        <music21.pitch.Accidental sharp>
649        >>> n.editorial
650        <music21.editorial.Editorial {'ficta': <music21.pitch.Accidental sharp>}>
651        '''
652        if self._editorial is None:
653            self._editorial = editorial.Editorial()
654        return self._editorial
655
656    @editorial.setter
657    def editorial(self, ed: 'music21.editorial.Editorial'):
658        self._editorial = ed
659
660    @property
661    def hasStyleInformation(self) -> bool:
662        '''
663        Returns True if there is a :class:`~music21.style.Style` object
664        already associated with this object, False otherwise.
665
666        Calling .style on an object will always create a new
667        Style object, so even though a new Style object isn't too expensive
668        to create, this property helps to prevent creating new Styles more than
669        necessary.
670
671        >>> mObj = base.Music21Object()
672        >>> mObj.hasStyleInformation
673        False
674        >>> mObj.style
675        <music21.style.Style object at 0x10b0a2080>
676        >>> mObj.hasStyleInformation
677        True
678        '''
679        return not (self._style is None)
680
681    @property
682    def style(self) -> 'music21.style.Style':
683        '''
684        Returns (or Creates and then Returns) the Style object
685        associated with this object, or sets a new
686        style object.  Different classes might use
687        different Style objects because they might have different
688        style needs (such as text formatting or bezier positioning)
689
690        Eventually will also query the groups to see if they have
691        any styles associated with them.
692
693        >>> n = note.Note()
694        >>> st = n.style
695        >>> st
696        <music21.style.NoteStyle object at 0x10ba96208>
697        >>> st.absoluteX = 20.0
698        >>> st.absoluteX
699        20.0
700        >>> n.style = style.Style()
701        >>> n.style.absoluteX is None
702        True
703        '''
704        if self._style is None:
705            styleClass = self._styleClass
706            self._style = styleClass()
707        return self._style
708
709    @style.setter
710    def style(self, newStyle: Optional['music21.style.Style']):
711        self._style = newStyle
712
713    # --------------------------
714    # convenience.  used to be in note.Note, but belongs everywhere:
715
716    @property
717    def quarterLength(self) -> OffsetQL:
718        '''
719        Set or Return the Duration as represented in Quarter Length, possibly as a fraction.
720
721        >>> n = note.Note()
722        >>> n.quarterLength = 2.0
723        >>> n.quarterLength
724        2.0
725        >>> n.quarterLength = 1/3
726        >>> n.quarterLength
727        Fraction(1, 3)
728        '''
729        return self.duration.quarterLength
730
731    @quarterLength.setter
732    def quarterLength(self, value: OffsetQLIn):
733        self.duration.quarterLength = value
734
735    @property
736    def derivation(self) -> Derivation:
737        '''
738        Return the :class:`~music21.derivation.Derivation` object for this element.
739
740        Or create one if none exists:
741
742        >>> n = note.Note()
743        >>> n.derivation
744        <Derivation of <music21.note.Note C> from None>
745        >>> import copy
746        >>> n2 = copy.deepcopy(n)
747        >>> n2.pitch.step = 'D'  # for seeing easier...
748        >>> n2.derivation
749        <Derivation of <music21.note.Note D> from <music21.note.Note C> via '__deepcopy__'>
750        >>> n2.derivation.origin is n
751        True
752
753        Note that (for now at least) derivation.origin is NOT a weakref:
754
755        >>> del n
756        >>> n2.derivation
757        <Derivation of <music21.note.Note D> from <music21.note.Note C> via '__deepcopy__'>
758        >>> n2.derivation.origin
759        <music21.note.Note C>
760        '''
761        if self._derivation is None:
762            self._derivation = Derivation(client=self)
763        return self._derivation
764
765    @derivation.setter
766    def derivation(self, newDerivation: Optional[Derivation]) -> None:
767        self._derivation = newDerivation
768
769    def clearCache(self, **keywords):
770        '''
771        A number of music21 attributes (especially with Chords and RomanNumerals, etc.)
772        are expensive to compute and are therefore cached.  Generally speaking
773        objects are responsible for making sure that their own caches are up to date,
774        but a power user might want to do something in an unusual way (such as manipulating
775        private attributes on a Pitch object) and need to be able to clear caches.
776
777        That's what this is here for.  If all goes well, you'll never need to call it
778        unless you're expanding music21's core functionality.
779
780        `**keywords` is not used in Music21Object but is included for subclassing.
781
782        Look at :func:`~music21.common.decorators.cacheMethod` for the other half of this
783        utility.
784
785        New in v.6 -- exposes previously hidden functionality.
786        '''
787        self._cache = {}
788
789    def getOffsetBySite(
790        self,
791        site: 'music21.stream.Stream',
792        *,
793        returnSpecial=False,
794        stringReturns=False,
795    ) -> Union[float, fractions.Fraction, str]:
796        '''
797        If this class has been registered in a container such as a Stream,
798        that container can be provided here, and the offset in that object
799        can be returned.
800
801        >>> n = note.Note('A-4')  # a Music21Object
802        >>> n.offset = 30
803        >>> n.getOffsetBySite(None)
804        30.0
805
806        >>> s1 = stream.Stream()
807        >>> s1.id = 'containingStream'
808        >>> s1.insert(20 / 3, n)
809        >>> n.getOffsetBySite(s1)
810        Fraction(20, 3)
811        >>> float(n.getOffsetBySite(s1))
812        6.6666...
813
814        n.getOffsetBySite(None) should still return 30.0
815
816        >>> n.getOffsetBySite(None)
817        30.0
818
819        If the Stream does not contain the element and the element is not derived from
820        an element that does, then a SitesException is raised:
821
822        >>> s2 = stream.Stream()
823        >>> s2.id = 'notContainingStream'
824        >>> n.getOffsetBySite(s2)
825        Traceback (most recent call last):
826        music21.sites.SitesException: an entry for this object <music21.note.Note A-> is not
827              stored in stream <music21.stream.Stream notContainingStream>
828
829        Consider this use of derivations:
830
831        >>> import copy
832        >>> nCopy = copy.deepcopy(n)
833        >>> nCopy.derivation
834        <Derivation of <music21.note.Note A-> from <music21.note.Note A-> via '__deepcopy__'>
835        >>> nCopy.getOffsetBySite(s1)
836        Fraction(20, 3)
837
838        nCopy can still find the offset of `n` in `s1`!
839        This is the primary difference between element.getOffsetBySite(stream)
840        and stream.elementOffset(element)
841
842        >>> s1.elementOffset(nCopy)
843        Traceback (most recent call last):
844        music21.sites.SitesException: an entry for this object ... is not
845            stored in stream <music21.stream.Stream containingStream>
846
847        If the object is stored at the end of the Stream, then the highest time
848        is usually returned:
849
850        >>> s3 = stream.Stream()
851        >>> n3 = note.Note(type='whole')
852        >>> s3.append(n3)
853        >>> rb = bar.Barline()
854        >>> s3.storeAtEnd(rb)  # s3.rightBarline = rb would do the same...
855        >>> rb.getOffsetBySite(s3)
856        4.0
857
858        However, setting returnSpecial to True will return OffsetSpecial.AT_END
859
860        >>> rb.getOffsetBySite(s3, returnSpecial=True)
861        <OffsetSpecial.AT_END>
862
863        Even with returnSpecial normal offsets are still returned as a float or Fraction:
864
865        >>> n3.getOffsetBySite(s3, returnSpecial=True)
866        0.0
867
868        Changed in v7. -- stringReturns renamed to returnSpecial.  Returns an OffsetSpecial Enum.
869        '''
870        if stringReturns and not returnSpecial:  # pragma: no cover
871            returnSpecial = stringReturns
872            environLocal.warn('stringReturns is deprecated: use returnSpecial instead')
873
874        if site is None:
875            return self._naiveOffset
876
877        try:
878            a = None
879            tryOrigin = self
880            originMemo = set()
881            maxSearch = 100
882            while a is None:
883                try:
884                    a = site.elementOffset(tryOrigin, returnSpecial=returnSpecial)
885                except AttributeError as ae:
886                    raise SitesException(
887                        f'You were using {site!r} as a site, when it is not a Stream...'
888                    ) from ae
889                except Music21Exception as e:  # currently StreamException, but will change
890                    if tryOrigin in site._endElements:
891                        if returnSpecial is True:
892                            return OffsetSpecial.AT_END
893                        else:
894                            return site.highestTime
895
896                    tryOrigin = self.derivation.origin
897                    if id(tryOrigin) in originMemo:
898                        raise e
899                    originMemo.add(id(tryOrigin))
900                    maxSearch -= 1  # prevent infinite recursive searches...
901                    if tryOrigin is None or maxSearch < 0:
902                        raise e
903            return a
904        except SitesException as se:
905            raise SitesException(
906                f'an entry for this object {self!r} is not stored in stream {site!r}'
907            ) from se
908
909    def setOffsetBySite(self,
910                        site: Optional['music21.stream.Stream'],
911                        value: Union[int, float, fractions.Fraction]):
912        '''
913        Change the offset for a site.  These are equivalent:
914
915            n1.setOffsetBySite(stream1, 20)
916
917        and
918
919            stream1.setElementOffset(n1, 20)
920
921        Which you choose to use will depend on whether you are iterating over a list
922        of notes (etc.) or streams.
923
924        >>> import music21
925        >>> aSite = stream.Stream()
926        >>> aSite.id = 'aSite'
927        >>> a = music21.Music21Object()
928        >>> aSite.insert(0, a)
929        >>> aSite.setElementOffset(a, 20)
930        >>> a.setOffsetBySite(aSite, 30)
931        >>> a.getOffsetBySite(aSite)
932        30.0
933
934        And if it isn't in a Stream? Raises an exception and the offset does not change.
935
936        >>> b = note.Note('D')
937        >>> b.setOffsetBySite(aSite, 40)
938        Traceback (most recent call last):
939        music21.exceptions21.StreamException: Cannot set the offset for element
940            <music21.note.Note D>, not in Stream <music21.stream.Stream aSite>.
941
942        >>> b.offset
943        0.0
944
945        Setting offset for `None` changes the "naive offset" of an object:
946
947        >>> b.setOffsetBySite(None, 32)
948        >>> b.offset
949        32.0
950        >>> b.activeSite is None
951        True
952
953        Running `setOffsetBySite` also changes the `activeSite` of the object.
954        '''
955        if site is not None:
956            site.setElementOffset(self, value)
957        else:
958            if isinstance(value, int):
959                value = float(value)
960            self._naiveOffset = value
961
962    def getOffsetInHierarchy(self, site) -> Union[float, fractions.Fraction]:
963        '''
964        For an element which may not be in site, but might be in a Stream in site (or further
965        in streams), find the cumulative offset of the element in that site.
966
967        >>> s = stream.Score(id='mainScore')
968        >>> p = stream.Part()
969        >>> m = stream.Measure()
970        >>> n = note.Note()
971        >>> m.insert(5.0, n)
972        >>> p.insert(10.0, m)
973        >>> s.insert(0.0, p)
974        >>> n.getOffsetInHierarchy(s)
975        15.0
976
977        If no hierarchy beginning with site contains the element
978        and the element is not derived from
979        an element that does, then a SitesException is raised:
980
981        >>> s2 = stream.Score(id='otherScore')
982        >>> n.getOffsetInHierarchy(s2)
983        Traceback (most recent call last):
984        music21.sites.SitesException: Element <music21.note.Note C>
985            is not in hierarchy of <music21.stream.Score otherScore>
986
987        But if the element is derived from an element in
988        a hierarchy then it can get the offset:
989
990        >>> n2 = n.transpose('P5')
991        >>> n2.derivation.origin is n
992        True
993        >>> n2.derivation.method
994        'transpose'
995        >>> n2.getOffsetInHierarchy(s)
996        15.0
997
998        There is no corresponding `.setOffsetInHierarchy()`
999        since it's unclear what that would mean.
1000
1001        See also :meth:`music21.stream.iterator.RecursiveIterator.currentHierarchyOffset`
1002        for a method that is about 10x faster when running through a recursed stream.
1003
1004        *new in v.3*
1005
1006        OMIT_FROM_DOCS
1007
1008        Timing: 113microseconds for a search vs 1 microsecond for getOffsetBySite
1009        vs 0.4 for elementOffset.  Hence the short-circuit for easy looking below...
1010
1011        TODO: If timing permits, replace .flatten() w/ and w/o retainContainers with this routine.
1012
1013        Currently not possible; for instance, if b = bwv66.6
1014
1015        %timeit b = corpus.parse('bwv66.6') -- 24.8ms
1016        %timeit b = corpus.parse('bwv66.6'); b.flatten() -- 42.9ms
1017        %timeit b = corpus.parse('bwv66.6'); b.recurse().stream() -- 83.1ms
1018        '''
1019        try:
1020            return self.getOffsetBySite(site)
1021        except SitesException:
1022            pass
1023
1024        # do not use priorityTarget, just slows things down because will need to search
1025        # all anyhow, since site is not in self.sites.yieldSites()
1026        for cs in self.contextSites():
1027            if cs.site is site:
1028                return cs.offset
1029
1030        raise SitesException(f'Element {self} is not in hierarchy of {site}')
1031
1032    def getSpannerSites(self, spannerClassList=None) -> List['music21.spanner.Spanner']:
1033        '''
1034        Return a list of all :class:`~music21.spanner.Spanner` objects
1035        (or Spanner subclasses) that contain
1036        this element. This method provides a way for
1037        objects to be aware of what Spanners they
1038        reside in. Note that Spanners are not Streams
1039        but specialized Music21Objects that use a
1040        Stream subclass, SpannerStorage, internally to keep track
1041        of the elements that are spanned.
1042
1043        >>> n1 = note.Note('C4')
1044        >>> n2 = note.Note('D4')
1045        >>> sp1 = spanner.Slur(n1, n2)
1046        >>> n1.getSpannerSites() == [sp1]
1047        True
1048
1049        Note that not all Spanners are in the spanner module. They
1050        tend to reside in modules closer to their musical function:
1051
1052        >>> sp2 = dynamics.Crescendo(n2, n1)
1053
1054        The order that Spanners are returned is usually the order they
1055        were created, but on fast computers there can be ties, so use
1056        a set comparison if you expect multiple:
1057
1058        >>> set(n2.getSpannerSites()) == {sp1, sp2}
1059        True
1060
1061        Optionally a class name or list of class names can be
1062        specified and only Spanners of that class will be returned
1063
1064        >>> sp3 = dynamics.Diminuendo(n1, n2)
1065        >>> n2.getSpannerSites('Diminuendo') == [sp3]
1066        True
1067
1068        A larger class name can be used to get all subclasses:
1069
1070        >>> set(n2.getSpannerSites('DynamicWedge')) == {sp2, sp3}
1071        True
1072        >>> set(n2.getSpannerSites(['Slur', 'Diminuendo'])) == {sp1, sp3}
1073        True
1074
1075
1076        >>> set(n2.getSpannerSites(['Slur', 'Diminuendo'])) == {sp3, sp1}
1077        True
1078
1079
1080        Example: see which pairs of notes are in the same slur.
1081
1082        >>> n3 = note.Note('E4')
1083        >>> sp4 = spanner.Slur(n1, n3)
1084
1085        >>> for n in [n1, n2, n3]:
1086        ...    for nOther in [n1, n2, n3]:
1087        ...        if n is nOther:
1088        ...            continue
1089        ...        nSlurs = n.getSpannerSites('Slur')
1090        ...        nOtherSlurs = nOther.getSpannerSites('Slur')
1091        ...        for thisSlur in nSlurs:
1092        ...            if thisSlur in nOtherSlurs:
1093        ...               print(f'{n.name} shares a slur with {nOther.name}')
1094        C shares a slur with D
1095        C shares a slur with E
1096        D shares a slur with C
1097        E shares a slur with C
1098        '''
1099        found = self.sites.getSitesByClass('SpannerStorage')
1100        post = []
1101        if spannerClassList is not None:
1102            if not common.isIterable(spannerClassList):
1103                spannerClassList = [spannerClassList]
1104
1105        for obj in found:
1106            if obj is None:  # pragma: no cover
1107                continue
1108            if spannerClassList is None:
1109                post.append(obj.spannerParent)
1110            else:
1111                for spannerClass in spannerClassList:
1112                    if spannerClass in obj.spannerParent.classes:
1113                        post.append(obj.spannerParent)
1114                        break
1115
1116        return post
1117
1118    def purgeOrphans(self, excludeStorageStreams=True) -> None:
1119        '''
1120        A Music21Object may, due to deep copying or other reasons,
1121        have a site (with an offset) which
1122        no longer contains the Music21Object. These lingering sites
1123        are called orphans. This methods gets rid of them.
1124
1125        The `excludeStorageStreams` are SpannerStorage and VariantStorage.
1126        '''
1127        # environLocal.printDebug(['purging orphans'])
1128        orphans = []
1129        # TODO: how can this be optimized to not use getSites, so as to
1130        # not unwrap weakrefs?
1131        for s in self.sites.yieldSites(excludeNone=True):
1132            # of the site does not actually have this Music21Object in
1133            # its elements list, it is an orphan and should be removed
1134            # note: this permits non-site context Streams to continue
1135            if s.isStream and not s.hasElement(self):
1136                if excludeStorageStreams:
1137                    # only get those that are not Storage Streams
1138                    if ('SpannerStorage' not in s.classes
1139                            and 'VariantStorage' not in s.classes):
1140                        # environLocal.printDebug(['removing orphan:', s])
1141                        orphans.append(id(s))
1142                else:  # get all
1143                    orphans.append(id(s))
1144        for i in orphans:
1145            self.sites.removeById(i)
1146            p = self._getActiveSite()  # this can be simplified.
1147            if p is not None and id(p) == i:
1148                # noinspection PyArgumentList
1149                self._setActiveSite(None)
1150
1151    def purgeLocations(self, rescanIsDead=False) -> None:
1152        '''
1153        Remove references to all locations in objects that no longer exist.
1154        '''
1155        # NOTE: this method is overridden on Spanner
1156        # and Variant, so not an easy fix to remove...
1157        self.sites.purgeLocations(rescanIsDead=rescanIsDead)
1158
1159    # --------------------------------------------------------------------------------
1160    # contexts...
1161
1162    def getContextByClass(
1163        self,
1164        className,
1165        *,
1166        getElementMethod=ElementSearch.AT_OR_BEFORE,
1167        sortByCreationTime=False,
1168        followDerivation=True,
1169        priorityTargetOnly=False,
1170    ) -> Optional['Music21Object']:
1171        # noinspection PyShadowingNames
1172        '''
1173        A very powerful method in music21 of fundamental importance: Returns
1174        the element matching the className that is closest to this element in
1175        its current hierarchy (or the hierarchy of the derivation origin unless
1176        `followDerivation` is False.  For instance, take this stream of changing time
1177        signatures:
1178
1179        >>> p = converter.parse('tinynotation: 3/4 C4 D E 2/4 F G A B 1/4 c')
1180        >>> p
1181        <music21.stream.Part 0x104ce64e0>
1182
1183        >>> p.show('t')
1184        {0.0} <music21.stream.Measure 1 offset=0.0>
1185            {0.0} <music21.clef.BassClef>
1186            {0.0} <music21.meter.TimeSignature 3/4>
1187            {0.0} <music21.note.Note C>
1188            {1.0} <music21.note.Note D>
1189            {2.0} <music21.note.Note E>
1190        {3.0} <music21.stream.Measure 2 offset=3.0>
1191            {0.0} <music21.meter.TimeSignature 2/4>
1192            {0.0} <music21.note.Note F>
1193            {1.0} <music21.note.Note G>
1194        {5.0} <music21.stream.Measure 3 offset=5.0>
1195            {0.0} <music21.note.Note A>
1196            {1.0} <music21.note.Note B>
1197        {7.0} <music21.stream.Measure 4 offset=7.0>
1198            {0.0} <music21.meter.TimeSignature 1/4>
1199            {0.0} <music21.note.Note C>
1200            {1.0} <music21.bar.Barline type=final>
1201
1202        Let's get the last two notes of the piece, the B and high c:
1203
1204        >>> m4 = p.measure(4)
1205        >>> c = m4.notes.first()
1206        >>> c
1207        <music21.note.Note C>
1208
1209        >>> m3 = p.measure(3)
1210        >>> b = m3.notes.last()
1211        >>> b
1212        <music21.note.Note B>
1213
1214        Now when we run `getContextByClass('TimeSignature')` on c, we get a
1215        time signature of 1/4.
1216
1217        >>> c.getContextByClass('TimeSignature')
1218        <music21.meter.TimeSignature 1/4>
1219
1220        Doing what we just did wouldn't be hard to do with other methods,
1221        though `getContextByClass` makes it easier.  But the time signature
1222        context for b would be much harder to get without this method, since in
1223        order to do it, it searches backwards within the measure, finds that
1224        there's nothing there.  It goes to the previous measure and searches
1225        that one backwards until it gets the proper TimeSignature of 2/4:
1226
1227        >>> b.getContextByClass('TimeSignature')
1228        <music21.meter.TimeSignature 2/4>
1229
1230        The method is smart enough to stop when it gets to the beginning of the
1231        part.  This is all you need to know for most uses.  The rest of the
1232        docs are for advanced uses:
1233
1234        The method searches both Sites as well as associated objects to find a
1235        matching class. Returns `None` if no match is found.
1236
1237        A reference to the caller is required to find the offset of the object
1238        of the caller.
1239
1240        The caller may be a Sites reference from a lower-level object.  If so,
1241        we can access the location of that lower-level object. However, if we
1242        need a flat representation, the caller needs to be the source Stream,
1243        not its Sites reference.
1244
1245        The `getElementMethod` is an enum value (new in v.7) from
1246        :class:`~music21.common.enums.ElementSearch` that selects which
1247        Stream method is used to get elements for searching. (The historical form
1248        of supplying one of the following values as a string is also supported.)
1249
1250        >>> from music21.common.enums import ElementSearch
1251        >>> [x for x in ElementSearch]
1252        [<ElementSearch.BEFORE>,
1253         <ElementSearch.AFTER>,
1254         <ElementSearch.AT_OR_BEFORE>,
1255         <ElementSearch.AT_OR_AFTER>,
1256         <ElementSearch.BEFORE_OFFSET>,
1257         <ElementSearch.AFTER_OFFSET>,
1258         <ElementSearch.AT_OR_BEFORE_OFFSET>,
1259         <ElementSearch.AT_OR_AFTER_OFFSET>,
1260         <ElementSearch.BEFORE_NOT_SELF>,
1261         <ElementSearch.AFTER_NOT_SELF>,
1262         <ElementSearch.ALL>]
1263
1264        The "after" do forward contexts -- looking ahead.
1265
1266        Demonstrations of these keywords:
1267
1268        Because `b` is a `Note`, `.getContextByClass('Note')` will only find itself:
1269
1270        >>> b.getContextByClass('Note') is b
1271        True
1272
1273        To get the previous `Note`, use `getElementMethod=ElementSearch.BEFORE`:
1274
1275        >>> a = b.getContextByClass('Note', getElementMethod=ElementSearch.BEFORE)
1276        >>> a
1277        <music21.note.Note A>
1278
1279        This is similar to `.previous('Note')`, though that method is a bit more
1280        sophisticated:
1281
1282        >>> b.previous('Note')
1283        <music21.note.Note A>
1284
1285        To get the following `Note` use `getElementMethod=ElementSearch.AFTER`:
1286
1287        >>> c = b.getContextByClass('Note', getElementMethod=ElementSearch.AFTER)
1288        >>> c
1289        <music21.note.Note C>
1290
1291        This is similar to `.next('Note')`, though, again, that method is a bit more
1292        sophisticated:
1293
1294        >>> b.next('Note')
1295        <music21.note.Note C>
1296
1297        A Stream might contain several elements at the same offset, leading to
1298        potentially surprising results where searching by `ElementSearch.AT_OR_BEFORE`
1299        does not find an element that is technically the NEXT node but still at 0.0:
1300
1301        >>> s = stream.Stream()
1302        >>> s.insert(0, clef.BassClef())
1303        >>> s.next()
1304        <music21.clef.BassClef>
1305        >>> s.getContextByClass(clef.Clef) is None
1306        True
1307        >>> s.getContextByClass(clef.Clef, getElementMethod=ElementSearch.AT_OR_AFTER)
1308        <music21.clef.BassClef>
1309
1310        This can be remedied by explicitly searching by offsets:
1311
1312        >>> s.getContextByClass(clef.Clef, getElementMethod=ElementSearch.AT_OR_BEFORE_OFFSET)
1313        <music21.clef.BassClef>
1314
1315        Or by not limiting the search by temporal position at all:
1316
1317        >>> s.getContextByClass(clef.Clef, getElementMethod=ElementSearch.ALL)
1318        <music21.clef.BassClef>
1319
1320        Notice that if searching for a `Stream` context, the element is not
1321        guaranteed to be in that Stream.  This is obviously true in this case:
1322
1323        >>> p2 = stream.Part()
1324        >>> m = stream.Measure(number=1)
1325        >>> p2.insert(0, m)
1326        >>> n = note.Note('D')
1327        >>> m.insert(2.0, n)
1328        >>> try:
1329        ...     n.getContextByClass('Part').elementOffset(n)
1330        ... except Music21Exception:
1331        ...     print('not there')
1332        not there
1333
1334        But it is less clear with something like this:
1335
1336        >>> import copy
1337        >>> n2 = copy.deepcopy(n)
1338        >>> try:
1339        ...     n2.getContextByClass('Measure').elementOffset(n2)
1340        ... except Music21Exception:
1341        ...     print('not there')
1342        not there
1343
1344        A measure context is being found, but only through the derivation chain.
1345
1346        >>> n2.getContextByClass('Measure')
1347        <music21.stream.Measure 1 offset=0.0>
1348
1349        To prevent this error, use the `followDerivation=False` setting
1350
1351        >>> print(n2.getContextByClass('Measure', followDerivation=False))
1352        None
1353
1354        Or if you want the offset of the element following the derivation chain,
1355        call `getOffsetBySite()` on the object:
1356
1357        >>> n2.getOffsetBySite(n2.getContextByClass('Measure'))
1358        2.0
1359
1360        * changed in v.5.7 -- added followDerivation=False and made
1361            everything but the class keyword only
1362        * added in v.6 -- added priorityTargetOnly -- see contextSites for description.
1363        * added in v.7 -- added getElementMethod `all` and `ElementSearch` enum.
1364
1365        Raises `ValueError` if `getElementMethod` is not a value in `ElementSearch`.
1366
1367        >>> n2.getContextByClass('TextExpression', getElementMethod='invalid')
1368        Traceback (most recent call last):
1369        ValueError: Invalid getElementMethod: invalid
1370
1371        OMIT_FROM_DOCS
1372
1373        Testing that this works:
1374
1375        >>> import gc
1376        >>> _ = gc.collect()
1377        >>> for site, positionStart, searchType in b.contextSites(
1378        ...                                 returnSortTuples=True,
1379        ...                                 sortByCreationTime=False,
1380        ...                                 followDerivation=True
1381        ...                                 ):
1382        ...     print(site, positionStart, searchType)
1383        <music21.stream.Measure 3 offset=5.0> SortTuple(atEnd=0, offset=1.0, ...) elementsFirst
1384        <music21.stream.Part 0x1118cadd8> SortTuple(atEnd=0, offset=6.0, ...) flatten
1385        '''
1386        OFFSET_METHODS = [
1387            ElementSearch.BEFORE_OFFSET,
1388            ElementSearch.AFTER_OFFSET,
1389            ElementSearch.AT_OR_BEFORE_OFFSET,
1390            ElementSearch.AT_OR_AFTER_OFFSET,
1391        ]
1392        BEFORE_METHODS = [
1393            ElementSearch.BEFORE,
1394            ElementSearch.BEFORE_OFFSET,
1395            ElementSearch.AT_OR_BEFORE,
1396            ElementSearch.AT_OR_BEFORE_OFFSET,
1397            ElementSearch.BEFORE_NOT_SELF,
1398        ]
1399        AFTER_METHODS = [
1400            ElementSearch.AFTER,
1401            ElementSearch.AFTER_OFFSET,
1402            ElementSearch.AT_OR_AFTER,
1403            ElementSearch.AT_OR_AFTER,
1404            ElementSearch.AT_OR_AFTER_OFFSET,
1405            ElementSearch.AFTER_NOT_SELF,
1406        ]
1407        AT_METHODS = [
1408            ElementSearch.AT_OR_BEFORE,
1409            ElementSearch.AT_OR_AFTER,
1410            ElementSearch.AT_OR_BEFORE_OFFSET,
1411            ElementSearch.AT_OR_AFTER_OFFSET,
1412        ]
1413        NOT_SELF_METHODS = [
1414            ElementSearch.BEFORE_NOT_SELF,
1415            ElementSearch.AFTER_NOT_SELF,
1416        ]
1417        # ALL is just a no-op
1418        def payloadExtractor(checkSite, flatten, innerPositionStart):
1419            '''
1420            change the site (stream) to a Tree (using caches if possible),
1421            then find the node before (or after) the positionStart and
1422            return the element there or None.
1423
1424            flatten can be True, 'semiFlat', or False.
1425            '''
1426            siteTree = checkSite.asTree(flatten=flatten, classList=className)
1427            if getElementMethod in OFFSET_METHODS:
1428                # these methods match only by offset.  Used in .getBeat among other places
1429                if getElementMethod in (ElementSearch.BEFORE_OFFSET,
1430                                        ElementSearch.AT_OR_AFTER_OFFSET):
1431                    innerPositionStart = ZeroSortTupleLow.modify(offset=innerPositionStart.offset)
1432                else:
1433                    innerPositionStart = ZeroSortTupleHigh.modify(offset=innerPositionStart.offset)
1434
1435            if getElementMethod in BEFORE_METHODS:
1436                contextNode = siteTree.getNodeBefore(innerPositionStart)
1437            else:
1438                contextNode = siteTree.getNodeAfter(innerPositionStart)
1439
1440            if contextNode is not None:
1441                payload = contextNode.payload
1442                return payload
1443            else:
1444                return None
1445
1446        def wellFormed(checkContextEl, checkSite) -> bool:
1447            '''
1448            Long explanation for a short method.
1449
1450            It is possible for a contextEl to be returned that contradicts the
1451            'Before' or 'After' criterion due to the following:
1452
1453            (I'll take the example of Before; After is much harder
1454            to construct, but possible).
1455
1456            Assume that s is a Score, and tb2 = s.flatten()[1] and tb1 is the previous element
1457            (would be s.flatten()[0]) -- both are at offset 0 in s and are of the same class
1458            (so same sort order and priority) and are thus ordered entirely by insert
1459            order.
1460
1461            in s we have the following.
1462
1463            s.sortTuple   = 0.0 <0.-20.0>  # not inserted
1464            tb1.sortTuple = 0.0 <0.-31.1>
1465            tb2.sortTuple = 0.0 <0.-31.2>
1466
1467            in s.flatten() we have:
1468
1469            s.flatten().sortTuple = 0.0 <0.-20.0>  # not inserted
1470            tb1.sortTuple         = 0.0 <0.-31.3>
1471            tb2.sortTuple         = 0.0 <0.-31.4>
1472
1473            Now tb2 is declared through s.flatten()[1], so its activeSite
1474            is s.flatten().  Calling .previous() finds tb1 in s.flatten().  This is normal.
1475
1476            tb1 calls .previous().  Search of first site finds nothing before tb1,
1477            so .getContextByClass() is ready to return None.  But other sites need to be checked
1478
1479            Search returns to s.  .getContextByClass() asks is there anything before tb1's
1480            sort tuple of 0.0 <0.-31.3> (.3 is the insertIndex) in s?  Yes, it's tb2 at
1481            0.0 <0.-31.2> (because s was created before s.flatten(), the insert indices of objects
1482            in s are lower than the insert indices of objects in s.flatten() (perhaps insert indices
1483            should be eventually made global within the context of a stream, but not global
1484            overall? but that wasn't the solution here).  So we go back to tb2 in s. Then in
1485            theory we should go to tb1 in s, then s, then None.  This would have certain
1486            elements appear twice in a .previous() search, which is not optimal, but wouldn't be
1487            such a huge bug to make this method necessary.
1488
1489            That'd be the only bug that would occur if we did: sf = s.flatten(), tb2 = sf[1]. But
1490            consider the exact phrasing above:  tb2 = s.flatten()[1].
1491            s.flatten() is created for an instant,
1492            it is assigned to tb2's ._activeSite via weakRef, and then tb2's sortTuple is set
1493            via this temporary stream.
1494
1495            Suppose tb2 is from that temp s.flatten()[1].  Then tb1 = tb2.previous() which is found
1496            in s.flatten().  Suppose then that for some reason s._cache['flat'] gets cleaned up
1497            (It was a bug that s._cache was being cleaned by the positioning of notes during
1498            s_flat's setOffset,
1499            but _cache cleanups are allowed to happen at any time,
1500            so it's not a bug that it's being cleaned; assuming that it wouldn't be cleaned
1501            would be the bug) and garbage collection runs.
1502
1503            Now we get tb1.previous() would get tb2 in s. Okay, it's redundant but not a huge deal,
1504            and tb2.previous() gets tb1.  tb1's ._activeSite is still a weakref to s.flatten().
1505            When tb1's getContextByClass() is called, it needs its .sortTuple().  This looks
1506            first at .activeSite.  That is None, so it gets it from .offset which is the .offset
1507            of the last .activeSite (even if it is dead.  A lot of code depends on .offset
1508            still being available if .activeSite dies, so changing that is not an option for now).
1509            So its sortTuple is 0.0 <0.-31.3>. which has a .previous() of tb2 in s, which can
1510            call previous can get tb1, etc. So with really bad timing of cache cleanups and
1511            garbage collecting, it's possible to get an infinite loop.
1512
1513            There may be ways to set activeSite on .getContextByClass() call such that this routine
1514            is not necessary, but I could not find one that was not disruptive for normal
1515            usages.
1516
1517            There are some possible issues that one could raise about "wellFormed".
1518            Suppose for instance, that in one stream (b) we have [tb1, tb2] and
1519            then in another stream context (a), created earlier,
1520            we have [tb0, tb1, tb2].  tb1 is set up with (b) as an activeSite. Finding nothing
1521            previous, it goes to (a) and finds tb2; it then discovers that in (a), tb2 is after
1522            tb1 so it returns None for this context.  One might say, "wait a second, why
1523            isn't tb0 returned? It's going to be skipped." To this, I would answer, the original
1524            context in which .previous() or .getContextByClass() was called was (b). There is
1525            no absolute obligation to find what was previous in a different site context. It is
1526            absolutely fair game to say, "there's nothing prior to tb1".  Then why even
1527            search other streams/sites? Because it's quite common to create a new site
1528            for say a single measure or .getElementsByOffset(), etc., so that when leaving
1529            this extracted section, one wants to see how that fits into a larger stream hierarchy.
1530            '''
1531            try:
1532                selfSortTuple = self.sortTuple(checkSite, raiseExceptionOnMiss=True)
1533                contextSortTuple = checkContextEl.sortTuple(checkSite, raiseExceptionOnMiss=True)
1534            except SitesException:
1535                # might be raised by selfSortTuple; should not be by contextSortTuple.
1536                # It just means that selfSortTuple isn't in the same stream
1537                # as contextSortTuple, such as
1538                # when crossing measure borders.  Thus it's well-formed.
1539                return True
1540
1541            if getElementMethod in BEFORE_METHODS and selfSortTuple < contextSortTuple:
1542                # print(getElementMethod, selfSortTuple.shortRepr(),
1543                #       contextSortTuple.shortRepr(), self, contextEl)
1544                return False
1545            elif getElementMethod in AFTER_METHODS and selfSortTuple > contextSortTuple:
1546                # print(getElementMethod, selfSortTuple.shortRepr(),
1547                #       contextSortTuple.shortRepr(), self, contextEl)
1548                return False
1549            else:
1550                return True
1551
1552        if getElementMethod not in ElementSearch:
1553            raise ValueError(f'Invalid getElementMethod: {getElementMethod}')
1554
1555        if className and not common.isListLike(className):
1556            className = (className,)
1557
1558        if getElementMethod in AT_METHODS and not self.classSet.isdisjoint(className):
1559            return self
1560
1561        for site, positionStart, searchType in self.contextSites(
1562            returnSortTuples=True,
1563            sortByCreationTime=sortByCreationTime,
1564            followDerivation=followDerivation,
1565            priorityTargetOnly=priorityTargetOnly,
1566        ):
1567            if searchType in ('elementsOnly', 'elementsFirst'):
1568                contextEl = payloadExtractor(site,
1569                                             flatten=False,
1570                                             innerPositionStart=positionStart)
1571
1572                if contextEl is not None and wellFormed(contextEl, site):
1573                    try:
1574                        site.coreSelfActiveSite(contextEl)
1575                    except SitesException:
1576                        pass
1577                    return contextEl
1578                # otherwise, continue to check for flattening...
1579
1580            if searchType != 'elementsOnly':  # flatten or elementsFirst
1581                if (getElementMethod in AFTER_METHODS
1582                        and (not className
1583                             or not site.classSet.isdisjoint(className))):
1584                    if getElementMethod in NOT_SELF_METHODS and self is site:
1585                        pass
1586                    elif getElementMethod not in NOT_SELF_METHODS:  # for 'After' we can't do the
1587                        # containing site because that comes before.
1588                        return site  # if the site itself is the context, return it...
1589
1590                contextEl = payloadExtractor(site,
1591                                             flatten='semiFlat',
1592                                             innerPositionStart=positionStart)
1593                if contextEl is not None and wellFormed(contextEl, site):
1594                    try:
1595                        site.coreSelfActiveSite(contextEl)
1596                    except SitesException:
1597                        pass
1598                    return contextEl
1599
1600                if (getElementMethod in BEFORE_METHODS
1601                        and (not className
1602                             or not site.classSet.isdisjoint(className))):
1603                    if getElementMethod in NOT_SELF_METHODS and self is site:
1604                        pass
1605                    else:
1606                        return site  # if the site itself is the context, return it...
1607
1608                # otherwise, continue to check in next contextSite.
1609
1610        # nothing found...
1611        return None
1612
1613    def contextSites(
1614        self,
1615        *,
1616        callerFirst=None,
1617        memo=None,
1618        offsetAppend=0.0,
1619        sortByCreationTime: Union[str, bool] = False,
1620        priorityTarget=None,
1621        returnSortTuples=False,
1622        followDerivation=True,
1623        priorityTargetOnly=False,
1624    ):
1625        '''
1626        A generator that returns a list of namedtuples of sites to search for a context...
1627
1628        Each tuple contains three elements:
1629
1630        .site --  Stream object,
1631        .offset -- the offset or position (sortTuple) of this element in that Stream
1632        .recurseType -- the method of searching that should be applied to search for a context.
1633
1634        The recurseType methods are:
1635
1636            * 'flatten' -- flatten the stream and then look from this offset backwards.
1637
1638            * 'elementsOnly' -- only search the stream's personal
1639               elements from this offset backwards
1640
1641            * 'elementsFirst' -- search this stream backwards,
1642               and then flatten and search backwards
1643
1644        >>> c = corpus.parse('bwv66.6')
1645        >>> c.id = 'bach'
1646        >>> n = c[2][4][2]
1647        >>> n
1648        <music21.note.Note G#>
1649
1650        Returning sortTuples are important for distinguishing the order of multiple sites
1651        at the same offset.
1652
1653        >>> for csTuple in n.contextSites(returnSortTuples=True):
1654        ...      yClearer = (csTuple.site, csTuple.offset.shortRepr(), csTuple.recurseType)
1655        ...      print(yClearer)
1656        (<music21.stream.Measure 3 offset=9.0>, '0.5 <0.20...>', 'elementsFirst')
1657        (<music21.stream.Part Alto>, '9.5 <0.20...>', 'flatten')
1658        (<music21.stream.Score bach>, '9.5 <0.20...>', 'elementsOnly')
1659
1660        Streams have themselves as the first element in their context sites, at position
1661        zero and classSortOrder negative infinity.
1662
1663        This example shows the context sites for Measure 3 of the
1664        Alto part. We will get the measure object using direct access to
1665        indices to ensure that no other temporary
1666        streams are created; normally, we would do `c.parts['Alto'].measure(3)`.
1667
1668        >>> m = c[2][4]
1669        >>> m
1670        <music21.stream.Measure 3 offset=9.0>
1671        >>> for csTuple in m.contextSites(returnSortTuples=True):
1672        ...      yClearer = (csTuple.site, csTuple.offset.shortRepr(), csTuple.recurseType)
1673        ...      print(yClearer)
1674        (<music21.stream.Measure 3 offset=9.0>, '0.0 <-inf.-20...>', 'elementsFirst')
1675        (<music21.stream.Part Alto>, '9.0 <0.-20...>', 'flatten')
1676        (<music21.stream.Score bach>, '9.0 <0.-20...>', 'elementsOnly')
1677
1678        Here we make a copy of the earlier measure and we see that its contextSites
1679        follow the derivationChain from the original measure and still find the Part
1680        and Score of the original Measure 3 even though mCopy is not in any of these
1681        objects.
1682
1683        >>> import copy
1684        >>> mCopy = copy.deepcopy(m)
1685        >>> mCopy.number = 3333
1686        >>> for csTuple in mCopy.contextSites():
1687        ...      print(csTuple, mCopy in csTuple.site)
1688        ContextTuple(site=<music21.stream.Measure 3333 offset=0.0>,
1689                     offset=0.0,
1690                     recurseType='elementsFirst') False
1691        ContextTuple(site=<music21.stream.Part Alto>,
1692                     offset=9.0,
1693                     recurseType='flatten') False
1694        ContextTuple(site=<music21.stream.Score bach>,
1695                     offset=9.0,
1696                     recurseType='elementsOnly') False
1697
1698        If followDerivation were False, then the Part and Score would not be found.
1699
1700        >>> for csTuple in mCopy.contextSites(followDerivation=False):
1701        ...     print(csTuple)
1702        ContextTuple(site=<music21.stream.Measure 3333 offset=0.0>,
1703                     offset=0.0,
1704                     recurseType='elementsFirst')
1705
1706
1707        >>> partIterator = c.parts
1708        >>> m3 = partIterator[1].measure(3)
1709        >>> for csTuple in m3.contextSites():
1710        ...      print(csTuple)
1711        ContextTuple(site=<music21.stream.Measure 3 offset=9.0>,
1712                     offset=0.0,
1713                     recurseType='elementsFirst')
1714        ContextTuple(site=<music21.stream.Part Alto>,
1715                     offset=9.0,
1716                     recurseType='flatten')
1717        ContextTuple(site=<music21.stream.Score bach>,
1718                     offset=9.0,
1719                     recurseType='elementsOnly')
1720
1721        Sorting order:
1722
1723        >>> p1 = stream.Part()
1724        >>> p1.id = 'p1'
1725        >>> m1 = stream.Measure()
1726        >>> m1.number = 1
1727        >>> n = note.Note()
1728        >>> m1.append(n)
1729        >>> p1.append(m1)
1730        >>> for csTuple in n.contextSites():
1731        ...     print(csTuple.site)
1732        <music21.stream.Measure 1 offset=0.0>
1733        <music21.stream.Part p1>
1734
1735        >>> p2 = stream.Part()
1736        >>> p2.id = 'p2'
1737        >>> m2 = stream.Measure()
1738        >>> m2.number = 2
1739        >>> m2.append(n)
1740        >>> p2.append(m2)
1741
1742        The keys could have appeared in any order, but by default
1743        we set set priorityTarget to activeSite.  So this is the same as omitting.
1744
1745        >>> for y in n.contextSites(priorityTarget=n.activeSite):
1746        ...     print(y[0])
1747        <music21.stream.Measure 2 offset=0.0>
1748        <music21.stream.Part p2>
1749        <music21.stream.Measure 1 offset=0.0>
1750        <music21.stream.Part p1>
1751
1752        We can sort sites by creationTime...
1753
1754        >>> for csTuple in n.contextSites(sortByCreationTime=True):
1755        ...     print(csTuple.site)
1756        <music21.stream.Measure 2 offset=0.0>
1757        <music21.stream.Part p2>
1758        <music21.stream.Measure 1 offset=0.0>
1759        <music21.stream.Part p1>
1760
1761        oldest first...
1762
1763        >>> for csTuple in n.contextSites(sortByCreationTime='reverse'):
1764        ...     print(csTuple.site)
1765        <music21.stream.Measure 1 offset=0.0>
1766        <music21.stream.Part p1>
1767        <music21.stream.Measure 2 offset=0.0>
1768        <music21.stream.Part p2>
1769
1770        Note that by default we search all sites, but you might want to only search
1771        one, for instance:
1772
1773        >>> c = note.Note('C')
1774        >>> m1 = stream.Measure()
1775        >>> m1.append(c)
1776
1777        >>> d = note.Note('D')
1778        >>> m2 = stream.Measure()
1779        >>> m2.append([c, d])
1780
1781        >>> c.activeSite = m1
1782        >>> c.next('Note')  # uses contextSites
1783        <music21.note.Note D>
1784
1785        There is a particular site in which there is a Note after c,
1786        but we want to know if there is one in m1 or its hierarchy, so
1787        we can pass in activeSiteOnly to `.next()` which sets
1788        `priorityTargetOnly=True` for contextSites
1789
1790        >>> print(c.next('Note', activeSiteOnly=True))
1791        None
1792
1793
1794        *  removed in v3: priorityTarget cannot be set, in order
1795            to use `.sites.yieldSites()`
1796        *  changed in v5.5: all arguments are keyword only.
1797        *  changed in v6: added `priorityTargetOnly=False` to only search in the
1798            context of the priorityTarget.
1799        '''
1800        from music21 import stream
1801
1802        if memo is None:
1803            memo = []
1804
1805        if callerFirst is None:
1806            callerFirst = self
1807            if self.isStream and self not in memo:
1808                recursionType = self.recursionType
1809                environLocal.printDebug(
1810                    f'Caller first is {callerFirst} with offsetAppend {offsetAppend}')
1811                if returnSortTuples:
1812                    selfSortTuple = self.sortTuple().modify(
1813                        offset=0.0,
1814                        priority=float('-inf')
1815                    )
1816                    yield ContextTuple(self, selfSortTuple, recursionType)
1817                else:
1818                    yield ContextTuple(self, 0.0, recursionType)
1819                memo.append(self)
1820
1821        if priorityTarget is None and sortByCreationTime is False:
1822            priorityTarget = self.activeSite
1823        else:
1824            environLocal.printDebug(f'sortByCreationTime {sortByCreationTime}')
1825
1826        topLevel = self
1827        for siteObj in self.sites.yieldSites(sortByCreationTime=sortByCreationTime,
1828                                             priorityTarget=priorityTarget,
1829                                             excludeNone=True):
1830            if siteObj in memo:
1831                continue
1832            if isinstance(siteObj, stream.SpannerStorage):
1833                continue
1834
1835            try:
1836                st = self.sortTuple(siteObj)
1837                if followDerivation:
1838                    # do not change this to ElementOffset because getOffsetBySite can
1839                    # follow derivation chains.
1840                    offsetInStream = self.getOffsetBySite(siteObj)
1841                else:
1842                    offsetInStream = siteObj.elementOffset(self)
1843
1844                newOffset = opFrac(offsetInStream + offsetAppend)
1845
1846                positionInStream = st.modify(offset=newOffset)
1847            except SitesException:
1848                continue  # not a valid site any more.  Could be caught in derivationChain
1849
1850            recursionType = siteObj.recursionType
1851            if returnSortTuples:
1852                yield ContextTuple(siteObj, positionInStream, recursionType)
1853            else:
1854                yield ContextTuple(siteObj, positionInStream.offset, recursionType)
1855
1856            memo.append(siteObj)
1857            environLocal.printDebug(
1858                f'looking in contextSites for {siteObj}'
1859                + f' with position {positionInStream.shortRepr()}')
1860            for topLevel, inStreamPos, recurType in siteObj.contextSites(
1861                callerFirst=callerFirst,
1862                memo=memo,
1863                offsetAppend=positionInStream.offset,
1864                returnSortTuples=True,  # ALWAYS
1865                sortByCreationTime=sortByCreationTime
1866            ):
1867                # get activeSite unless sortByCreationTime
1868                inStreamOffset = inStreamPos.offset
1869                # now take that offset and use it to modify the positionInStream
1870                # to get where Exactly the object would be if it WERE in this stream
1871                hypotheticalPosition = positionInStream.modify(offset=inStreamOffset)
1872
1873                if topLevel not in memo:
1874                    # environLocal.printDebug('Yielding {}, {}, {} from contextSites'.format(
1875                    #                                                topLevel,
1876                    #                                                inStreamPos.shortRepr(),
1877                    #                                                recurType))
1878                    if returnSortTuples:
1879                        yield ContextTuple(topLevel, hypotheticalPosition, recurType)
1880                    else:
1881                        yield ContextTuple(topLevel, inStreamOffset, recurType)
1882                    memo.append(topLevel)
1883            if priorityTargetOnly:
1884                break
1885
1886        if followDerivation:
1887            for derivedObject in topLevel.derivation.chain():
1888                environLocal.printDebug(
1889                    'looking now in derivedObject, '
1890                    + f'{derivedObject} with offsetAppend {offsetAppend}')
1891                for derivedCsTuple in derivedObject.contextSites(
1892                        callerFirst=None,
1893                        memo=memo,
1894                        offsetAppend=0.0,
1895                        returnSortTuples=True,
1896                        sortByCreationTime=sortByCreationTime):
1897                    # get activeSite unless sortByCreationTime
1898                    if derivedCsTuple.site in memo:
1899                        continue
1900
1901                    environLocal.printDebug(
1902                        f'Yielding {derivedCsTuple} from derivedObject contextSites'
1903                    )
1904                    offsetAdjustedCsTuple = ContextTuple(
1905                        derivedCsTuple.site,
1906                        derivedCsTuple.offset.modify(offset=derivedCsTuple[1].offset
1907                                                            + offsetAppend),
1908                        derivedCsTuple.recurseType)
1909                    if returnSortTuples:
1910                        yield offsetAdjustedCsTuple
1911                    else:
1912                        yield ContextTuple(offsetAdjustedCsTuple.site,
1913                                           offsetAdjustedCsTuple.offset.offset,
1914                                           offsetAdjustedCsTuple.recurseType)
1915                    memo.append(derivedCsTuple.site)
1916
1917        environLocal.printDebug('--returning from derivedObject search')
1918
1919    def getAllContextsByClass(self, className):
1920        '''
1921        Returns a generator that yields elements found by `.getContextByClass` and
1922        then finds the previous contexts for that element.
1923
1924        >>> s = stream.Stream()
1925        >>> s.append(meter.TimeSignature('2/4'))
1926        >>> s.append(note.Note('C'))
1927        >>> s.append(meter.TimeSignature('3/4'))
1928        >>> n = note.Note('D')
1929        >>> s.append(n)
1930
1931
1932        >>> for ts in n.getAllContextsByClass('TimeSignature'):
1933        ...     print(ts, ts.offset)
1934        <music21.meter.TimeSignature 3/4> 1.0
1935        <music21.meter.TimeSignature 2/4> 0.0
1936
1937        TODO: make it so that it does not skip over multiple matching classes
1938        at the same offset. with sortTuple
1939
1940        '''
1941        el = self.getContextByClass(className)
1942        while el is not None:
1943            yield el
1944            el = el.getContextByClass(className, getElementMethod='getElementBeforeOffset')
1945
1946    # -------------------------------------------------------------------------
1947
1948    def next(self, className=None, *, activeSiteOnly=False):
1949        '''
1950        Get the next element found in the activeSite (or other Sites)
1951        of this Music21Object.
1952
1953        The `className` can be used to specify one or more classes to match.
1954
1955        >>> s = corpus.parse('bwv66.6')
1956        >>> m3 = s.parts[0].measure(3)
1957        >>> m4 = s.parts[0].measure(4)
1958        >>> m3
1959        <music21.stream.Measure 3 offset=9.0>
1960        >>> m3.show('t')
1961        {0.0} <music21.layout.SystemLayout>
1962        {0.0} <music21.note.Note A>
1963        {0.5} <music21.note.Note B>
1964        {1.0} <music21.note.Note G#>
1965        {2.0} <music21.note.Note F#>
1966        {3.0} <music21.note.Note A>
1967        >>> m3.next()
1968        <music21.layout.SystemLayout>
1969        >>> nextM3 = m3.next('Measure')
1970        >>> nextM3 is m4
1971        True
1972
1973        Note that calling next() repeatedly gives...the same object.  You'll want to
1974        call next on that object...
1975
1976        >>> m3.next('Measure') is s.parts[0].measure(4)
1977        True
1978        >>> m3.next('Measure') is s.parts[0].measure(4)
1979        True
1980
1981        So do this instead:
1982
1983        >>> o = m3
1984        >>> for i in range(5):
1985        ...     print(o)
1986        ...     o = o.next('Measure')
1987        <music21.stream.Measure 3 offset=9.0>
1988        <music21.stream.Measure 4 offset=13.0>
1989        <music21.stream.Measure 5 offset=17.0>
1990        <music21.stream.Measure 6 offset=21.0>
1991        <music21.stream.Measure 7 offset=25.0>
1992
1993        We can find the next element given a certain class with the `className`:
1994
1995        >>> n = m3.next('Note')
1996        >>> n
1997        <music21.note.Note A>
1998        >>> n.measureNumber
1999        3
2000        >>> n is m3.notes.first()
2001        True
2002        >>> n.next()
2003        <music21.note.Note B>
2004
2005        Notice though that when we get to the end of the set of measures, something
2006        interesting happens (maybe it shouldn't? don't count on this...): we descend
2007        into the last measure and give its elements instead.
2008
2009        We'll leave o where it is (m8 now) to demonstrate what happens, and also
2010        print its Part for more information...
2011
2012        >>> while o is not None:
2013        ...     print(o, o.getContextByClass('Part'))
2014        ...     o = o.next()
2015        <music21.stream.Measure 8 offset=29.0> <music21.stream.Part Soprano>
2016        <music21.note.Note F#> <music21.stream.Part Soprano>
2017        <music21.note.Note F#> <music21.stream.Part Soprano>
2018        <music21.note.Note F#> <music21.stream.Part Soprano>
2019        <music21.stream.Measure 9 offset=33.0> <music21.stream.Part Soprano>
2020        <music21.note.Note F#> <music21.stream.Part Soprano>
2021        <music21.note.Note F#> <music21.stream.Part Soprano>
2022        <music21.note.Note E#> <music21.stream.Part Soprano>
2023        <music21.note.Note F#> <music21.stream.Part Soprano>
2024        <music21.bar.Barline type=final> <music21.stream.Part Soprano>
2025
2026        * changed in v.6 -- added activeSiteOnly -- see description in `.contextSites()`
2027        '''
2028        allSiteContexts = list(self.contextSites(
2029            returnSortTuples=True,
2030            priorityTargetOnly=activeSiteOnly,
2031        ))
2032        maxRecurse = 20
2033
2034        thisElForNext = self
2035        while maxRecurse:
2036            nextEl = thisElForNext.getContextByClass(
2037                className=className,
2038                getElementMethod='getElementAfterNotSelf',
2039                priorityTargetOnly=activeSiteOnly,
2040            )
2041
2042            callContinue = False
2043            for singleSiteContext, unused_positionInContext, unused_recurseType in allSiteContexts:
2044                if nextEl is singleSiteContext:
2045                    if nextEl and nextEl[0] is not self:  # has elements
2046                        return nextEl[0]
2047
2048                    thisElForNext = nextEl
2049                    callContinue = True
2050                    break
2051
2052            if callContinue:
2053                maxRecurse -= 1
2054                continue
2055
2056            if nextEl is not self:
2057                return nextEl
2058            maxRecurse -= 1
2059
2060        if maxRecurse == 0:
2061            raise Music21Exception('Maximum recursion!')
2062
2063    def previous(self, className=None, *, activeSiteOnly=False):
2064        '''
2065        Get the previous element found in the activeSite or other .sites of this
2066        Music21Object.
2067
2068        The `className` can be used to specify one or more classes to match.
2069
2070        >>> s = corpus.parse('bwv66.6')
2071        >>> m2 = s.parts[0].getElementsByClass('Measure')[2]  # pickup measure
2072        >>> m3 = s.parts[0].getElementsByClass('Measure')[3]
2073        >>> m3
2074        <music21.stream.Measure 3 offset=9.0>
2075        >>> m3prev = m3.previous()
2076        >>> m3prev
2077        <music21.note.Note C#>
2078        >>> m3prev is m2.notes[-1]
2079        True
2080        >>> m3.previous('Measure') is m2
2081        True
2082
2083        We'll iterate backwards from the first note of the second measure of the Alto part.
2084
2085        >>> o = s.parts[1].getElementsByClass('Measure')[2][0]
2086        >>> while o:
2087        ...    print(o)
2088        ...    o = o.previous()
2089        <music21.note.Note E>
2090        <music21.stream.Measure 2 offset=5.0>
2091        <music21.note.Note E>
2092        <music21.note.Note E>
2093        <music21.note.Note E>
2094        <music21.note.Note F#>
2095        <music21.stream.Measure 1 offset=1.0>
2096        <music21.note.Note E>
2097        <music21.meter.TimeSignature 4/4>
2098        f# minor
2099        <music21.clef.TrebleClef>
2100        <music21.stream.Measure 0 offset=0.0>
2101        P2: Alto: Instrument 2
2102        <music21.stream.Part Alto>
2103        <music21.stream.Part Soprano>
2104        <music21.metadata.Metadata object at 0x11116d080>
2105        <music21.stream.Score 0x10513af98>
2106
2107        * changed in v.6 -- added activeSiteOnly -- see description in `.contextSites()`
2108        '''
2109        # allSiteContexts = list(self.contextSites(returnSortTuples=True))
2110        # maxRecurse = 20
2111
2112        prevEl = self.getContextByClass(className=className,
2113                                        getElementMethod='getElementBeforeNotSelf',
2114                                        priorityTargetOnly=activeSiteOnly,
2115                                        )
2116
2117        # for singleSiteContext, unused_positionInContext, unused_recurseType in allSiteContexts:
2118        #     if prevEl is singleSiteContext:
2119        #         prevElPrev = prevEl.getContextByClass(prevEl.__class__,
2120        #                                             getElementMethod='getElementBeforeNotSelf')
2121        #         if prevElPrev and prevElPrev is not self:
2122        #             return prevElPrev
2123        isInPart = False
2124        if self.isStream and prevEl is not None:
2125            # if self is a Part, ensure that the previous element is not in self
2126            for cs, unused1, unused2 in prevEl.contextSites():
2127                if cs is self:
2128                    isInPart = True
2129                    break
2130
2131        if prevEl and prevEl is not self and not isInPart:
2132            return prevEl
2133        else:
2134            # okay, go up to next level
2135            activeS = self.activeSite  # might be None...
2136            if activeS is None:
2137                return None
2138            if className is not None and not common.isListLike(className):
2139                className = (className,)
2140            asTree = activeS.asTree(classList=className, flatten=False)
2141            prevNode = asTree.getNodeBefore(self.sortTuple())
2142            if prevNode is None:
2143                if className is None or not activeS.classSet.isdisjoint(className):
2144                    return activeS
2145                else:
2146                    return None
2147            else:
2148                return prevNode.payload
2149
2150    # end contexts...
2151    # --------------------------------------------------------------------------------
2152
2153    # -------------------------------------------------------------------------
2154    # properties
2155
2156    def _getActiveSite(self):
2157        # can be None
2158        if sites.WEAKREF_ACTIVE:
2159            if self._activeSite is None:  # leave None
2160                return None
2161            else:  # even if current activeSite is not a weakref, this will work
2162                # environLocal.printDebug(['_getActiveSite() called:',
2163                #                          'self._activeSite', self._activeSite])
2164                return common.unwrapWeakref(self._activeSite)
2165        else:  # pragma: no cover
2166            return self._activeSite
2167
2168    def _setActiveSite(self, site: Union['music21.stream.Stream', None]):
2169        # environLocal.printDebug(['_setActiveSite() called:', 'self', self, 'site', site])
2170
2171        # NOTE: this is a performance intensive call
2172        if site is not None:
2173            try:
2174                storedOffset = site.elementOffset(self)
2175            except SitesException as se:
2176                raise SitesException(
2177                    'activeSite cannot be set for '
2178                    + f'object {self} not in the Stream {site}'
2179                ) from se
2180
2181            self._activeSiteStoredOffset = storedOffset
2182            # siteId = id(site)
2183            # if not self.sites.hasSiteId(siteId):  # This should raise a warning, should not happen
2184            #    # environLocal.warn('Adding a siteDict entry for a ' +
2185            #    #                        'site that should already be there!')
2186            #    self.sites.add(site, idKey=siteId)
2187        else:
2188            self._activeSiteStoredOffset = None
2189
2190        if sites.WEAKREF_ACTIVE:
2191            if site is None:  # leave None alone
2192                self._activeSite = None
2193            else:
2194                self._activeSite = common.wrapWeakref(site)
2195        else:  # pragma: no cover
2196            self._activeSite = site
2197
2198    activeSite = property(_getActiveSite,
2199                          _setActiveSite,
2200                          doc='''
2201        A reference to the most-recent object used to
2202        contain this object. In most cases, this will be a
2203        Stream or Stream sub-class. In most cases, an object's
2204        activeSite attribute is automatically set when an the
2205        object is attached to a Stream.
2206
2207
2208        >>> n = note.Note('C#4')
2209        >>> p = stream.Part()
2210        >>> p.insert(20.0, n)
2211        >>> n.activeSite is p
2212        True
2213        >>> n.offset
2214        20.0
2215
2216        >>> m = stream.Measure()
2217        >>> m.insert(10.0, n)
2218        >>> n.activeSite is m
2219        True
2220        >>> n.offset
2221        10.0
2222        >>> n.activeSite = p
2223        >>> n.offset
2224        20.0
2225        ''')
2226
2227    def _getOffset(self):
2228        '''Get the offset for the activeSite.
2229
2230        >>> n = note.Note()
2231        >>> m = stream.Measure()
2232        >>> m.id = 'm1'
2233        >>> m.insert(3.0, n)
2234        >>> n.activeSite is m
2235        True
2236        >>> n.offset
2237        3.0
2238
2239        Still works...
2240
2241        >>> n.offset
2242        3.0
2243
2244        There is a branch that does slow searches.
2245        See test/testSerialization to have it active.
2246        '''
2247        # there is a problem if a new activeSite is being set and no offsets have
2248        # been provided for that activeSite; when self.offset is called,
2249        # the first case here would match
2250        # environLocal.printDebug(['Music21Object._getOffset', 'self.id',
2251        #                           self.id, 'id(self)', id(self), self.__class__])
2252        activeSiteWeakRef = self._activeSite
2253        if activeSiteWeakRef is not None:
2254            activeSite = self.activeSite
2255            if activeSite is None:
2256                # it has died since last visit, as is the case with short-lived streams like
2257                # .getElementsByClass, so we will return the most recent position
2258                return self._activeSiteStoredOffset
2259
2260            try:
2261                o = activeSite.elementOffset(self)
2262            except SitesException:
2263                environLocal.printDebug(
2264                    'Not in Stream: changing activeSite to None and returning _naiveOffset')
2265                self.activeSite = None
2266                o = self._naiveOffset
2267        else:
2268            o = self._naiveOffset
2269
2270        return o
2271
2272    def _setOffset(self, value):
2273        '''
2274        Set the offset for the activeSite.
2275        '''
2276        # assume that most times this is a number; in that case, the fastest
2277        # thing to do is simply try to set the offset w/ float(value)
2278        try:
2279            offset = opFrac(value)
2280        except TypeError:
2281            offset = value
2282
2283        if hasattr(value, 'quarterLength'):
2284            # probably a Duration object, but could be something else -- in any case, we'll take it.
2285            offset = value.quarterLength
2286
2287        if self.activeSite is not None:
2288            self.activeSite.setElementOffset(self, offset)
2289        else:
2290            self._naiveOffset = offset
2291
2292    offset = property(_getOffset,
2293                      _setOffset,
2294                      doc='''
2295        The offset property sets or returns the position of this object
2296        as a float or fractions.Fraction value
2297        (generally in `quarterLengths`), depending on what is representable.
2298
2299        Offsets are measured from the start of the object's `activeSite`,
2300        that is, the most recently referenced `Stream` or `Stream` subclass such
2301        as `Part`, `Measure`, or `Voice`.  It is a simpler
2302        way of calling `o.getOffsetBySite(o.activeSite, returnType='rational')`.
2303
2304        If we put a `Note` into a `Stream`, we will see the activeSite changes.
2305
2306        >>> import fractions
2307        >>> n1 = note.Note('D#3')
2308        >>> n1.activeSite is None
2309        True
2310
2311        >>> m1 = stream.Measure()
2312        >>> m1.number = 4
2313        >>> m1.insert(10.0, n1)
2314        >>> n1.offset
2315        10.0
2316        >>> n1.activeSite
2317        <music21.stream.Measure 4 offset=0.0>
2318
2319        >>> n1.activeSite is m1
2320        True
2321
2322        The most recently referenced `Stream` becomes an object's `activeSite` and
2323        thus the place where `.offset` looks to find its number.
2324
2325        >>> m2 = stream.Measure()
2326        >>> m2.insert(3.0/5, n1)
2327        >>> m2.number = 5
2328        >>> n1.offset
2329        Fraction(3, 5)
2330        >>> n1.activeSite is m2
2331        True
2332
2333        Notice though that `.offset` depends on the `.activeSite` which is the most
2334        recently accessed/referenced Stream.
2335
2336        Here we will iterate over the `elements` in `m1` and we
2337        will see that the `.offset` of `n1` now is its offset in
2338        `m1` even though we haven't done anything directly to `n1`.
2339        Simply iterating over a site is enough to change the `.activeSite`
2340        of its elements:
2341
2342        >>> for element in m1:
2343        ...     pass
2344        >>> n1.offset
2345        10.0
2346
2347
2348        The property can also set the offset for the object if no
2349        container has been set:
2350
2351
2352        >>> n1 = note.Note()
2353        >>> n1.id = 'hi'
2354        >>> n1.offset = 20/3.
2355        >>> n1.offset
2356        Fraction(20, 3)
2357        >>> float(n1.offset)
2358        6.666...
2359
2360
2361        >>> s1 = stream.Stream()
2362        >>> s1.append(n1)
2363        >>> n1.offset
2364        0.0
2365        >>> s2 = stream.Stream()
2366        >>> s2.insert(30.5, n1)
2367        >>> n1.offset
2368        30.5
2369
2370        After calling `getElementById` on `s1`, the
2371        returned element's `offset` will be its offset in `s1`.
2372
2373        >>> n2 = s1.getElementById('hi')
2374        >>> n2 is n1
2375        True
2376        >>> n2.offset
2377        0.0
2378
2379        Iterating over the elements in a Stream will
2380        make its `offset` be the offset in iterated
2381        Stream.
2382
2383        >>> for thisElement in s2:
2384        ...     thisElement.offset
2385        30.5
2386
2387        When in doubt, use `.getOffsetBySite(streamObj)`
2388        which is safer or streamObj.elementOffset(self) which is 3x faster.
2389        ''')
2390
2391    def sortTuple(self, useSite=False, raiseExceptionOnMiss=False):
2392        '''
2393        Returns a collections.namedtuple called SortTuple(atEnd, offset, priority, classSortOrder,
2394        isNotGrace, insertIndex)
2395        which contains the six elements necessary to determine the sort order of any set of
2396        objects in a Stream.
2397
2398        1) atEnd = {0, 1}; Elements specified to always stay at
2399        the end of a stream (``stream.storeAtEnd``)
2400        sort after normal elements.
2401
2402        2) offset = float; Offset (with respect to the active site) is the next and most
2403        important parameter in determining the order of elements in a stream (the note on beat 1
2404        has offset 0.0, while the note on beat 2 might have offset 1.0).
2405
2406        3) priority = int; Priority is a
2407        user-specified property (default 0) that can set the order of
2408        elements which have the same
2409        offset (for instance, two Parts both at offset 0.0).
2410
2411        4) classSortOrder = int or float; ClassSortOrder
2412        is the third level of comparison that gives an ordering to elements with different classes,
2413        ensuring, for instance that Clefs (classSortOrder = 0) sort before Notes
2414        (classSortOrder = 20).
2415
2416        5) isNotGrace = {0, 1}; 0 = grace, 1 = normal. Grace notes sort before normal notes
2417
2418        6) The last tie breaker is the creation time (insertIndex) of the site object
2419        represented by the activeSite.
2420
2421        >>> n = note.Note()
2422        >>> n.offset = 4.0
2423        >>> n.priority = -3
2424        >>> n.sortTuple()
2425        SortTuple(atEnd=0, offset=4.0, priority=-3, classSortOrder=20,
2426                    isNotGrace=1, insertIndex=0)
2427
2428        >>> st = n.sortTuple()
2429
2430        Check that all these values are the same as above...
2431
2432        >>> st.offset == n.offset
2433        True
2434        >>> st.priority == n.priority
2435        True
2436
2437        An object's classSortOrder comes from the Class object itself:
2438
2439        >>> st.classSortOrder == note.Note.classSortOrder
2440        True
2441
2442        SortTuples have a few methods that are documented in :class:`~music21.sorting.SortTuple`.
2443        The most useful one for documenting is `.shortRepr()`.
2444
2445        >>> st.shortRepr()
2446        '4.0 <-3.20.0>'
2447
2448        Inserting the note into the Stream will set the insertIndex.  Most implementations of
2449        music21 will use a global counter rather than an actual timer.  Note that this is a
2450        last resort, but useful for things such as multiple Parts inserted in order.  It changes
2451        with each run, so we can't display it here...
2452
2453        >>> s = stream.Stream()
2454        >>> s.insert(n)
2455        >>> n.sortTuple()
2456        SortTuple(atEnd=0, offset=4.0, priority=-3, classSortOrder=20,
2457                     isNotGrace=1, insertIndex=...)
2458
2459        >>> nInsertIndex = n.sortTuple().insertIndex
2460
2461        If we create another nearly identical note, the insertIndex will be different:
2462
2463        >>> n2 = note.Note()
2464        >>> n2.offset = 4.0
2465        >>> n2.priority = -3
2466        >>> s.insert(n2)
2467        >>> n2InsertIndex = n2.sortTuple().insertIndex
2468        >>> n2InsertIndex > nInsertIndex
2469        True
2470
2471        >>> rb = bar.Barline()
2472        >>> s.storeAtEnd(rb)
2473        >>> rb.sortTuple()
2474        SortTuple(atEnd=1, offset=0.0, priority=0, classSortOrder=-5,
2475                    isNotGrace=1, insertIndex=...)
2476
2477
2478        Normally if there's a site specified and the element is not in the site,
2479        the offset of None will be used, but if raiseExceptionOnMiss is set to True
2480        then a SitesException will be raised:
2481
2482        >>> aloneNote = note.Note()
2483        >>> aloneNote.offset = 30
2484        >>> aloneStream = stream.Stream(id='aloneStream')  # no insert
2485        >>> aloneNote.sortTuple(aloneStream)
2486        SortTuple(atEnd=0, offset=30.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=0)
2487
2488        >>> aloneNote.sortTuple(aloneStream, raiseExceptionOnMiss=True)
2489        Traceback (most recent call last):
2490        music21.sites.SitesException: an entry for this object 0x... is not stored in
2491            stream <music21.stream.Stream aloneStream>
2492        '''
2493        if useSite is False:  # False or a Site; since None is a valid site, default is False
2494            useSite = self.activeSite
2495
2496        if useSite is None:
2497            foundOffset = self.offset
2498        else:
2499            try:
2500                foundOffset = useSite.elementOffset(self, returnSpecial=True)
2501            except SitesException:
2502                if raiseExceptionOnMiss:
2503                    raise
2504                # environLocal.warn(r)
2505                    # activeSite may have vanished! or does not have the element
2506                foundOffset = self._naiveOffset
2507
2508        if foundOffset == OffsetSpecial.AT_END:
2509            offset = 0.0
2510            atEnd = 1
2511        else:
2512            offset = foundOffset
2513            atEnd = 0
2514
2515        if self.duration.isGrace:
2516            isNotGrace = 0
2517        else:
2518            isNotGrace = 1
2519
2520        if (useSite is not False
2521                and self.sites.hasSiteId(id(useSite))):
2522            insertIndex = self.sites.siteDict[id(useSite)].globalSiteIndex
2523        elif self.activeSite is not None:
2524            insertIndex = self.sites.siteDict[id(self.activeSite)].globalSiteIndex
2525        else:
2526            insertIndex = 0
2527
2528        return SortTuple(atEnd, offset, self.priority,
2529                          self.classSortOrder, isNotGrace, insertIndex)
2530
2531    # -----------------------------------------------------------------
2532    def _getDuration(self) -> Optional[duration.Duration]:
2533        '''
2534        Gets the DurationObject of the object or None
2535        '''
2536        # lazy duration creation
2537        if self._duration is None:
2538            self._duration = duration.Duration(0)
2539        return self._duration
2540
2541    def _setDuration(self, durationObj: duration.Duration):
2542        '''
2543        Set the duration as a quarterNote length
2544        '''
2545        durationObjAlreadyExists = not (self._duration is None)
2546
2547        try:
2548            ql = durationObj.quarterLength
2549            self._duration = durationObj
2550            durationObj.client = self
2551            if durationObjAlreadyExists:
2552                self.informSites({'changedElement': 'duration', 'quarterLength': ql})
2553
2554        except AttributeError as ae:
2555            # need to permit Duration object assignment here
2556            raise Exception(
2557                f'this must be a Duration object, not {durationObj}'
2558            ) from ae
2559
2560    duration = property(_getDuration, _setDuration,
2561                        doc='''
2562        Get and set the duration of this object as a Duration object.
2563        ''')
2564
2565    def informSites(self, changedInformation=None):
2566        '''
2567        trigger called whenever sites need to be informed of a change
2568        in the parameters of this object.
2569
2570        `changedInformation` is not used now, but it can be a dictionary
2571        of what has changed.
2572
2573        subclass this to do very interesting things.
2574        '''
2575        for s in self.sites.get():
2576            if hasattr(s, 'coreElementsChanged'):
2577                # noinspection PyCallingNonCallable
2578                s.coreElementsChanged(updateIsFlat=False, keepIndex=True)
2579
2580    def _getPriority(self):
2581        return self._priority
2582
2583    def _setPriority(self, value):
2584        '''
2585        value is an int.
2586
2587        Informs all sites of the change.
2588        '''
2589        if not isinstance(value, int):
2590            raise ElementException('priority values must be integers.')
2591        if self._priority != value:
2592            self._priority = value
2593            self.informSites({'changedElement': 'priority', 'priority': value})
2594
2595    priority = property(_getPriority,
2596                        _setPriority,
2597                        doc='''
2598        Get and set the priority integer value.
2599
2600        Priority specifies the order of processing from left (lowest number)
2601        to right (highest number) of objects at the same offset.  For
2602        instance, if you want a key change and a clef change to happen at
2603        the same time but the key change to appear first, then set:
2604        keySigElement.priority = 1; clefElement.priority = 2 this might be
2605        a slightly counterintuitive numbering of priority, but it does
2606        mean, for instance, if you had two elements at the same offset,
2607        an allegro tempo change and an andante tempo change, then the
2608        tempo change with the higher priority number would apply to the
2609        following notes (by being processed second).
2610
2611        Default priority is 0; thus negative priorities are encouraged
2612        to have Elements that appear before non-priority set elements.
2613
2614        In case of tie, there are defined class sort orders defined in
2615        `music21.base.classSortOrder`.  For instance, a key signature
2616        change appears before a time signature change before a
2617        note at the same offset.  This produces the familiar order of
2618        materials at the start of a musical score.
2619
2620        >>> import music21
2621        >>> a = music21.Music21Object()
2622        >>> a.priority = 3
2623        >>> a.priority = 'high'
2624        Traceback (most recent call last):
2625        music21.base.ElementException: priority values must be integers.
2626        ''')
2627
2628    # -------------------------------------------------------------------------
2629    # display and writing
2630
2631    def write(self, fmt=None, fp=None, **keywords):  # pragma: no cover
2632        '''
2633        Write out a file of music notation (or an image, etc.) in a given format.  If
2634        fp is specified as a file path then the file will be placed there.  If it is not
2635        given then a temporary file will be created.
2636
2637        If fmt is not given then the default of your Environment's 'writeFormat' will
2638        be used.  For most people that is musicxml.
2639
2640        Returns the full path to the file.
2641
2642        Some formats, including .musicxml, create a copy of the stream, pack it into a well-formed
2643        score if necessary, and run :meth:`~music21.stream.Score.makeNotation`. To
2644        avoid this when writing .musicxml, use `makeNotation=False`, an advanced option
2645        that prioritizes speed but may not guarantee satisfactory notation.
2646        '''
2647        if fmt is None:  # get setting in environment
2648            fmt = environLocal['writeFormat']
2649        elif fmt.startswith('.'):
2650            fmt = fmt[1:]
2651
2652        if fmt == 'mxl':
2653            keywords['compress'] = True
2654
2655        regularizedConverterFormat, unused_ext = common.findFormat(fmt)
2656        if regularizedConverterFormat is None:
2657            raise Music21ObjectException(f'cannot support showing in this format yet: {fmt}')
2658
2659        formatSubs = fmt.split('.')
2660        fmt = formatSubs[0]
2661        subformats = formatSubs[1:]
2662
2663        scClass = common.findSubConverterForFormat(regularizedConverterFormat)
2664        formatWriter = scClass()
2665        return formatWriter.write(self,
2666                                  regularizedConverterFormat,
2667                                  fp=fp,
2668                                  subformats=subformats,
2669                                  **keywords)
2670
2671    def _reprText(self, **keywords):
2672        '''
2673        Return a text representation possible with line
2674        breaks. This methods can be overridden by subclasses
2675        to provide alternative text representations.
2676        '''
2677        return repr(self)
2678
2679    def _reprTextLine(self):
2680        '''
2681        Return a text representation without line breaks. This
2682        methods can be overridden by subclasses to provide
2683        alternative text representations.
2684        '''
2685        return repr(self)
2686
2687    def show(self, fmt=None, app=None, **keywords):  # pragma: no cover
2688        '''
2689        Displays an object in a format provided by the
2690        fmt argument or, if not provided, the format set in the user's Environment
2691
2692        Valid formats include (but are not limited to)::
2693
2694            musicxml
2695            text
2696            midi
2697            lily (or lilypond)
2698            lily.png
2699            lily.pdf
2700            lily.svg
2701            braille
2702            vexflow
2703            musicxml.png
2704
2705        N.B. score.write('lily') returns a bare lilypond file,
2706        score.show('lily') runs it through lilypond and displays it as a png.
2707
2708        Some formats, including .musicxml, create a copy of the stream, pack it into a well-formed
2709        score if necessary, and run :meth:`~music21.stream.Score.makeNotation`. To
2710        avoid this when showing .musicxml, use `makeNotation=False`, an advanced option
2711        that prioritizes speed but may not guarantee satisfactory notation.
2712        '''
2713        # note that all formats here must be defined in
2714        # common.VALID_SHOW_FORMATS
2715        if fmt is None:  # get setting in environment
2716            if common.runningUnderIPython():
2717                try:
2718                    fmt = environLocal['ipythonShowFormat']
2719                except environment.EnvironmentException:
2720                    fmt = 'ipython.musicxml.png'
2721            else:
2722                fmt = environLocal['showFormat']
2723        elif fmt.startswith('.'):
2724            fmt = fmt[1:]
2725        elif common.runningUnderIPython() and fmt.startswith('midi'):
2726            fmt = 'ipython.' + fmt
2727
2728        regularizedConverterFormat, unused_ext = common.findFormat(fmt)
2729        if regularizedConverterFormat is None:
2730            raise Music21ObjectException(f'cannot support showing in this format yet:{fmt}')
2731
2732        formatSubs = fmt.split('.')
2733        fmt = formatSubs[0]
2734        subformats = formatSubs[1:]
2735
2736        scClass = common.findSubConverterForFormat(regularizedConverterFormat)
2737        formatWriter = scClass()
2738        return formatWriter.show(self,
2739                                 regularizedConverterFormat,
2740                                 app=app,
2741                                 subformats=subformats,
2742                                 **keywords)
2743
2744    # -------------------------------------------------------------------------
2745    # duration manipulation, processing, and splitting
2746
2747    def containerHierarchy(
2748        self,
2749        *,
2750        followDerivation=True,
2751        includeNonStreamDerivations=False
2752    ):
2753        '''
2754        Return a list of Stream subclasses that this object
2755        is contained within or (if followDerivation is set) is derived from.
2756
2757        This gives access to the hierarchy that contained or
2758        created this object.
2759
2760        >>> s = corpus.parse('bach/bwv66.6')
2761        >>> noteE = s[1][2][3]
2762        >>> noteE
2763        <music21.note.Note E>
2764        >>> [e for e in noteE.containerHierarchy()]
2765        [<music21.stream.Measure 1 offset=1.0>,
2766         <music21.stream.Part Soprano>,
2767         <music21.stream.Score 0x1049a5668>]
2768
2769
2770        Note that derived objects also can follow the container hierarchy:
2771
2772        >>> import copy
2773        >>> n2 = copy.deepcopy(noteE)
2774        >>> [e for e in n2.containerHierarchy()]
2775        [<music21.stream.Measure 1 offset=1.0>,
2776         <music21.stream.Part Soprano>,
2777         <music21.stream.Score 0x1049a5668>]
2778
2779        Unless followDerivation is False:
2780
2781        >>> [e for e in n2.containerHierarchy(followDerivation=False)]
2782        []
2783
2784        if includeNonStreamDerivations is True then n2's containerHierarchy will include
2785        n even though it's not a container.  It gives a good idea of how the hierarchy is being
2786        constructed.
2787
2788        >>> [e for e in n2.containerHierarchy(includeNonStreamDerivations=True)]
2789        [<music21.note.Note E>,
2790         <music21.stream.Measure 1 offset=1.0>,
2791         <music21.stream.Part Soprano>,
2792         <music21.stream.Score 0x1049a5668>]
2793
2794
2795        The method follows activeSites, so set the activeSite as necessary.
2796
2797        >>> p = stream.Part(id='newPart')
2798        >>> m = stream.Measure(number=20)
2799        >>> p.insert(0, m)
2800        >>> m.insert(0, noteE)
2801        >>> noteE.activeSite
2802        <music21.stream.Measure 20 offset=0.0>
2803        >>> noteE.containerHierarchy()
2804        [<music21.stream.Measure 20 offset=0.0>,
2805         <music21.stream.Part newPart>]
2806
2807        * changed in v.5.7: followDerivation and includeNonStreamDerivations are now keyword only
2808        '''
2809        post = []
2810        focus = self
2811        endMe = 200
2812        while endMe > 0:
2813            endMe = endMe - 1  # do not go forever
2814            # collect activeSite unless activeSite is None;
2815            # if so, try to get rootDerivation
2816            candidate = focus.activeSite
2817            # environLocal.printDebug(['containerHierarchy(): activeSite found:', candidate])
2818            if candidate is None:  # nothing more to derive
2819                # if this is a Stream, we might find a root derivation
2820                if followDerivation is True and hasattr(focus, 'derivation'):
2821                    # environLocal.printDebug(['containerHierarchy():
2822                    # found rootDerivation:', focus.rootDerivation])
2823                    alt = focus.derivation.rootDerivation
2824                    if alt is None:
2825                        return post
2826                    else:
2827                        candidate = alt
2828                else:
2829                    return post
2830            if includeNonStreamDerivations is True or candidate.isStream:
2831                post.append(candidate)
2832            focus = candidate
2833        return post
2834
2835    def splitAtQuarterLength(
2836        self,
2837        quarterLength,
2838        *,
2839        retainOrigin=True,
2840        addTies=True,
2841        displayTiedAccidentals=False
2842    ) -> _SplitTuple:
2843        # noinspection PyShadowingNames
2844        '''
2845        Split an Element into two Elements at a provided
2846        `quarterLength` (offset) into the Element.
2847
2848        Returns a specialized tuple that also has
2849        a .spannerList element which is a list of spanners
2850        that were created during the split, such as by splitting a trill
2851        note into more than one trill.
2852
2853        TODO: unite into a "split" function -- document obscure uses.
2854
2855        >>> a = note.Note('C#5')
2856        >>> a.duration.type = 'whole'
2857        >>> a.articulations = [articulations.Staccato()]
2858        >>> a.lyric = 'hi'
2859        >>> a.expressions = [expressions.Mordent(), expressions.Trill(), expressions.Fermata()]
2860        >>> st = a.splitAtQuarterLength(3)
2861        >>> b, c = st
2862        >>> b.duration.type
2863        'half'
2864        >>> b.duration.dots
2865        1
2866        >>> b.duration.quarterLength
2867        3.0
2868        >>> b.articulations
2869        []
2870        >>> b.lyric
2871        'hi'
2872        >>> b.expressions
2873        [<music21.expressions.Mordent>, <music21.expressions.Trill>]
2874        >>> c.duration.type
2875        'quarter'
2876        >>> c.duration.dots
2877        0
2878        >>> c.duration.quarterLength
2879        1.0
2880        >>> c.articulations
2881        [<music21.articulations.Staccato>]
2882        >>> c.lyric
2883        >>> c.expressions
2884        [<music21.expressions.Fermata>]
2885        >>> c.getSpannerSites()
2886        [<music21.expressions.TrillExtension <music21.note.Note C#><music21.note.Note C#>>]
2887
2888        st is a _SplitTuple which can get the spanners from it for inserting into a Stream.
2889
2890        >>> st.spannerList
2891        [<music21.expressions.TrillExtension <music21.note.Note C#><music21.note.Note C#>>]
2892
2893
2894
2895        Make sure that ties and accidentals remain as they should be:
2896
2897        >>> d = note.Note('D#4')
2898        >>> d.duration.quarterLength = 3.0
2899        >>> d.tie = tie.Tie('start')
2900        >>> e, f = d.splitAtQuarterLength(2.0)
2901        >>> e.tie, f.tie
2902        (<music21.tie.Tie start>, <music21.tie.Tie continue>)
2903        >>> e.pitch.accidental.displayStatus is None
2904        True
2905        >>> f.pitch.accidental.displayStatus
2906        False
2907
2908        Should be the same for chords...
2909
2910        >>> g = chord.Chord(['C4', 'E4', 'G#4'])
2911        >>> g.duration.quarterLength = 3.0
2912        >>> g[1].tie = tie.Tie('start')
2913        >>> h, i = g.splitAtQuarterLength(2.0)
2914        >>> for j in range(3):
2915        ...   (h[j].tie, i[j].tie)
2916        (<music21.tie.Tie start>, <music21.tie.Tie stop>)
2917        (<music21.tie.Tie start>, <music21.tie.Tie continue>)
2918        (<music21.tie.Tie start>, <music21.tie.Tie stop>)
2919
2920        >>> h[2].pitch.accidental.displayStatus, i[2].pitch.accidental.displayStatus
2921        (None, False)
2922
2923
2924        If quarterLength == self.quarterLength then the second element will be None.
2925
2926        >>> n = note.Note()
2927        >>> n.quarterLength = 0.5
2928        >>> a, b = n.splitAtQuarterLength(0.5)
2929        >>> b is None
2930        True
2931        >>> a is n
2932        True
2933
2934        (same with retainOrigin off)
2935
2936        >>> n = note.Note()
2937        >>> n.quarterLength = 0.5
2938        >>> a, b = n.splitAtQuarterLength(0.5, retainOrigin=False)
2939        >>> a is n
2940        False
2941
2942
2943        If quarterLength > self.quarterLength then a DurationException will be raised:
2944
2945        >>> n = note.Note()
2946        >>> n.quarterLength = 0.5
2947        >>> a, b = n.splitAtQuarterLength(0.7)
2948        Traceback (most recent call last):
2949        music21.duration.DurationException: cannot split a duration (0.5)
2950            at this quarterLength (7/10)
2951
2952        Changed in v7. -- all but quarterLength are keyword only
2953        '''
2954        from music21 import chord
2955        from music21 import note
2956        quarterLength = opFrac(quarterLength)
2957
2958        if quarterLength > self.duration.quarterLength:
2959            raise duration.DurationException(
2960                f'cannot split a duration ({self.duration.quarterLength}) '
2961                + f'at this quarterLength ({quarterLength})'
2962            )
2963
2964        if retainOrigin is True:
2965            e = self
2966        else:
2967            e = copy.deepcopy(self)
2968        eRemain = copy.deepcopy(self)
2969
2970        # clear lyrics from remaining parts
2971        if hasattr(eRemain, 'lyrics') and not callable(eRemain.lyrics):
2972            # lyrics is a function on Streams...
2973            eRemain.lyrics = []  # pylint: disable=attribute-defined-outside-init
2974
2975        spannerList = []
2976        for listType in ('expressions', 'articulations'):
2977            if hasattr(e, listType):
2978                temp = getattr(e, listType)
2979                setattr(e, listType, [])  # pylint: disable=attribute-defined-outside-init
2980                setattr(eRemain, listType, [])
2981                for thisExpression in temp:  # using thisExpression as a shortcut for expr or art.
2982                    if hasattr(thisExpression, 'splitClient'):  # special method (see Trill)
2983                        spanners = thisExpression.splitClient([e, eRemain])
2984                        for s in spanners:
2985                            spannerList.append(s)
2986                    elif hasattr(thisExpression, 'tieAttach'):
2987                        if thisExpression.tieAttach == 'first':
2988                            eList = getattr(e, listType)
2989                            eList.append(thisExpression)
2990                        elif thisExpression.tieAttach == 'last':
2991                            eRemainList = getattr(eRemain, listType)
2992                            eRemainList.append(thisExpression)
2993                        else:  # default = 'all'
2994                            eList = getattr(e, listType)
2995                            eList.append(thisExpression)
2996                            eRemainList = getattr(eRemain, listType)
2997                            eRemainList.append(thisExpression)
2998                    else:  # default = 'all'
2999                        eList = getattr(e, listType)
3000                        eList.append(thisExpression)
3001                        eRemainList = getattr(eRemain, listType)
3002                        eRemainList.append(thisExpression)
3003
3004        if abs(quarterLength - self.duration.quarterLength) < 0:
3005            quarterLength = self.duration.quarterLength
3006
3007        lenEnd = self.duration.quarterLength - quarterLength
3008        lenStart = self.duration.quarterLength - lenEnd
3009
3010        d1 = duration.Duration()
3011        d1.quarterLength = lenStart
3012
3013        d2 = duration.Duration()
3014        d2.quarterLength = lenEnd
3015
3016        e.duration = d1
3017        eRemain.duration = d2
3018
3019        # some higher-level classes need this functionality
3020        # set ties
3021        if addTies and isinstance(e, (note.Note, note.Unpitched)):
3022            forceEndTieType = 'stop'
3023            if hasattr(e, 'tie') and e.tie is not None:
3024                # the last tie of what was formally a start should
3025                # continue
3026                if e.tie.type == 'start':
3027                    # keep start  if already set
3028                    forceEndTieType = 'continue'
3029                # a stop was ending a previous tie; we know that
3030                # the first is now a continue
3031                elif e.tie.type == 'stop':
3032                    forceEndTieType = 'stop'
3033                    e.tie.type = 'continue'
3034                elif e.tie.type == 'continue':
3035                    forceEndTieType = 'continue'
3036                    # keep continue if already set
3037            elif hasattr(e, 'tie'):
3038                e.tie = tie.Tie('start')  # pylint: disable=attribute-defined-outside-init
3039                # #need a tie object
3040
3041            if hasattr(eRemain, 'tie'):
3042                # pylint: disable=attribute-defined-outside-init
3043                eRemain.tie = tie.Tie(forceEndTieType)
3044
3045        elif addTies and isinstance(e, chord.Chord):
3046            for i in range(len(e.notes)):
3047                component = e.notes[i]
3048                remainComponent = eRemain.notes[i]
3049                forceEndTieType = 'stop'
3050                if component.tie is not None:
3051                    # the last tie of what was formally a start should
3052                    # continue
3053                    if component.tie.type == 'start':
3054                        # keep start  if already set
3055                        forceEndTieType = 'continue'
3056                    # a stop was ending a previous tie; we know that
3057                    # the first is now a continue
3058                    elif component.tie.type == 'stop':
3059                        forceEndTieType = 'stop'
3060                        component.tie.type = 'continue'
3061                    elif component.tie.type == 'continue':
3062                        forceEndTieType = 'continue'
3063                        # keep continue if already set
3064                else:
3065                    component.tie = tie.Tie('start')  # need a tie object
3066                remainComponent.tie = tie.Tie(forceEndTieType)
3067
3068        # hide accidentals on tied notes where previous note
3069        # had an accidental that was shown
3070        if addTies and hasattr(e, 'pitches'):
3071            for i, p in enumerate(e.pitches):
3072                remainP = eRemain.pitches[i]
3073                if hasattr(p, 'accidental') and p.accidental is not None:
3074                    if not displayTiedAccidentals:  # if False
3075                        if p.accidental.displayType != 'even-tied':
3076                            remainP.accidental.displayStatus = False
3077                    else:  # display tied accidentals
3078                        remainP.accidental.displayType = 'even-tied'
3079                        remainP.accidental.displayStatus = True
3080
3081        if eRemain.duration.quarterLength > 0.0:
3082            st = _SplitTuple([e, eRemain])
3083        else:
3084            st = _SplitTuple([e, None])
3085
3086        if spannerList:
3087            st.spannerList = spannerList
3088
3089        return st
3090
3091    def splitByQuarterLengths(
3092        self,
3093        quarterLengthList: List[Union[int, float]],
3094        addTies=True,
3095        displayTiedAccidentals=False
3096    ) -> _SplitTuple:
3097        '''
3098        Given a list of quarter lengths, return a list of
3099        Music21Object objects, copied from this Music21Object,
3100        that are partitioned and tied with the specified quarter
3101        length list durations.
3102
3103        TODO: unite into a "split" function -- document obscure uses.
3104
3105        >>> n = note.Note()
3106        >>> n.quarterLength = 3
3107        >>> post = n.splitByQuarterLengths([1, 1, 1])
3108        >>> [n.quarterLength for n in post]
3109        [1.0, 1.0, 1.0]
3110        '''
3111        if opFrac(sum(quarterLengthList)) != self.duration.quarterLength:
3112            raise Music21ObjectException(
3113                'cannot split by quarter length list whose sum is not '
3114                + 'equal to the quarterLength duration of the source: '
3115                + f'{quarterLengthList}, {self.duration.quarterLength}'
3116            )
3117
3118        # if nothing to do
3119        if len(quarterLengthList) == 1:
3120            # return a copy of self in a list
3121            return _SplitTuple([copy.deepcopy(self)])
3122        elif len(quarterLengthList) <= 1:
3123            raise Music21ObjectException(
3124                f'cannot split by this quarter length list: {quarterLengthList}.')
3125
3126        eList = []
3127        spannerList = []  # this does not fully work with trills over multiple splits yet.
3128        eRemain = copy.deepcopy(self)
3129        for qlIndex in range(len(quarterLengthList) - 1):
3130            qlSplit = quarterLengthList[qlIndex]
3131            st = eRemain.splitAtQuarterLength(qlSplit,
3132                                              addTies=addTies,
3133                                              displayTiedAccidentals=displayTiedAccidentals)
3134            newEl, eRemain = st
3135            eList.append(newEl)
3136            spannerList.extend(st.spannerList)
3137
3138        if eRemain is not None:
3139            eList.append(eRemain)
3140
3141        stOut = _SplitTuple(eList)
3142        stOut.spannerList = spannerList
3143        return stOut
3144
3145    def splitAtDurations(self: _M21T) -> _SplitTuple:
3146        '''
3147        Takes a Music21Object (e.g., a note.Note) and returns a list of similar
3148        objects with only a single duration.DurationTuple in each.
3149        Ties are added if the object supports ties.
3150
3151        Articulations only appear on the first note.  Same with lyrics.
3152
3153        Fermatas should be on last note, but not done yet.
3154
3155        >>> a = note.Note()
3156        >>> a.duration.clear()  # remove defaults
3157        >>> a.duration.addDurationTuple(duration.durationTupleFromTypeDots('half', 0))
3158        >>> a.duration.quarterLength
3159        2.0
3160        >>> a.duration.addDurationTuple(duration.durationTupleFromTypeDots('whole', 0))
3161        >>> a.duration.quarterLength
3162        6.0
3163        >>> b = a.splitAtDurations()
3164        >>> b
3165        (<music21.note.Note C>, <music21.note.Note C>)
3166        >>> b[0].pitch == b[1].pitch
3167        True
3168        >>> b[0].duration
3169        <music21.duration.Duration 2.0>
3170        >>> b[0].duration.type
3171        'half'
3172        >>> b[1].duration.type
3173        'whole'
3174        >>> b[0].quarterLength, b[1].quarterLength
3175        (2.0, 4.0)
3176
3177        >>> c = note.Note()
3178        >>> c.quarterLength = 2.5
3179        >>> d, e = c.splitAtDurations()
3180        >>> d.duration.type
3181        'half'
3182        >>> e.duration.type
3183        'eighth'
3184        >>> d.tie.type
3185        'start'
3186        >>> print(e.tie)
3187        <music21.tie.Tie stop>
3188
3189        Assume c is tied to the next note.  Then the last split note should also be tied
3190
3191        >>> c.tie = tie.Tie('start')
3192        >>> d, e = c.splitAtDurations()
3193        >>> d.tie.type
3194        'start'
3195        >>> e.tie.type
3196        'continue'
3197
3198        Rests have no ties:
3199
3200        >>> f = note.Rest()
3201        >>> f.quarterLength = 2.5
3202        >>> g, h = f.splitAtDurations()
3203        >>> (g.duration.type, h.duration.type)
3204        ('half', 'eighth')
3205        >>> f.tie is None
3206        True
3207        >>> g.tie is None
3208        True
3209
3210
3211        It should work for complex notes with tuplets.
3212
3213        (this duration occurs in Modena A, Le greygnour bien, from the ars subtilior, c. 1380;
3214        hence how I discovered this bug)
3215
3216        >>> n = note.Note()
3217        >>> n.duration.quarterLength = 0.5 + 0.0625  # eighth + 64th
3218        >>> t = duration.Tuplet(4, 3)
3219        >>> n.duration.appendTuplet(t)
3220        >>> first, last = n.splitAtDurations()
3221        >>> (first.duration, last.duration)
3222        (<music21.duration.Duration 0.375>, <music21.duration.Duration 0.046875>)
3223
3224        Notice that this duration could have been done w/o tuplets, so no tuplets in output:
3225
3226        >>> (first.duration.type, first.duration.dots, first.duration.tuplets)
3227        ('16th', 1, ())
3228        >>> (last.duration.type, last.duration.dots, last.duration.tuplets)
3229        ('128th', 1, ())
3230
3231        Test of one with tuplets that cannot be split:
3232
3233        >>> n = note.Note()
3234        >>> n.duration.quarterLength = 0.5 + 0.0625  # eighth + 64th
3235        >>> t = duration.Tuplet(3, 2, 'eighth')
3236        >>> n.duration.appendTuplet(t)
3237        >>> (n.duration.type, n.duration.dots, n.duration.tuplets)
3238        ('complex', 0, (<music21.duration.Tuplet 3/2/eighth>,))
3239
3240        >>> first, last = n.splitAtDurations()
3241        >>> (first.duration, last.duration)
3242        (<music21.duration.Duration 1/3>, <music21.duration.Duration 1/24>)
3243
3244        >>> (first.duration.type, first.duration.dots, first.duration.tuplets)
3245        ('eighth', 0, (<music21.duration.Tuplet 3/2/eighth>,))
3246        >>> (last.duration.type, last.duration.dots, last.duration.tuplets)
3247        ('64th', 0, (<music21.duration.Tuplet 3/2/64th>,))
3248
3249
3250        TODO: unite this and other functions into a "split" function -- document obscure uses.
3251
3252        '''
3253        atm = self.duration.aggregateTupletMultiplier()
3254        quarterLengthList = [c.quarterLength * atm for c in self.duration.components]
3255        splitList = self.splitByQuarterLengths(quarterLengthList)
3256        return splitList
3257    # -------------------------------------------------------------------------
3258    # temporal and beat based positioning
3259
3260    @property
3261    def measureNumber(self) -> Optional[int]:
3262        # noinspection PyShadowingNames
3263        '''
3264        Return the measure number of a :class:`~music21.stream.Measure` that contains this
3265        object if the object is in a measure.
3266
3267        Returns None if the object is not in a measure.  Also note that by
3268        default Measure objects
3269        have measure number 0.
3270
3271        If an object belongs to multiple measures (not in the same hierarchy...)
3272        then it returns the
3273        measure number of the :meth:`~music21.base.Music21Object.activeSite` if that is a
3274        :class:`~music21.stream.Measure` object.  Otherwise it will use
3275        :meth:`~music21.base.Music21Object.getContextByClass`
3276        to find the number of the measure it was most recently added to.
3277
3278        >>> m = stream.Measure()
3279        >>> m.number = 12
3280        >>> n = note.Note()
3281        >>> m.append(n)
3282        >>> n.measureNumber
3283        12
3284
3285        >>> n2 = note.Note()
3286        >>> n2.measureNumber is None
3287        True
3288        >>> m2 = stream.Measure()
3289        >>> m2.append(n2)
3290        >>> n2.measureNumber
3291        0
3292
3293        This updates live if the measure number changes:
3294
3295        >>> m2.number = 11
3296        >>> n2.measureNumber
3297        11
3298
3299
3300        The most recent measure added to is used unless activeSite is a measure:
3301
3302        >>> m.append(n2)
3303        >>> n2.measureNumber
3304        12
3305        >>> n2.activeSite = m2
3306        >>> n2.measureNumber
3307        11
3308
3309        Copies can retain measure numbers until set themselves:
3310
3311        >>> import copy
3312        >>> nCopy = copy.deepcopy(n2)
3313        >>> nCopy.measureNumber
3314        12
3315        >>> m3 = stream.Measure()
3316        >>> m3.number = 4
3317        >>> m3.append(nCopy)
3318        >>> nCopy.measureNumber
3319        4
3320        '''
3321        mNumber = None  # default for not defined
3322        if self.activeSite is not None and self.activeSite.isMeasure:
3323            mNumber = self.activeSite.number
3324        else:
3325            # testing sortByCreationTime == true; this may be necessary
3326            # as we often want the most recent measure
3327            for cs in self.contextSites():
3328                m = cs[0]
3329                if m.isMeasure:
3330                    mNumber = m.number
3331        return mNumber
3332
3333    def _getMeasureOffset(self, includeMeasurePadding=True) -> Union[float, fractions.Fraction]:
3334        # noinspection PyShadowingNames
3335        '''
3336        Try to obtain the nearest Measure that contains this object,
3337        and return the offset of this object within that Measure.
3338
3339        If a Measure is found, and that Measure has padding
3340        defined as `paddingLeft` (for pickup measures, etc.), padding will be added to the
3341        native offset gathered from the object.
3342
3343        >>> n = note.Note()
3344        >>> n.quarterLength = 2
3345        >>> m = stream.Measure()
3346        >>> n._getMeasureOffset()  # returns zero when not assigned
3347        0.0
3348        >>> n.quarterLength = 0.5
3349
3350        >>> m = stream.Measure()
3351        >>> m.repeatAppend(n, 4)
3352        >>> [n._getMeasureOffset() for n in m.notes]
3353        [0.0, 0.5, 1.0, 1.5]
3354
3355        >>> m.paddingLeft = 2
3356        >>> [n._getMeasureOffset() for n in m.notes]
3357        [2.0, 2.5, 3.0, 3.5]
3358        >>> [n._getMeasureOffset(includeMeasurePadding=False) for n in m.notes]
3359        [0.0, 0.5, 1.0, 1.5]
3360        '''
3361        # TODO: v7 -- expose as public.
3362        activeS = self.activeSite
3363        if activeS is not None and activeS.isMeasure:
3364            # environLocal.printDebug(['found activeSite as Measure, using for offset'])
3365            offsetLocal = activeS.elementOffset(self)
3366            if includeMeasurePadding:
3367                offsetLocal += activeS.paddingLeft
3368        else:
3369            # environLocal.printDebug(['did not find activeSite as Measure,
3370            #    doing context search', 'self.activeSite', self.activeSite])
3371            # testing sortByCreationTime == true; this may be necessary
3372            # as we often want the most recent measure
3373            m = self.getContextByClass('Measure', sortByCreationTime=True)
3374            if m is not None:
3375                # environLocal.printDebug(['using found Measure for offset access'])
3376                try:
3377                    offsetLocal = m.elementOffset(self)
3378                    if includeMeasurePadding:
3379                        offsetLocal += m.paddingLeft
3380                except SitesException:
3381                    offsetLocal = self.offset
3382
3383            else:  # hope that we get the right one
3384                # environLocal.printDebug(
3385                #  ['_getMeasureOffset(): cannot find a Measure; using standard offset access'])
3386                offsetLocal = self.offset
3387
3388        # environLocal.printDebug(
3389        #     ['_getMeasureOffset(): found local offset as:', offsetLocal, self])
3390        return offsetLocal
3391
3392    def _getTimeSignatureForBeat(self) -> 'music21.meter.TimeSignature':
3393        '''
3394        used by all the _getBeat, _getBeatDuration, _getBeatStrength functions.
3395
3396        extracted to make sure that all three of the routines use the same one.
3397        '''
3398        ts: Optional['music21.meter.TimeSignature'] = self.getContextByClass(
3399            'TimeSignature', getElementMethod='getElementAtOrBeforeOffset')
3400        if ts is None:
3401            raise Music21ObjectException('this object does not have a TimeSignature in Sites')
3402        return ts
3403
3404    @property
3405    def beat(self) -> Union[fractions.Fraction, float]:
3406        # noinspection PyShadowingNames
3407        '''
3408        Return the beat of this object as found in the most
3409        recently positioned Measure. Beat values count from 1 and
3410        contain a floating-point designation between 0 and 1 to
3411        show proportional progress through the beat.
3412
3413        >>> n = note.Note()
3414        >>> n.quarterLength = 0.5
3415        >>> m = stream.Measure()
3416        >>> m.timeSignature = meter.TimeSignature('3/4')
3417        >>> m.repeatAppend(n, 6)
3418        >>> [n.beat for n in m.notes]
3419        [1.0, 1.5, 2.0, 2.5, 3.0, 3.5]
3420
3421
3422        Fractions are returned for positions that cannot be represented perfectly using floats:
3423
3424        >>> m.timeSignature = meter.TimeSignature('6/8')
3425        >>> [n.beat for n in m.notes]
3426        [1.0, Fraction(4, 3), Fraction(5, 3), 2.0, Fraction(7, 3), Fraction(8, 3)]
3427
3428        >>> s = stream.Stream()
3429        >>> s.insert(0, meter.TimeSignature('3/4'))
3430        >>> s.repeatAppend(note.Note(), 8)
3431        >>> [n.beat for n in s.notes]
3432        [1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0]
3433
3434
3435        Notes inside flat streams can still find the original beat placement from outer
3436        streams:
3437
3438        >>> p = stream.Part()
3439        >>> ts = meter.TimeSignature('2/4')
3440        >>> p.insert(0, ts)
3441
3442        >>> n = note.Note('C4', type='eighth')
3443        >>> m1 = stream.Measure(number=1)
3444        >>> m1.repeatAppend(n, 4)
3445
3446        >>> m2 = stream.Measure(number=2)
3447        >>> m2.repeatAppend(n, 4)
3448
3449        >>> p.append([m1, m2])
3450        >>> [n.beat for n in p.flatten().notes]
3451        [1.0, 1.5, 2.0, 2.5, 1.0, 1.5, 2.0, 2.5]
3452
3453
3454        Fractions print out as improper fraction strings
3455
3456        >>> m = stream.Measure()
3457        >>> m.timeSignature = meter.TimeSignature('4/4')
3458        >>> n = note.Note()
3459        >>> n.quarterLength = 1/3
3460        >>> m.repeatAppend(n, 12)
3461        >>> for n in m.notes[:5]:
3462        ...    print(n.beat)
3463        1.0
3464        4/3
3465        5/3
3466        2.0
3467        7/3
3468
3469        If there is no TimeSignature object in sites then returns the special float
3470        'nan' meaning "Not a Number":
3471
3472        >>> isolatedNote = note.Note('E4')
3473        >>> isolatedNote.beat
3474        nan
3475
3476        Not-a-number objects do not compare equal to themselves:
3477
3478        >>> isolatedNote.beat == isolatedNote.beat
3479        False
3480
3481        Instead to test for nan, import the math module and use `isnan()`:
3482
3483        >>> import math
3484        >>> math.isnan(isolatedNote.beat)
3485        True
3486
3487
3488        Changed in v.6.3 -- returns `nan` if
3489        there is no TimeSignature in sites.  Previously raised an exception.
3490        '''
3491        try:
3492            ts = self._getTimeSignatureForBeat()
3493            return ts.getBeatProportion(ts.getMeasureOffsetOrMeterModulusOffset(self))
3494        except Music21ObjectException:
3495            return float('nan')
3496
3497    @property
3498    def beatStr(self) -> str:
3499        '''
3500        Return a string representation of the beat of
3501        this object as found in the most recently positioned
3502        Measure. Beat values count from 1 and contain a
3503        fractional designation to show progress through the beat.
3504
3505
3506        >>> n = note.Note(type='eighth')
3507        >>> m = stream.Measure()
3508        >>> m.timeSignature = meter.TimeSignature('3/4')
3509        >>> m.repeatAppend(n, 6)
3510
3511        >>> [n.beatStr for n in m.notes]
3512        ['1', '1 1/2', '2', '2 1/2', '3', '3 1/2']
3513
3514        >>> m.timeSignature = meter.TimeSignature('6/8')
3515        >>> [n.beatStr for n in m.notes]
3516        ['1', '1 1/3', '1 2/3', '2', '2 1/3', '2 2/3']
3517
3518        >>> s = stream.Stream()
3519        >>> s.insert(0, meter.TimeSignature('3/4'))
3520        >>> s.repeatAppend(note.Note(type='quarter'), 8)
3521        >>> [n.beatStr for n in s.notes]
3522        ['1', '2', '3', '1', '2', '3', '1', '2']
3523
3524        If there is no TimeSignature object in sites then returns 'nan' for not a number.
3525
3526        >>> isolatedNote = note.Note('E4')
3527        >>> isolatedNote.beatStr
3528        'nan'
3529
3530        Changed in v.6.3 -- returns 'nan' if
3531        there is no TimeSignature in sites.  Previously raised an exception.
3532        '''
3533        try:
3534            ts = self._getTimeSignatureForBeat()
3535            return ts.getBeatProportionStr(ts.getMeasureOffsetOrMeterModulusOffset(self))
3536        except Music21ObjectException:
3537            return 'nan'
3538
3539    @property
3540    def beatDuration(self) -> 'music21.duration.Duration':
3541        '''
3542        Return a :class:`~music21.duration.Duration` of the beat
3543        active for this object as found in the most recently
3544        positioned Measure.
3545
3546        If extending beyond the Measure, or in a Stream with a TimeSignature,
3547        the meter modulus value will be returned.
3548
3549        >>> n = note.Note('C4', type='eighth')
3550        >>> n.duration
3551        <music21.duration.Duration 0.5>
3552
3553        >>> m = stream.Measure()
3554        >>> m.timeSignature = meter.TimeSignature('3/4')
3555        >>> m.repeatAppend(n, 6)
3556        >>> n0 = m.notes.first()
3557        >>> n0.beatDuration
3558        <music21.duration.Duration 1.0>
3559
3560        Notice that the beat duration is the same for all these notes
3561        and has nothing to do with the duration of the element itself
3562
3563        >>> [n.beatDuration.quarterLength for n in m.notes]
3564        [1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
3565
3566        Changing the time signature changes the beat duration:
3567
3568        >>> m.timeSignature = meter.TimeSignature('6/8')
3569        >>> [n.beatDuration.quarterLength for n in m.notes]
3570        [1.5, 1.5, 1.5, 1.5, 1.5, 1.5]
3571
3572        Complex time signatures will give different note lengths:
3573
3574        >>> s = stream.Stream()
3575        >>> s.insert(0, meter.TimeSignature('2/4+3/4'))
3576        >>> s.repeatAppend(note.Note(type='quarter'), 8)
3577        >>> [n.beatDuration.quarterLength for n in s.notes]
3578        [2.0, 2.0, 3.0, 3.0, 3.0, 2.0, 2.0, 3.0]
3579
3580
3581        If there is no TimeSignature object in sites then returns a duration object
3582        of Zero length.
3583
3584        >>> isolatedNote = note.Note('E4')
3585        >>> isolatedNote.beatDuration
3586        <music21.duration.Duration 0.0>
3587
3588        Changed in v.6.3 -- returns a duration.Duration object of length 0 if
3589        there is no TimeSignature in sites.  Previously raised an exception.
3590        '''
3591        try:
3592            ts = self._getTimeSignatureForBeat()
3593            return ts.getBeatDuration(ts.getMeasureOffsetOrMeterModulusOffset(self))
3594        except Music21ObjectException:
3595            return duration.Duration(0)
3596
3597    @property
3598    def beatStrength(self) -> float:
3599        '''
3600        Return the metrical accent of this object
3601        in the most recently positioned Measure. Accent values
3602        are between zero and one, and are derived from the local
3603        TimeSignature's accent MeterSequence weights. If the offset
3604        of this object does not match a defined accent weight, a
3605        minimum accent weight will be returned.
3606
3607
3608        >>> n = note.Note(type='eighth')
3609        >>> m = stream.Measure()
3610        >>> m.timeSignature = meter.TimeSignature('3/4')
3611        >>> m.repeatAppend(n, 6)
3612
3613        The first note of a measure is (generally?) always beat strength 1.0:
3614
3615        >>> m.notes.first().beatStrength
3616        1.0
3617
3618        Notes on weaker beats have lower strength:
3619
3620        >>> [n.beatStrength for n in m.notes]
3621        [1.0, 0.25, 0.5, 0.25, 0.5, 0.25]
3622
3623        >>> m.timeSignature = meter.TimeSignature('6/8')
3624        >>> [n.beatStrength for n in m.notes]
3625        [1.0, 0.25, 0.25, 0.5, 0.25, 0.25]
3626
3627
3628        Importantly, the actual numbers here have no particular meaning.  You cannot
3629        "add" two beatStrengths of 0.25 and say that they have the same beat strength
3630        as one note of 0.5.  Only the ordinal relations really matter.  Even taking
3631        an average of beat strengths is a tiny bit methodologically suspect (though
3632        it is common in research for lack of a better method).
3633
3634        We can also get the beatStrength for elements not in
3635        a measure, if the enclosing stream has a :class:`~music21.meter.TimeSignature`.
3636        We just assume that the time signature carries through to
3637        hypothetical following measures:
3638
3639        >>> n = note.Note('E-3', type='quarter')
3640        >>> s = stream.Stream()
3641        >>> s.insert(0.0, meter.TimeSignature('2/2'))
3642        >>> s.repeatAppend(n, 12)
3643        >>> [n.beatStrength for n in s.notes]
3644        [1.0, 0.25, 0.5, 0.25, 1.0, 0.25, 0.5, 0.25, 1.0, 0.25, 0.5, 0.25]
3645
3646
3647        Changing the meter changes the output, of course, as can be seen from the
3648        fourth quarter note onward:
3649
3650        >>> s.insert(4.0, meter.TimeSignature('3/4'))
3651        >>> [n.beatStrength for n in s.notes]
3652        [1.0, 0.25, 0.5, 0.25, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5]
3653
3654
3655        The method returns correct numbers for the prevailing time signature
3656        even if no measures have been made:
3657
3658        >>> n = note.Note('E--3', type='half')
3659        >>> s = stream.Stream()
3660        >>> s.isMeasure
3661        False
3662
3663        >>> s.insert(0, meter.TimeSignature('2/2'))
3664        >>> s.repeatAppend(n, 16)
3665        >>> s.notes[0].beatStrength
3666        1.0
3667        >>> s.notes[1].beatStrength
3668        0.5
3669        >>> s.notes[4].beatStrength
3670        1.0
3671        >>> s.notes[5].beatStrength
3672        0.5
3673
3674        Getting the beatStrength of an object without a time signature in its context
3675        returns the not-a-number special object 'nan':
3676
3677        >>> n2 = note.Note(type='whole')
3678        >>> n2.beatStrength
3679        nan
3680        >>> from math import isnan
3681        >>> isnan(n2.beatStrength)
3682        True
3683
3684        Changed in v6.3 -- return 'nan' instead of raising an exception.
3685        '''
3686        try:
3687            ts = self._getTimeSignatureForBeat()
3688            meterModulus = ts.getMeasureOffsetOrMeterModulusOffset(self)
3689
3690            return ts.getAccentWeight(meterModulus,
3691                                      forcePositionMatch=True,
3692                                      permitMeterModulus=False)
3693        except Music21ObjectException:
3694            return float('nan')
3695
3696    def _getSeconds(self) -> float:
3697        # do not search of duration is zero
3698        if self.duration.quarterLength == 0.0:
3699            return 0.0
3700
3701        ti = self.getContextByClass('TempoIndication')
3702        if ti is None:
3703            return float('nan')
3704        mm = ti.getSoundingMetronomeMark()
3705        # once we have mm, simply pass in this duration
3706        return mm.durationToSeconds(self.duration)
3707
3708    def _setSeconds(self, value: Union[int, float]) -> None:
3709        ti = self.getContextByClass('TempoIndication')
3710        if ti is None:
3711            raise Music21ObjectException('this object does not have a TempoIndication in Sites')
3712        mm = ti.getSoundingMetronomeMark()
3713        self.duration = mm.secondsToDuration(value)
3714        for s in self.sites.get(excludeNone=True):
3715            if self in s.elements:
3716                s.coreElementsChanged()  # highest time is changed.
3717
3718    seconds = property(_getSeconds, _setSeconds, doc='''
3719        Get or set the duration of this object in seconds, assuming
3720        that this object has a :class:`~music21.tempo.MetronomeMark`
3721        or :class:`~music21.tempo.MetricModulation`
3722        (or any :class:`~music21.tempo.TempoIndication`) in its past context.
3723
3724        >>> s = stream.Stream()
3725        >>> for i in range(3):
3726        ...    s.append(note.Note(type='quarter'))
3727        ...    s.append(note.Note(type='quarter', dots=1))
3728        >>> s.insert(0, tempo.MetronomeMark(number=60))
3729        >>> s.insert(2, tempo.MetronomeMark(number=120))
3730        >>> s.insert(4, tempo.MetronomeMark(number=30))
3731        >>> [n.seconds for n in s.notes]
3732        [1.0, 1.5, 0.5, 0.75, 2.0, 3.0]
3733
3734        Setting the number of seconds on a music21 object changes its duration:
3735
3736        >>> lastNote = s.notes[-1]
3737        >>> lastNote.duration.fullName
3738        'Dotted Quarter'
3739        >>> lastNote.seconds = 4.0
3740        >>> lastNote.duration.fullName
3741        'Half'
3742
3743        Any object of length 0 has zero-second length:
3744
3745        >>> tc = clef.TrebleClef()
3746        >>> tc.seconds
3747        0.0
3748
3749        If an object has positive duration but no tempo indication in its context,
3750        then the special number 'nan' for "not-a-number" is returned:
3751
3752        >>> r = note.Rest(type='whole')
3753        >>> r.seconds
3754        nan
3755
3756        Check for 'nan' with the `math.isnan()` routine:
3757
3758        >>> import math
3759        >>> math.isnan(r.seconds)
3760        True
3761
3762        Setting seconds for an element without a tempo-indication in its sites raises
3763        a Music21ObjectException:
3764
3765        >>> r.seconds = 2.0
3766        Traceback (most recent call last):
3767        music21.base.Music21ObjectException: this object does not have a TempoIndication in Sites
3768
3769        Note that if an object is in multiple Sites with multiple Metronome marks,
3770        the activeSite (or the hierarchy of the activeSite)
3771        determines its seconds for getting or setting:
3772
3773        >>> r = note.Rest(type='whole')
3774        >>> m1 = stream.Measure()
3775        >>> m1.insert(0, tempo.MetronomeMark(number=60))
3776        >>> m1.append(r)
3777        >>> r.seconds
3778        4.0
3779
3780        >>> m2 = stream.Measure()
3781        >>> m2.insert(0, tempo.MetronomeMark(number=120))
3782        >>> m2.append(r)
3783        >>> r.seconds
3784        2.0
3785        >>> r.activeSite = m1
3786        >>> r.seconds
3787        4.0
3788        >>> r.seconds = 1.0
3789        >>> r.duration.type
3790        'quarter'
3791        >>> r.activeSite = m2
3792        >>> r.seconds = 1.0
3793        >>> r.duration.type
3794        'half'
3795
3796        Changed in v6.3 -- return nan instead of raising an exception.
3797        ''')
3798
3799
3800# ------------------------------------------------------------------------------
3801
3802
3803class ElementWrapper(Music21Object):
3804    '''
3805    An ElementWrapper is a way of containing any object that is not a
3806    :class:`~music21.base.Music21Object`, so that that object can be positioned
3807    within a :class:`~music21.stream.Stream`.
3808
3809    The object stored within ElementWrapper is available from the
3810    :attr:`~music21.base.ElementWrapper.obj` attribute.  All the attributes of
3811    the stored object (except .id and anything else that conflicts with a
3812    Music21Object attribute) are gettable and settable by querying the
3813    ElementWrapper.  This feature makes it possible easily to mix
3814    Music21Objects and non-Music21Objects with similarly named attributes in
3815    the same Stream.
3816
3817    This example inserts 10 random wave files into a music21 Stream and then
3818    reports their filename and number of audio channels (in this example, it's
3819    always 2) if they fall on a strong beat in fast 6/8
3820
3821    >>> import music21
3822    >>> #_DOCS_SHOW import wave
3823    >>> import random
3824    >>> class Wave_read: #_DOCS_HIDE
3825    ...    def getnchannels(self): return 2 #_DOCS_HIDE
3826
3827    >>> s = stream.Stream()
3828    >>> s.id = 'mainStream'
3829    >>> s.append(meter.TimeSignature('fast 6/8'))
3830    >>> for i in range(10):
3831    ...    #_DOCS_SHOW fileName = 'thisSound_' + str(random.randint(1, 20)) + '.wav'
3832    ...    fileName = 'thisSound_' + str(1 + ((i * 100) % 19)) + '.wav' #_DOCS_HIDE
3833    ...    soundFile = Wave_read() #_DOCS_HIDE # #make a more predictable "random" set.
3834    ...    #_DOCS_SHOW soundFile = wave.open(fileName)
3835    ...    soundFile.fileName = fileName
3836    ...    el = music21.ElementWrapper(soundFile)
3837    ...    s.insert(i, el)
3838
3839    >>> for j in s.getElementsByClass('ElementWrapper'):
3840    ...    if j.beatStrength > 0.4:
3841    ...        (j.offset, j.beatStrength, j.getnchannels(), j.fileName)
3842    (0.0, 1.0, 2, 'thisSound_1.wav')
3843    (3.0, 1.0, 2, 'thisSound_16.wav')
3844    (6.0, 1.0, 2, 'thisSound_12.wav')
3845    (9.0, 1.0, 2, 'thisSound_8.wav')
3846    >>> for j in s.getElementsByClass('ElementWrapper'):
3847    ...    if j.beatStrength > 0.4:
3848    ...        (j.offset, j.beatStrength, j.getnchannels() + 1, j.fileName)
3849    (0.0, 1.0, 3, 'thisSound_1.wav')
3850    (3.0, 1.0, 3, 'thisSound_16.wav')
3851    (6.0, 1.0, 3, 'thisSound_12.wav')
3852    (9.0, 1.0, 3, 'thisSound_8.wav')
3853
3854    Test representation of an ElementWrapper
3855
3856    >>> for i, j in enumerate(s.getElementsByClass('ElementWrapper')):
3857    ...     if i == 2:
3858    ...         j.id = None
3859    ...     else:
3860    ...         j.id = str(i) + '_wrapper'
3861    ...     if i <=2:
3862    ...         print(j)
3863    <music21.base.ElementWrapper id=0_wrapper offset=0.0 obj='<...Wave_read object...'>
3864    <music21.base.ElementWrapper id=1_wrapper offset=1.0 obj='<...Wave_read object...'>
3865    <music21.base.ElementWrapper offset=2.0 obj='<...Wave_read object...>'>
3866    '''
3867    _id = None
3868    obj = None
3869
3870    _DOC_ORDER = ['obj']
3871    _DOC_ATTR = {
3872        'obj': 'The object this wrapper wraps. It should not be a Music21Object.',
3873    }
3874
3875    def __init__(self, obj=None):
3876        super().__init__()
3877        self.obj = obj  # object stored here
3878        # the unlinkedDuration is the duration that is inherited from
3879        # Music21Object
3880        # self._unlinkedDuration = None
3881
3882    # -------------------------------------------------------------------------
3883
3884    def _reprInternal(self):
3885        shortObj = (str(self.obj))[0:30]
3886        if len(str(self.obj)) > 30:
3887            shortObj += '...'
3888            if shortObj[0] == '<':
3889                shortObj += '>'
3890
3891        if self.id is not None:
3892            return f'id={self.id} offset={self.offset} obj={shortObj!r}'
3893        else:
3894            return f'offset={self.offset} obj={shortObj!r}'
3895
3896    def __eq__(self, other) -> bool:
3897        '''Test ElementWrapper equality
3898
3899        >>> import music21
3900        >>> n = note.Note('C#')
3901        >>> a = music21.ElementWrapper(n)
3902        >>> a.offset = 3.0
3903        >>> b = music21.ElementWrapper(n)
3904        >>> b.offset = 3.0
3905        >>> a == b
3906        True
3907        >>> a is not b
3908        True
3909        >>> c = music21.ElementWrapper(n)
3910        >>> c.offset = 2.0
3911        >>> c.offset
3912        2.0
3913        >>> a == c
3914        False
3915        '''
3916        for other_prop in ('obj', 'offset', 'priority', 'groups', 'activeSite', 'duration'):
3917            if not hasattr(other, other_prop):
3918                return False
3919
3920        if (self.obj == other.obj
3921                and self.offset == other.offset
3922                and self.priority == other.priority
3923                and self.groups == other.groups
3924                and self.duration == self.duration):
3925            return True
3926        else:
3927            return False
3928
3929    def __setattr__(self, name: str, value: Any) -> None:
3930        # environLocal.printDebug(['calling __setattr__ of ElementWrapper', name, value])
3931
3932        # if in the ElementWrapper already, set that first
3933        if name in self.__dict__:
3934            object.__setattr__(self, name, value)
3935
3936        # if not, change the attribute in the stored object
3937        storedObj = object.__getattribute__(self, 'obj')
3938        if (name not in ('offset', '_offset', '_activeSite')
3939                and storedObj is not None
3940                and hasattr(storedObj, name)):
3941            setattr(storedObj, name, value)
3942        # unless neither has the attribute, in which case add it to the ElementWrapper
3943        else:
3944            object.__setattr__(self, name, value)
3945
3946    def __getattr__(self, name: str) -> Any:
3947        '''This method is only called when __getattribute__() fails.
3948        Using this also avoids the potential recursion problems of subclassing
3949        __getattribute__()_
3950
3951        see: https://stackoverflow.com/questions/371753/python-using-getattribute-method
3952        for examples
3953        '''
3954        storedObj = Music21Object.__getattribute__(self, 'obj')
3955        if storedObj is None:
3956            raise AttributeError(f'Could not get attribute {name!r} in an object-less element')
3957        return object.__getattribute__(storedObj, name)
3958
3959    def isTwin(self, other: 'ElementWrapper') -> bool:
3960        '''
3961        A weaker form of equality.  a.isTwin(b) is true if
3962        a and b store either the same object OR objects that are equal.
3963        In other words, it is essentially the same object in a different context
3964
3965        >>> import copy
3966        >>> import music21
3967
3968        >>> aE = music21.ElementWrapper(obj='hello')
3969
3970        >>> bE = copy.copy(aE)
3971        >>> aE is bE
3972        False
3973        >>> aE == bE
3974        True
3975        >>> aE.isTwin(bE)
3976        True
3977
3978        >>> bE.offset = 14.0
3979        >>> bE.priority = -4
3980        >>> aE == bE
3981        False
3982        >>> aE.isTwin(bE)
3983        True
3984        '''
3985        if not hasattr(other, 'obj'):
3986            return False
3987
3988        if self.obj is other.obj or self.obj == other.obj:
3989            return True
3990        else:
3991            return False
3992
3993
3994# -----------------------------------------------------------------------------
3995
3996
3997class TestMock(Music21Object):
3998    pass
3999
4000
4001class Test(unittest.TestCase):
4002
4003    def testCopyAndDeepcopy(self):
4004        '''
4005        Test copying all objects defined in this module
4006        '''
4007        for part in sys.modules[self.__module__].__dict__:
4008            match = False
4009            for skip in ['_', '__', 'Test', 'Exception']:
4010                if part.startswith(skip) or part.endswith(skip):
4011                    match = True
4012            if match:
4013                continue
4014            name = getattr(sys.modules[self.__module__], part)
4015            # noinspection PyTypeChecker
4016            if callable(name) and not isinstance(name, types.FunctionType):
4017                try:  # see if obj can be made w/ args
4018                    obj = name()
4019                except TypeError:
4020                    continue
4021                try:
4022                    i = copy.copy(obj)
4023                    j = copy.deepcopy(obj)
4024                except TypeError as e:
4025                    self.fail(f'Could not copy {obj}: {e}')
4026
4027    def testM21ObjRepr(self):
4028        from music21 import base
4029        a = base.Music21Object()
4030        address = hex(id(a))
4031        self.assertEqual(repr(a), f'<music21.base.Music21Object object at {address}>')
4032
4033    def testObjectCreation(self):
4034        a = TestMock()
4035        a.groups.append('hello')
4036        a.id = 'hi'
4037        a.offset = 2.0
4038        self.assertEqual(a.offset, 2.0)
4039
4040    def testElementEquality(self):
4041        from music21 import note
4042        n = note.Note('F-')
4043        a = ElementWrapper(n)
4044        a.offset = 3.0
4045        c = ElementWrapper(n)
4046        c.offset = 3.0
4047        self.assertEqual(a, c)
4048        self.assertIsNot(a, c)
4049        b = ElementWrapper(n)
4050        b.offset = 2.0
4051        self.assertNotEqual(a, b)
4052
4053    def testNoteCreation(self):
4054        from music21 import note
4055        n = note.Note('A')
4056        n.offset = 1.0  # duration.Duration('quarter')
4057        n.groups.append('flute')
4058
4059        b = copy.deepcopy(n)
4060        b.offset = 2.0  # duration.Duration('half')
4061
4062        self.assertFalse(n is b)
4063        n.pitch.accidental = '-'
4064        self.assertEqual(b.name, 'A')
4065        self.assertEqual(n.offset, 1.0)
4066        self.assertEqual(b.offset, 2.0)
4067        n.groups[0] = 'bassoon'
4068        self.assertFalse('flute' in n.groups)
4069        self.assertTrue('flute' in b.groups)
4070
4071    def testOffsets(self):
4072        from music21 import note
4073        a = ElementWrapper(note.Note('A#'))
4074        a.offset = 23.0
4075        self.assertEqual(a.offset, 23.0)
4076
4077    def testObjectsAndElements(self):
4078        from music21 import note
4079        from music21 import stream
4080        note1 = note.Note('B-')
4081        note1.duration.type = 'whole'
4082        stream1 = stream.Stream()
4083        stream1.append(note1)
4084        unused_subStream = stream1.notes
4085
4086    def testM21BaseDeepcopy(self):
4087        '''
4088        Test copying
4089        '''
4090        a = Music21Object()
4091        a.id = 'test'
4092        b = copy.deepcopy(a)
4093        self.assertNotEqual(a, b)
4094        self.assertEqual(b.id, 'test')
4095
4096    def testM21BaseSites(self):
4097        '''
4098        Basic testing of M21 base object sites
4099        '''
4100        from music21 import stream
4101        from music21 import base  # self import needed.
4102        a = base.Music21Object()
4103        b = stream.Stream()
4104
4105        # storing a single offset does not add a Sites entry
4106        a.offset = 30
4107        # all offsets are store in locations
4108        self.assertEqual(len(a.sites), 1)
4109        self.assertEqual(a._naiveOffset, 30.0)
4110        self.assertEqual(a.offset, 30.0)
4111
4112        # assigning a activeSite directly  # v2.1. no longer allowed if not in site
4113        def assignActiveSite(aa, bb):
4114            aa.activeSite = bb
4115
4116        self.assertRaises(SitesException, assignActiveSite, a, b)
4117        # now we have two offsets in locations
4118        b.insert(a)
4119        self.assertEqual(len(a.sites), 2)
4120        self.assertEqual(a.activeSite, b)
4121
4122        a.offset = 40
4123        # still have activeSite
4124        self.assertEqual(a.activeSite, b)
4125        # now the offset returns the value for the current activeSite
4126        # b.setElementOffset(a, 40.0)
4127        self.assertEqual(a.offset, 40.0)
4128
4129        # assigning a activeSite to None
4130        a.activeSite = None
4131        # properly returns original offset
4132        self.assertEqual(a.offset, 30.0)
4133        # we still have two locations stored
4134        self.assertEqual(len(a.sites), 2)
4135        self.assertEqual(a.getOffsetBySite(b), 40.0)
4136
4137    def testM21BaseLocationsCopy(self):
4138        '''
4139        Basic testing of M21 base object
4140        '''
4141        from music21 import stream
4142        from music21 import base
4143        a = stream.Stream()
4144        a.id = 'a obj'
4145        b = base.Music21Object()
4146        b.id = 'b obj'
4147
4148        b.id = 'test'
4149        a.insert(0, b)
4150        c = copy.deepcopy(b)
4151        c.id = 'c obj'
4152
4153        # have two locations: None, and that set by assigning activeSite
4154        self.assertEqual(len(b.sites), 2)
4155        dummy = c.sites
4156        # c is in 1 site, None, because it is a copy of b
4157        self.assertEqual(len(c.sites), 1)
4158
4159    def testM21BaseLocationsCopyB(self):
4160        # the active site of a deepcopy should not be the same?
4161        # self.assertEqual(post[-1].activeSite, a)
4162        from music21 import stream
4163        from music21 import base
4164        a = stream.Stream()
4165        b = base.Music21Object()
4166        b.id = 'test'
4167        a.insert(30, b)
4168        b.activeSite = a
4169
4170        d = stream.Stream()
4171        self.assertEqual(b.activeSite, a)
4172        self.assertEqual(len(b.sites), 2)
4173        c = copy.deepcopy(b)
4174        self.assertIs(c.activeSite, None)
4175        d.insert(20, c)
4176        self.assertEqual(len(c.sites), 2)
4177
4178        # this works because the activeSite is being set on the object
4179        # the copied activeSite has been deepcopied, and cannot now be accessed
4180        # this fails! post[-1].getOffsetBySite(a)
4181
4182    def testSitesSearch(self):
4183        from music21 import note
4184        from music21 import stream
4185        from music21 import clef
4186
4187        n1 = note.Note('A')
4188        n2 = note.Note('B')
4189
4190        s1 = stream.Stream(id='s1')
4191        s1.insert(10, n1)
4192        s1.insert(100, n2)
4193
4194        c1 = clef.TrebleClef()
4195        c2 = clef.BassClef()
4196
4197        s2 = stream.Stream(id='s2')
4198        s2.insert(0, c1)
4199        s2.insert(100, c2)
4200        s2.insert(10, s1)  # placing s1 here should result in c2 being before n2
4201
4202        self.assertEqual(s1.getOffsetBySite(s2), 10)
4203        # make sure in the context of s1 things are as we expect
4204        self.assertEqual(s2.flatten().getElementAtOrBefore(0), c1)
4205        self.assertEqual(s2.flatten().getElementAtOrBefore(100), c2)
4206        self.assertEqual(s2.flatten().getElementAtOrBefore(20), n1)
4207        self.assertEqual(s2.flatten().getElementAtOrBefore(110), n2)
4208
4209        # note: we cannot do this
4210        #    self.assertEqual(s2.flatten().getOffsetBySite(n2), 110)
4211        # we can do this:
4212        self.assertEqual(n2.getOffsetBySite(s2.flatten()), 110)
4213
4214        # this seems more idiomatic
4215        self.assertEqual(s2.flatten().elementOffset(n2), 110)
4216
4217        # both notes can find the treble clef in the activeSite stream
4218        post = n1.getContextByClass(clef.TrebleClef)
4219        self.assertIsInstance(post, clef.TrebleClef)
4220
4221        post = n2.getContextByClass(clef.TrebleClef)
4222        self.assertIsInstance(post, clef.TrebleClef)
4223
4224        # n1 cannot find a bass clef because it is before the bass clef
4225        post = n1.getContextByClass(clef.BassClef)
4226        self.assertEqual(post, None)
4227
4228        # n2 can find a bass clef, due to its shifted position in s2
4229        post = n2.getContextByClass(clef.BassClef)
4230        self.assertIsInstance(post, clef.BassClef)
4231
4232    def testSitesMeasures(self):
4233        '''Can a measure determine the last Clef used?
4234        '''
4235        from music21 import corpus
4236        from music21 import clef
4237        from music21 import stream
4238        a = corpus.parse('bach/bwv324.xml')
4239        measures = a.parts[0].getElementsByClass('Measure').stream()  # measures of first part
4240
4241        # the activeSite of measures[1] is set to the new output stream
4242        self.assertEqual(measures[1].activeSite, measures)
4243        # the source Part should still be a context of this measure
4244        self.assertIn(a.parts[0], measures[1].sites)
4245
4246        # from the first measure, we can get the clef by using
4247        # getElementsByClass
4248        post = measures[0].getElementsByClass(clef.Clef)
4249        self.assertIsInstance(post[0], clef.TrebleClef)
4250
4251        # make sure we can find offset in a flat representation
4252        self.assertRaises(SitesException, a.parts[0].flatten().elementOffset, a.parts[0][3])
4253
4254        # for the second measure
4255        post = a.parts[0][3].getContextByClass(clef.Clef)
4256        self.assertIsInstance(post, clef.TrebleClef)
4257
4258        # for the second measure accessed from measures
4259        # we can get the clef, now that getContextByClass uses semiFlat
4260        post = measures[3].getContextByClass(clef.Clef)
4261        self.assertIsInstance(post, clef.TrebleClef)
4262
4263        # add the measure to a new stream
4264        newStream = stream.Stream()
4265        newStream.insert(0, measures[3])
4266        # all previous locations are still available as a context
4267        self.assertTrue(newStream in measures[3].sites)
4268        self.assertTrue(measures in measures[3].sites)
4269        self.assertTrue(a.parts[0] in measures[3].sites)
4270        # we can still access the clef through this measure on this
4271        # new stream
4272        post = newStream[0].getContextByClass(clef.Clef)
4273        self.assertTrue(isinstance(post, clef.TrebleClef), post)
4274
4275    def testSitesClef(self):
4276        from music21 import note
4277        from music21 import stream
4278        from music21 import clef
4279        sOuter = stream.Stream()
4280        sOuter.id = 'sOuter'
4281        sInner = stream.Stream()
4282        sInner.id = 'sInner'
4283
4284        n = note.Note()
4285        sInner.append(n)
4286        sOuter.append(sInner)
4287
4288        # append clef to outer stream
4289        altoClef = clef.AltoClef()
4290        altoClef.priority = -1
4291        sOuter.insert(0, altoClef)
4292        pre = sOuter.getElementAtOrBefore(0, [clef.Clef])
4293        self.assertTrue(isinstance(pre, clef.AltoClef), pre)
4294
4295        # we should be able to find a clef from the lower-level stream
4296        post = sInner.getContextByClass(clef.Clef)
4297        self.assertTrue(isinstance(post, clef.AltoClef), post)
4298
4299    def testBeatAccess(self):
4300        '''Test getting beat data from various Music21Objects.
4301        '''
4302        from music21 import corpus
4303        s = corpus.parse('bach/bwv66.6.xml')
4304        p1 = s.parts['Soprano']
4305
4306        # this does not work; cannot get these values from Measures
4307        #    self.assertEqual(p1.getElementsByClass('Measure')[3].beat, 3)
4308
4309        # clef/ks can get its beat; these objects are in a pickup,
4310        # and this give their bar offset relative to the bar
4311        eClef = p1.flatten().getElementsByClass('Clef').first()
4312        self.assertEqual(eClef.beat, 4.0)
4313        self.assertEqual(eClef.beatDuration.quarterLength, 1.0)
4314        self.assertEqual(eClef.beatStrength, 0.25)
4315
4316        eKS = p1.flatten().getElementsByClass('KeySignature').first()
4317        self.assertEqual(eKS.beat, 4.0)
4318        self.assertEqual(eKS.beatDuration.quarterLength, 1.0)
4319        self.assertEqual(eKS.beatStrength, 0.25)
4320
4321        # ts can get beatStrength, beatDuration
4322        eTS = p1.flatten().getElementsByClass('TimeSignature').first()
4323        self.assertEqual(eTS.beatDuration.quarterLength, 1.0)
4324        self.assertEqual(eTS.beatStrength, 0.25)
4325
4326        # compare offsets found with items positioned in Measures
4327        # as the first bar is a pickup, the measure offset here is returned
4328        # with padding (resulting in 3)
4329        post = []
4330        for n in p1.flatten().notesAndRests:
4331            post.append(n._getMeasureOffset())
4332        self.assertEqual(post, [3.0, 3.5, 0.0, 1.0, 2.0, 3.0, 0.0,
4333                                1.0, 2.0, 3.0, 0.0, 0.5, 1.0, 2.0,
4334                                3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0,
4335                                2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0,
4336                                1.0, 2.0, 0.0, 2.0, 3.0, 0.0, 1.0, 1.5, 2.0])
4337
4338        # compare derived beat string
4339        post = []
4340        for n in p1.flatten().notesAndRests:
4341            post.append(n.beatStr)
4342        self.assertEqual(post, ['4', '4 1/2', '1', '2', '3', '4', '1',
4343                                '2', '3', '4', '1', '1 1/2', '2', '3',
4344                                '4', '1', '2', '3', '4', '1', '2', '3',
4345                                '4', '1', '2', '3', '4', '1', '2', '3',
4346                                '1', '3', '4', '1', '2', '2 1/2', '3'])
4347
4348        # for stream and Stream subclass, overridden methods not yet
4349        # specialized
4350        # _getMeasureOffset gets the offset within the activeSite
4351        # this shows that measure offsets are accommodating pickup
4352        post = []
4353        for m in p1.getElementsByClass('Measure'):
4354            post.append(m._getMeasureOffset())
4355        self.assertEqual(post, [0.0, 1.0, 5.0, 9.0, 13.0, 17.0, 21.0, 25.0, 29.0, 33.0])
4356
4357        # all other methods define None
4358        post = []
4359        for n in p1.getElementsByClass('Measure'):
4360            post.append(n.beat)
4361        self.assertEqual(post, [None, None, None, None, None, None, None, None, None, None])
4362
4363        post = []
4364        for n in p1.getElementsByClass('Measure'):
4365            post.append(n.beatStr)
4366        self.assertEqual(post, [None, None, None, None, None, None, None, None, None, None])
4367
4368        post = []
4369        for n in p1.getElementsByClass('Measure'):
4370            post.append(n.beatDuration)
4371        self.assertEqual(post, [None, None, None, None, None, None, None, None, None, None])
4372
4373    def testGetBeatStrengthA(self):
4374        from music21 import stream
4375        from music21 import note
4376        from music21 import meter
4377
4378        n = note.Note('g')
4379        n.quarterLength = 1
4380        s = stream.Stream()
4381        s.insert(0, meter.TimeSignature('4/4'))
4382        s.repeatAppend(n, 8)
4383        # match = []
4384        self.assertEqual([e.beatStrength for e in s.notes],
4385                         [1.0, 0.25, 0.5, 0.25, 1.0, 0.25, 0.5, 0.25])
4386
4387        n = note.Note('E--3', type='quarter')
4388        s = stream.Stream()
4389        s.insert(0.0, meter.TimeSignature('2/2'))
4390        s.repeatAppend(n, 12)
4391        match = [s.notes[i].beatStrength for i in range(12)]
4392        self.assertEqual([1.0, 0.25, 0.5, 0.25, 1.0, 0.25, 0.5, 0.25, 1.0, 0.25, 0.5, 0.25], match)
4393
4394    def testMeasureNumberAccess(self):
4395        '''Test getting measure number data from various Music21Objects.
4396        '''
4397        from music21 import corpus
4398        from music21 import stream
4399        from music21 import note
4400
4401        s = corpus.parse('bach/bwv66.6.xml')
4402        p1 = s.parts['Soprano']
4403        for classStr in ['Clef', 'KeySignature', 'TimeSignature']:
4404            self.assertEqual(p1.flatten().getElementsByClass(
4405                classStr)[0].measureNumber, 0)
4406
4407        match = []
4408        for n in p1.flatten().notesAndRests:
4409            match.append(n.measureNumber)
4410        self.assertEqual(match, [0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3,
4411                                 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7,
4412                                 8, 8, 8, 9, 9, 9, 9])
4413
4414        # create a note and put it in different measures
4415        m1 = stream.Measure()
4416        m1.number = 3
4417        m2 = stream.Measure()
4418        m2.number = 74
4419        n = note.Note()
4420        self.assertEqual(n.measureNumber, None)  # not in a Measure
4421        m1.append(n)
4422        self.assertEqual(n.measureNumber, 3)
4423        m2.append(n)
4424        self.assertEqual(n.measureNumber, 74)
4425
4426    def testPickupMeasuresBuilt(self):
4427        from music21 import stream
4428        from music21 import meter
4429        from music21 import note
4430
4431        s = stream.Score()
4432
4433        m1 = stream.Measure()
4434        m1.timeSignature = meter.TimeSignature('4/4')
4435        n1 = note.Note('d2')
4436        n1.quarterLength = 1.0
4437        m1.append(n1)
4438        # barDuration is based only on TS
4439        self.assertEqual(m1.barDuration.quarterLength, 4.0)
4440        # duration shows the highest offset in the bar
4441        self.assertEqual(m1.duration.quarterLength, 1.0)
4442        # presently, the offset of the added note is zero
4443        self.assertEqual(n1.getOffsetBySite(m1), 0.0)
4444        # the _getMeasureOffset method is called by all methods that evaluate
4445        # beat position; this takes padding into account
4446        self.assertEqual(n1._getMeasureOffset(), 0.0)
4447        self.assertEqual(n1.beat, 1.0)
4448
4449        # the Measure.padAsAnacrusis() method looks at the barDuration and,
4450        # if the Measure is incomplete, assumes its an anacrusis and adds
4451        # the appropriate padding
4452        m1.padAsAnacrusis()
4453        # app values are the same except _getMeasureOffset()
4454        self.assertEqual(m1.barDuration.quarterLength, 4.0)
4455        self.assertEqual(m1.duration.quarterLength, 1.0)
4456        self.assertEqual(n1.getOffsetBySite(m1), 0.0)
4457        # lowest offset inside of Measure still returns 0
4458        self.assertEqual(m1.lowestOffset, 0.0)
4459        # these values are now different
4460        self.assertEqual(n1._getMeasureOffset(), 3.0)
4461        self.assertEqual(n1.beat, 4.0)
4462
4463        # appending this measure to the Score
4464        s.append(m1)
4465        # score duration is correct: 1
4466        self.assertEqual(s.duration.quarterLength, 1.0)
4467        # lowest offset is that of the first bar
4468        self.assertEqual(s.lowestOffset, 0.0)
4469        self.assertEqual(s.highestTime, 1.0)
4470
4471        m2 = stream.Measure()
4472        n2 = note.Note('e2')
4473        n2.quarterLength = 4.0
4474        m2.append(n2)
4475        # based on contents
4476        self.assertEqual(m2.duration.quarterLength, 4.0)
4477        # we cannot get a bar duration b/c we have not associated a ts
4478        try:
4479            m2.barDuration.quarterLength
4480        except exceptions21.StreamException:
4481            pass
4482
4483        # append to Score
4484        s.append(m2)
4485        # m2 can now find a time signature by looking to activeSite stream
4486        self.assertEqual(m2.duration.quarterLength, 4.0)
4487        # highest time of score takes into account new measure
4488        self.assertEqual(s.highestTime, 5.0)
4489        # offset are contiguous when accessed in a flat form
4490        self.assertEqual([n.offset for n in s.flatten().notesAndRests], [0.0, 1.0])
4491
4492        m3 = stream.Measure()
4493        n3 = note.Note('f#2')
4494        n3.quarterLength = 3.0
4495        m3.append(n3)
4496
4497        # add to stream
4498        s.append(m3)
4499        # m3 can now find a time signature by looking to activeSite stream
4500        self.assertEqual(m2.duration.quarterLength, 4.0)
4501        # highest time of score takes into account new measure
4502        self.assertEqual(s.highestTime, 8.0)
4503        # offset are contiguous when accessed in a flat form
4504        self.assertEqual([n.offset for n in s.flatten().notesAndRests], [0.0, 1.0, 5.0])
4505
4506    def testPickupMeasuresImported(self):
4507        from music21 import corpus
4508        self.maxDiff = None
4509        s = corpus.parse('bach/bwv103.6')
4510
4511        p = s.parts['soprano']
4512        m1 = p.getElementsByClass('Measure').first()
4513
4514        self.assertEqual([n.offset for n in m1.notesAndRests], [0.0, 0.5])
4515        self.assertEqual(m1.paddingLeft, 3.0)
4516
4517        offsets = [n.offset for n in p.flatten().notesAndRests]
4518        # offsets for flat representation have proper spacing
4519        self.assertEqual(offsets,
4520                         [0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
4521                          9.0, 10.0, 11.0, 12.0, 12.5, 13.0, 15.0, 16.0, 17.0,
4522                          18.0, 18.5, 18.75, 19.0, 19.5, 20.0, 21.0, 22.0, 23.0, 24.0,
4523                          25.0, 26.0, 27.0, 28.0, 29.0, 31.0, 32.0, 32.5, 33.0, 34.0,
4524                          35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0,
4525                          43.0, 44.0, 44.5, 45.0, 47.0])
4526
4527    def testHighestTime(self):
4528        from music21 import stream
4529        from music21 import note
4530        from music21 import bar
4531        s = stream.Stream()
4532        n1 = note.Note()
4533        n1.quarterLength = 30
4534        n2 = note.Note()
4535        n2.quarterLength = 20
4536
4537        b1 = bar.Barline()
4538        s.append(n1)
4539        self.assertEqual(s.highestTime, 30.0)
4540        s.coreSetElementOffset(b1, OffsetSpecial.AT_END, addElement=True)
4541
4542        self.assertEqual(b1.getOffsetBySite(s), 30.0)
4543
4544        s.append(n2)
4545        self.assertEqual(s.highestTime, 50.0)
4546        self.assertEqual(b1.getOffsetBySite(s), 50.0)
4547
4548    def testRecurseByClass(self):
4549        from music21 import note
4550        from music21 import stream
4551        from music21 import clef
4552        s1 = stream.Stream()
4553        s2 = stream.Stream()
4554        s3 = stream.Stream()
4555
4556        n1 = note.Note('C')
4557        n2 = note.Note('D')
4558        n3 = note.Note('E')
4559        c1 = clef.TrebleClef()
4560        c2 = clef.BassClef()
4561        c3 = clef.PercussionClef()
4562
4563        s1.append(c1)
4564        s1.append(n1)
4565
4566        s2.append(c2)
4567        s2.append(n2)
4568
4569        s3.append(c3)
4570        s3.append(n3)
4571
4572        # only get n1 here, as that is only level available
4573        self.assertEqual(s1.recurse().getElementsByClass('Note').first(), n1)
4574        self.assertEqual(s2.recurse().getElementsByClass('Note').first(), n2)
4575        self.assertEqual(s1.recurse().getElementsByClass('Clef').first(), c1)
4576        self.assertEqual(s2.recurse().getElementsByClass('Clef').first(), c2)
4577
4578        # attach s2 to s1
4579        s2.append(s1)
4580        # stream 1 gets both notes
4581        self.assertEqual(list(s2.recurse().getElementsByClass('Note')), [n2, n1])
4582
4583    def testSetEditorial(self):
4584        b2 = Music21Object()
4585        self.assertIsNone(b2._editorial)
4586        b2_ed = b2.editorial
4587        self.assertIsInstance(b2_ed, editorial.Editorial)
4588        self.assertIsNotNone(b2._editorial)
4589
4590    def testStoreLastDeepCopyOf(self):
4591        from music21 import note
4592
4593        n1 = note.Note()
4594        n2 = copy.deepcopy(n1)
4595        self.assertEqual(id(n2.derivation.origin), id(n1))
4596
4597    def testHasElement(self):
4598        from music21 import note
4599        from music21 import stream
4600        n1 = note.Note()
4601        s1 = stream.Stream()
4602        s1.append(n1)
4603        s2 = copy.deepcopy(s1)
4604        n2 = s2[0]  # this is a new instance; not the same as n1
4605        self.assertFalse(s2.hasElement(n1))
4606        self.assertTrue(s2.hasElement(n2))
4607
4608        self.assertFalse(s1 in n2.sites)
4609        self.assertTrue(s2 in n2.sites)
4610
4611    def testGetContextByClassA(self):
4612        from music21 import stream
4613        from music21 import note
4614        from music21 import tempo
4615
4616        p = stream.Part()
4617        m1 = stream.Measure()
4618        m1.repeatAppend(note.Note(quarterLength=1), 4)
4619        m2 = copy.deepcopy(m1)
4620        mm1 = tempo.MetronomeMark(number=50, referent=0.25)
4621        m1.insert(0, mm1)
4622        mm2 = tempo.MetronomeMark(number=150, referent=0.5)
4623        m2.insert(0, mm2)
4624        p.append([m1, m2])
4625        # p.show('t')
4626        # if done with default args, we get the same object, as we are using
4627        # getElementAtOrBefore
4628        self.assertEqual(str(mm2.getContextByClass('MetronomeMark')),
4629                         '<music21.tempo.MetronomeMark Eighth=150>')
4630        # if we provide the getElementMethod parameter, we can use
4631        # getElementBeforeOffset
4632        self.assertEqual(str(mm2.getContextByClass('MetronomeMark',
4633                                                   getElementMethod='getElementBeforeOffset')),
4634                         '<music21.tempo.MetronomeMark lento 16th=50>')
4635
4636    def testElementWrapperOffsetAccess(self):
4637        from music21 import stream
4638        from music21 import meter
4639        from music21 import base
4640
4641        class Mock:
4642            pass
4643
4644        s = stream.Stream()
4645        s.append(meter.TimeSignature('fast 6/8'))
4646        storage = []
4647        for i in range(2):
4648            mock = Mock()
4649            el = base.ElementWrapper(mock)
4650            storage.append(el)
4651            s.insert(i, el)
4652
4653        for ew in storage:
4654            self.assertTrue(s.hasElement(ew))
4655
4656        match = [e.getOffsetBySite(s) for e in storage]
4657        self.assertEqual(match, [0.0, 1.0])
4658
4659        self.assertEqual(s.elementOffset(storage[0]), 0.0)
4660        self.assertEqual(s.elementOffset(storage[1]), 1.0)
4661
4662    def testGetActiveSiteTimeSignature(self):
4663        from music21 import base
4664        from music21 import stream
4665        from music21 import meter
4666
4667        class Wave_read:
4668            def getnchannels(self):
4669                return 2
4670
4671        s = stream.Stream()
4672        s.append(meter.TimeSignature('fast 6/8'))
4673        # s.show('t')
4674        storage = []
4675        for i in range(6):
4676            soundFile = Wave_read()  # _DOCS_HIDE
4677            # el = music21.Music21Object()
4678            el = base.ElementWrapper(soundFile)
4679            storage.append(el)
4680            self.assertEqual(el.obj, soundFile)
4681            s.insert(i, el)
4682
4683        for ew in storage:
4684            self.assertTrue(s.hasElement(ew))
4685
4686        matchOffset = []
4687        matchBeatStrength = []
4688        matchAudioChannels = []
4689
4690        for j in s.getElementsByClass('ElementWrapper'):
4691            matchOffset.append(j.offset)
4692            matchBeatStrength.append(j.beatStrength)
4693            matchAudioChannels.append(j.getnchannels())
4694        self.assertEqual(matchOffset, [0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
4695        self.assertEqual(matchBeatStrength, [1.0, 0.25, 0.25, 1.0, 0.25, 0.25])
4696        self.assertEqual(matchAudioChannels, [2, 2, 2, 2, 2, 2])
4697
4698    def testGetMeasureOffsetOrMeterModulusOffsetA(self):
4699        # test getting metric position in a Stream with a TS
4700        from music21 import stream
4701        from music21 import note
4702        from music21 import meter
4703
4704        s = stream.Stream()
4705        s.repeatAppend(note.Note(), 12)
4706        s.insert(0, meter.TimeSignature('3/4'))
4707
4708        match = [n.beat for n in s.notes]
4709        self.assertEqual(match, [1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0])
4710
4711        match = [n.beatStr for n in s.notes]
4712        self.assertEqual(match, ['1', '2', '3', '1', '2', '3', '1', '2', '3', '1', '2', '3'])
4713
4714        match = [n.beatDuration.quarterLength for n in s.notes]
4715        self.assertEqual(match, [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
4716
4717        match = [n.beatStrength for n in s.notes]
4718        self.assertEqual(match, [1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5])
4719
4720    def testGetMeasureOffsetOrMeterModulusOffsetB(self):
4721        from music21 import stream
4722        from music21 import note
4723        from music21 import meter
4724
4725        s = stream.Stream()
4726        s.repeatAppend(note.Note(), 12)
4727        s.insert(0.0, meter.TimeSignature('3/4'))
4728        s.insert(3.0, meter.TimeSignature('4/4'))
4729        s.insert(7.0, meter.TimeSignature('2/4'))
4730
4731        match = [n.beat for n in s.notes]
4732        self.assertEqual(match, [1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 1.0, 2.0, 1.0])
4733
4734        match = [n.beatStr for n in s.notes]
4735        self.assertEqual(match, ['1', '2', '3', '1', '2', '3', '4', '1', '2', '1', '2', '1'])
4736
4737        match = [n.beatStrength for n in s.notes]
4738        self.assertEqual(match, [1.0, 0.5, 0.5, 1.0, 0.25, 0.5, 0.25, 1.0, 0.5, 1.0, 0.5, 1.0])
4739
4740    def testSecondsPropertyA(self):
4741        from music21 import stream
4742        from music21 import note
4743        from music21 import tempo
4744        s = stream.Stream()
4745        s.repeatAppend(note.Note(), 12)
4746        s.insert(0, tempo.MetronomeMark(number=120))
4747
4748        self.assertEqual([n.seconds for n in s.notes],
4749                         [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])
4750
4751        # changing tempo mid-stream
4752        s.insert(6, tempo.MetronomeMark(number=240))
4753        self.assertEqual([n.seconds for n in s.notes],
4754                         [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25])
4755
4756        # adding notes based on seconds
4757        s2 = stream.Stream()
4758        s2.insert(0, tempo.MetronomeMark(number=120))
4759        s2.append(note.Note())
4760        s2.notes.first().seconds = 2.0
4761        self.assertEqual(s2.notes.first().quarterLength, 4.0)
4762        self.assertEqual(s2.duration.quarterLength, 4.0)
4763
4764        s2.append(note.Note('C4', type='half'))
4765        s2.notes[1].seconds = 0.5
4766        self.assertEqual(s2.notes[1].quarterLength, 1.0)
4767        self.assertEqual(s2.duration.quarterLength, 5.0)
4768
4769        s2.append(tempo.MetronomeMark(number=30))
4770        s2.append(note.Note())
4771        s2.notes[2].seconds = 0.5
4772        self.assertEqual(s2.notes[2].quarterLength, 0.25)
4773        self.assertEqual(s2.duration.quarterLength, 5.25)
4774
4775    def testGetContextByClass2015(self):
4776        from music21 import converter
4777        p = converter.parse('tinynotation: 3/4 C4 D E 2/4 F G A B 1/4 c')
4778        b = p.measure(3).notes[-1]
4779        c = b.getContextByClass('Note', getElementMethod='getElementAfterOffset')
4780        self.assertEqual(c.name, 'C')
4781
4782        m = p.measure(1)
4783        self.assertIsNotNone(m.getContextByClass('Clef', getElementMethod='all'))
4784
4785    def testGetContextByClassB(self):
4786        from music21 import stream
4787        from music21 import note
4788        from music21 import meter
4789
4790        s = stream.Score()
4791
4792        p1 = stream.Part()
4793        m1 = stream.Measure()
4794        m1.repeatAppend(note.Note(), 3)
4795        m1.timeSignature = meter.TimeSignature('3/4')
4796        m2 = stream.Measure()
4797        m2.repeatAppend(note.Note(), 3)
4798        p1.append(m1)
4799        p1.append(m2)
4800
4801        p2 = stream.Part()
4802        m3 = stream.Measure()
4803        m3.timeSignature = meter.TimeSignature('3/4')
4804        m3.repeatAppend(note.Note(), 3)
4805        m4 = stream.Measure()
4806        m4.repeatAppend(note.Note(), 3)
4807        p2.append(m3)
4808        p2.append(m4)
4809
4810        s.insert(0, p1)
4811        s.insert(0, p2)
4812
4813        p3 = stream.Part()
4814        m5 = stream.Measure()
4815        m5.timeSignature = meter.TimeSignature('3/4')
4816        m5.repeatAppend(note.Note(), 3)
4817        m6 = stream.Measure()
4818        m6.repeatAppend(note.Note(), 3)
4819        p3.append(m5)
4820        p3.append(m6)
4821
4822        p4 = stream.Part()
4823        m7 = stream.Measure()
4824        m7.timeSignature = meter.TimeSignature('3/4')
4825        m7.repeatAppend(note.Note(), 3)
4826        m8 = stream.Measure()
4827        m8.repeatAppend(note.Note(), 3)
4828        p4.append(m7)
4829        p4.append(m8)
4830
4831        s.insert(0, p3)
4832        s.insert(0, p4)
4833
4834        # self.targetMeasures = m4
4835        # n1 = m2[-1]  # last element is a note
4836        n2 = m4[-1]  # last element is a note
4837
4838        # environLocal.printDebug(['getContextByClass()'])
4839        # self.assertEqual(str(n1.getContextByClass('TimeSignature')),
4840        #    '<music21.meter.TimeSignature 3/4>')
4841        environLocal.printDebug(['getContextByClass()'])
4842        self.assertEqual(str(n2.getContextByClass('TimeSignature')),
4843                         '<music21.meter.TimeSignature 3/4>')
4844
4845    def testNextA(self):
4846        from music21 import stream
4847        from music21 import scale
4848        from music21 import note
4849        s = stream.Stream()
4850        sc = scale.MajorScale()
4851        notes = []
4852        for i in sc.pitches:
4853            n = note.Note()
4854            s.append(n)
4855            notes.append(n)  # keep for reference and testing
4856
4857        self.assertEqual(notes[0], s[0])  # leave as get index query.
4858        s0Next = s[0].next()
4859        self.assertEqual(notes[1], s0Next)
4860        self.assertEqual(notes[0], s[1].previous())
4861
4862        self.assertEqual(id(notes[5]), id(s[4].next()))
4863        self.assertEqual(id(notes[3]), id(s[4].previous()))
4864
4865        # if a note has more than one site, what happens
4866        self.assertEqual(notes[6], s.notes[5].next())
4867        self.assertEqual(notes[7], s.notes[6].next())
4868
4869    def testNextB(self):
4870        from music21 import stream
4871        from music21 import note
4872
4873        m1 = stream.Measure()
4874        m1.number = 1
4875        n1 = note.Note('C')
4876        m1.append(n1)
4877
4878        m2 = stream.Measure()
4879        m2.number = 2
4880        n2 = note.Note('D')
4881        m2.append(n2)
4882
4883        # n1 cannot be connected to n2 as no common site
4884        n1next = n1.next()
4885        self.assertEqual(n1next, None)
4886
4887        p1 = stream.Part()
4888        p1.append(m1)
4889        p1.append(m2)
4890        n1next = n1.next()
4891        self.assertEqual(n1next, m2)
4892        self.assertEqual(n1.next('Note'), n2)
4893
4894    def testNextC(self):
4895        from music21 import corpus
4896        s = corpus.parse('bwv66.6')
4897
4898        # getting time signature and key sig
4899        p1 = s.parts[0]
4900        nLast = p1.flatten().notes[-1]
4901        self.assertEqual(str(nLast.previous('TimeSignature')),
4902                         '<music21.meter.TimeSignature 4/4>')
4903        self.assertEqual(str(nLast.previous('KeySignature')),
4904                         'f# minor')
4905
4906        # iterating at the Measure level, showing usage of flattenLocalSites
4907        measures = p1.getElementsByClass('Measure').stream()
4908        m3 = measures[3]
4909        m3prev = m3.previous()
4910        self.assertEqual(m3prev, measures[2][-1])
4911        m3Prev2 = measures[3].previous().previous()
4912        self.assertEqual(m3Prev2, measures[2][-2])
4913        self.assertTrue(m3Prev2.expressions)  # fermata
4914
4915        m3n = measures[3].next()
4916        self.assertEqual(m3n, measures[3][0])  # same as next line
4917        self.assertEqual(measures[3].next('Note'), measures[3].notes[0])
4918        self.assertEqual(m3n.next(), measures[3][1])
4919
4920        m3nm = m3.next('Measure')
4921        m3nn = m3nm.next('Measure')
4922        self.assertEqual(m3nn, measures[5])
4923        m3nnn = m3nn.next('Measure')
4924        self.assertEqual(m3nnn, measures[6])
4925
4926        self.assertEqual(measures[3].previous('Measure').previous('Measure'), measures[1])
4927        m0viaPrev = measures[3].previous('Measure').previous('Measure').previous('Measure')
4928        self.assertEqual(m0viaPrev, measures[0])
4929
4930        m0viaPrev.activeSite = s.parts[0]  # otherwise there are no instruments...
4931        sopranoInst = m0viaPrev.previous()
4932        self.assertEqual(str(sopranoInst), 'P1: Soprano: Instrument 1')
4933
4934        # set active site back to measure stream...
4935        self.assertEqual(str(measures[0].previous()), str(p1))
4936
4937    def testActiveSiteCopyingA(self):
4938        from music21 import note
4939        from music21 import stream
4940
4941        n1 = note.Note()
4942        s1 = stream.Stream()
4943        s1.append(n1)
4944        self.assertEqual(n1.activeSite, s1)
4945
4946        n2 = copy.deepcopy(n1)
4947        self.assertIs(n2._activeSite, None)
4948        self.assertIs(n2.derivation.origin.activeSite, s1)
4949
4950    def testSpannerSites(self):
4951        from music21 import note
4952        from music21 import spanner
4953        from music21 import dynamics
4954
4955        n1 = note.Note('C4')
4956        n2 = note.Note('D4')
4957        sp1 = spanner.Slur(n1, n2)
4958        ss = n1.getSpannerSites()
4959        self.assertEqual(ss, [sp1])
4960
4961        # test same for inherited classes and multiple sites, in order...
4962        sp2 = dynamics.Crescendo(n2, n1)
4963        # can return in arbitrary order esp. if speed is fast...
4964        # TODO: use Ordered Dict.
4965        self.assertEqual(set(n2.getSpannerSites()), {sp1, sp2})
4966
4967        # Optionally a class name or list of class names can be
4968        # specified and only Spanners of that class will be returned
4969
4970        sp3 = dynamics.Diminuendo(n1, n2)
4971        self.assertEqual(n2.getSpannerSites('Diminuendo'), [sp3])
4972
4973        # A larger class name can be used to get all subclasses:
4974
4975        self.assertEqual(set(n2.getSpannerSites('DynamicWedge')), {sp2, sp3})
4976        self.assertEqual(set(n2.getSpannerSites(['Slur', 'Diminuendo'])), {sp1, sp3})
4977
4978        # The order spanners are returned is generally the order that they were
4979        # added, but that is not guaranteed, so for safety sake, use set comparisons:
4980
4981        self.assertEqual(set(n2.getSpannerSites(['Slur', 'Diminuendo'])), {sp3, sp1})
4982
4983    def testContextSitesA(self):
4984        from music21 import corpus
4985        self.maxDiff = None
4986        c = corpus.parse('bwv66.6')
4987        c.id = 'bach'
4988        n = c[2][4][2]
4989        self.assertEqual(repr(n), '<music21.note.Note G#>')
4990        siteList = []
4991        for y in n.contextSites():
4992            yTup = (y.site, y.offset, y.recurseType)
4993            siteList.append(repr(yTup))
4994        self.assertEqual(siteList,
4995                         ["(<music21.stream.Measure 3 offset=9.0>, 0.5, 'elementsFirst')",
4996                          "(<music21.stream.Part Alto>, 9.5, 'flatten')",
4997                          "(<music21.stream.Score bach>, 9.5, 'elementsOnly')"])
4998
4999        m = c[2][4]
5000        self.assertEqual(repr(m), '<music21.stream.Measure 3 offset=9.0>')
5001
5002        siteList = []
5003        for y in m.contextSites():
5004            yTup = (y.site, y.offset, y.recurseType)
5005            siteList.append(repr(yTup))
5006        self.assertEqual(siteList,
5007                         ["(<music21.stream.Measure 3 offset=9.0>, 0.0, 'elementsFirst')",
5008                          "(<music21.stream.Part Alto>, 9.0, 'flatten')",
5009                          "(<music21.stream.Score bach>, 9.0, 'elementsOnly')"])
5010
5011        m2 = copy.deepcopy(m)
5012        m2.number = 3333
5013        siteList = []
5014        # environLocal.warn('#########################')
5015        for y in m2.contextSites():
5016            yTup = (y.site, y.offset, y.recurseType)
5017            siteList.append(repr(yTup))
5018        self.assertEqual(siteList,
5019                         ["(<music21.stream.Measure 3333 offset=0.0>, 0.0, 'elementsFirst')",
5020                          "(<music21.stream.Part Alto>, 9.0, 'flatten')",
5021                          "(<music21.stream.Score bach>, 9.0, 'elementsOnly')"])
5022        siteList = []
5023
5024        cParts = c.parts.stream()  # need this otherwise it could possibly be garbage collected.
5025        cParts.id = 'partStream'  # to make it easier to see below, will be cached...
5026        pTemp = cParts[1]
5027        m3 = pTemp.measure(3)
5028        self.assertIs(m, m3)
5029        for y in m3.contextSites():
5030            yTup = (y.site, y.offset, y.recurseType)
5031            siteList.append(repr(yTup))
5032
5033        self.assertEqual(siteList,
5034                         ["(<music21.stream.Measure 3 offset=9.0>, 0.0, 'elementsFirst')",
5035                          "(<music21.stream.Part Alto>, 9.0, 'flatten')",
5036                          "(<music21.stream.Score partStream>, 9.0, 'elementsOnly')",
5037                          "(<music21.stream.Score bach>, 9.0, 'elementsOnly')"])
5038
5039    def testContextSitesB(self):
5040        from music21 import stream
5041        from music21 import note
5042        p1 = stream.Part()
5043        p1.id = 'p1'
5044        m1 = stream.Measure()
5045        m1.number = 1
5046        n = note.Note()
5047        m1.append(n)
5048        p1.append(m1)
5049        siteList = []
5050        for y in n.contextSites():
5051            siteList.append(repr(y.site))
5052        self.assertEqual(siteList, ['<music21.stream.Measure 1 offset=0.0>',
5053                                    '<music21.stream.Part p1>'])
5054        p2 = stream.Part()
5055        p2.id = 'p2'
5056        m2 = stream.Measure()
5057        m2.number = 2
5058        m2.append(n)
5059        p2.append(m2)
5060
5061        siteList = []
5062        for y in n.contextSites():
5063            siteList.append(repr(y.site))
5064        self.assertEqual(siteList, ['<music21.stream.Measure 2 offset=0.0>',
5065                                    '<music21.stream.Part p2>',
5066                                    '<music21.stream.Measure 1 offset=0.0>',
5067                                    '<music21.stream.Part p1>'])
5068
5069        siteList = []
5070        for y in n.contextSites(sortByCreationTime=True):
5071            siteList.append(repr(y.site))
5072        self.assertEqual(siteList, ['<music21.stream.Measure 2 offset=0.0>',
5073                                    '<music21.stream.Part p2>',
5074                                    '<music21.stream.Measure 1 offset=0.0>',
5075                                    '<music21.stream.Part p1>'])
5076
5077        siteList = []
5078        for y in n.contextSites(sortByCreationTime='reverse'):
5079            siteList.append(repr(y.site))
5080        self.assertEqual(siteList, ['<music21.stream.Measure 1 offset=0.0>',
5081                                    '<music21.stream.Part p1>',
5082                                    '<music21.stream.Measure 2 offset=0.0>',
5083                                    '<music21.stream.Part p2>'])
5084
5085# great isolation test, but no asserts for now...
5086#     def testPreviousA(self):
5087#         from music21 import corpus
5088#         s = corpus.parse('bwv66.6')
5089#         o = s.parts[0].getElementsByClass('Measure')[2][1]
5090#         i = 20
5091#         while o and i:
5092#             print(o)
5093#             if isinstance(o, stream.Part):
5094#                 pass
5095#             o = o.previous()
5096#             i -= 1
5097#
5098
5099#     def testPreviousB(self):
5100#         '''
5101#         fixed a memo problem which could cause .previous() to run forever
5102#         on flat/derived streams.
5103#         '''
5104#         from music21 import corpus
5105#         s = corpus.parse('luca/gloria')
5106#         sf = s.flatten()
5107#         o = sf[1]
5108#         # o = s[2]
5109#         i = 200
5110#         while o and i:
5111#             print(o, o.activeSite, o.sortTuple().shortRepr())
5112#             o = o.previous()
5113# #            cc = s._cache
5114# #             for x in cc:
5115# #                 if x.startswith('elementTree'):
5116# #                     print(repr(cc[x]))
5117#             i -= 1
5118
5119    def testPreviousAfterDeepcopy(self):
5120        from music21 import stream
5121        from music21 import note
5122        e1 = note.Note('C')
5123        e2 = note.Note('D')
5124        s = stream.Stream()
5125        s.insert(0, e1)
5126        s.insert(1, e2)
5127        self.assertIs(e2.previous(), e1)
5128        self.assertIs(s[1].previous(), e1)
5129        t = copy.deepcopy(s)
5130        self.assertIs(t[1].previous(), t[0])
5131
5132        e1 = note.Note('C')
5133        e2 = note.Note('D')
5134
5135        v = stream.Part()
5136        m1 = stream.Measure()
5137        m1.number = 1
5138        m1.insert(0, e1)
5139        v.insert(0, m1)
5140        m2 = stream.Measure()
5141        m2.insert(0, e2)
5142        m2.number = 2
5143        v.append(m2)
5144        self.assertIs(e2.previous('Note'), e1)
5145        self.assertIs(v[1][0], e2)
5146        self.assertIs(v[1][0].previous('Note'), e1)
5147
5148        w = v.transpose('M3')  # same as deepcopy,
5149        # but more instructive in debugging since pitches change...
5150        #    w = copy.deepcopy(v)
5151        eCopy1 = w[0][0]
5152        self.assertEqual(eCopy1.pitch.name, 'E')
5153        eCopy2 = w[1][0]
5154        self.assertEqual(eCopy2.pitch.name, 'F#')
5155        prev = eCopy2.previous('Note')
5156        self.assertIs(prev, eCopy1)
5157
5158
5159# ------------------------------------------------------------------------------
5160# define presented order in documentation
5161_DOC_ORDER = [Music21Object, ElementWrapper]
5162
5163del (Any,
5164     Dict,
5165     FrozenSet,
5166     Iterable,
5167     List,
5168     Optional,
5169     Union,
5170     Tuple,
5171     TypeVar)
5172
5173
5174# -----------------------------------------------------------------------------
5175if __name__ == '__main__':
5176    import music21
5177    music21.mainTest(Test)  # , runTest='testPreviousB')
5178