# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Name:         stream/__init__.py
# Purpose:      base classes for dealing with groups of positioned objects
#
# Authors:      Michael Scott Cuthbert
#               Christopher Ariza
#               Josiah Wolf Oberholtzer
#               Evan Lynch
#
# Copyright:    Copyright © 2008-2020 Michael Scott Cuthbert and the music21 Project
# License:      BSD, see license.txt
# -----------------------------------------------------------------------------
'''
The :class:`~music21.stream.Stream` and its subclasses,
a subclass of the :class:`~music21.base.Music21Object`,
is the fundamental container of offset-positioned notation and
musical elements in music21. Common Stream subclasses, such
as the :class:`~music21.stream.Measure`
and :class:`~music21.stream.Score` objects, are defined in
this module.
'''
import collections
import copy
import itertools
import math
import pathlib
import unittest
import warnings
import sys

from fractions import Fraction
from typing import Union, List, Optional, Set, Tuple, Sequence

from music21 import base

from music21 import bar
from music21 import common
from music21 import clef
from music21 import chord
from music21 import defaults
from music21 import derivation
from music21 import duration
from music21 import exceptions21
from music21 import interval
from music21 import key
from music21 import metadata
from music21 import meter
from music21 import note
from music21 import pitch  # for typing
from music21 import spanner
from music21 import tie
from music21 import repeat
from music21 import sites
from music21 import style
from music21 import tempo

from music21.stream import core
from music21.stream import enums
from music21.stream import makeNotation
from music21.stream import streamStatus
from music21.stream import iterator
from music21.stream import filters

from music21.common.numberTools import opFrac

from music21 import environment
_MOD = 'stream'
environLocal = environment.Environment(_MOD)

StreamException = exceptions21.StreamException
ImmutableStreamException = exceptions21.ImmutableStreamException


class StreamDeprecationWarning(UserWarning):
    # Do not subclass Deprecation warning, because these
    # warnings need to be passed to users...
    pass


# -----------------------------------------------------------------------------
# Metaclass
_OffsetMap = collections.namedtuple('OffsetMap', ['element', 'offset', 'endTime', 'voiceIndex'])


# -----------------------------------------------------------------------------


class Stream(core.StreamCoreMixin, base.Music21Object):
    '''
    This is the fundamental container for Music21Objects;
    objects may be ordered and/or placed in time based on
    offsets from the start of this container.

    As a subclass of Music21Object, Streams have offsets,
    priority, id, and groups.

    Streams may be embedded within other Streams. As each
    Stream can have its own offset, when Streams are
    embedded the offset of an element is relatively only
    to its parent Stream. The :attr:`~music21.stream.Stream.flat`
    property provides access to a flat representation of all
    embedded Streams, with offsets relative to the
    top-level Stream.

    The Stream :attr:`~music21.stream.Stream.elements` attribute
    returns the contents of the Stream as a list. Direct access
    to, and manipulation of, the elements list is not recommended.
    Instead, use the host of high-level methods available.

    The Stream, like all Music21Objects, has a
    :class:`music21.duration.Duration` that is usually the
    "release" time of the chronologically last element in the Stream
    (that is, the highest onset plus the duration of
    any element in the Stream).
    The duration, however, can be "unlinked" and explicitly
    set independent of the Stream's contents.

    The first element passed to the Stream is an optional list,
    tuple, or other Stream of music21 objects which is used to
    populate the Stream by inserting each object at
    its :attr:`~music21.base.Music21Object.offset`
    property. Other arguments and keywords are ignored, but are
    allowed so that subclassing the Stream is easier.

    >>> s1 = stream.Stream()
    >>> s1.append(note.Note('C#4', type='half'))
    >>> s1.append(note.Note('D5', type='quarter'))
    >>> s1.duration.quarterLength
    3.0
    >>> for thisNote in s1.notes:
    ...     print(thisNote.octave)
    ...
    4
    5

    This is a demonstration of creating a Stream with other elements,
    including embedded Streams (in this case, :class:`music21.stream.Part`,
    a Stream subclass):

    >>> c1 = clef.TrebleClef()
    >>> c1.offset = 0.0
    >>> c1.priority = -1
    >>> n1 = note.Note('E-6', type='eighth')
    >>> n1.offset = 1.0
    >>> p1 = stream.Part()
    >>> p1.offset = 0.0
    >>> p1.id = 'embeddedPart'
    >>> p1.append(note.Rest())  # quarter rest
    >>> s2 = stream.Stream([c1, n1, p1])
    >>> s2.duration.quarterLength
    1.5
    >>> s2.show('text')
    {0.0} <music21.clef.TrebleClef>
    {0.0} <music21.stream.Part embeddedPart>
        {0.0} <music21.note.Rest rest>
    {1.0} <music21.note.Note E->
    '''
    # this static attributes offer a performance boost over other
    # forms of checking class
    isStream = True
    isMeasure = False
    classSortOrder = -20
    recursionType = 'elementsFirst'

    _styleClass = style.StreamStyle

    # define order to present names in documentation; use strings
    _DOC_ORDER = ['append', 'insert', 'storeAtEnd', 'insertAndShift',
                  'recurse', 'flat',
                  'notes', 'pitches',
                  'transpose',
                  'augmentOrDiminish', 'scaleOffsets', 'scaleDurations']
    # documentation for all attributes (not properties or methods)
    _DOC_ATTR = {
        'recursionType': '''
            Class variable:

            String of ('elementsFirst' (default), 'flatten', 'elementsOnly)
            that decides whether the stream likely holds relevant
            contexts for the elements in it.

            Define this for a stream class, not an individual object.

            see :meth:`~music21.base.Music21Object.contextSites`
            ''',
        'isSorted': '''
            Boolean describing whether the Stream is sorted or not.
            ''',
        'autoSort': '''
            Boolean describing whether the Stream is automatically sorted by
            offset whenever necessary.
            ''',
        'isFlat': '''
            Boolean describing whether this Stream contains embedded
            sub-Streams or Stream subclasses (not flat).
            ''',
        'definesExplicitSystemBreaks': '''
            Boolean that says whether all system breaks in the piece are
            explicitly defined.  Only used on musicxml output (maps to the
            musicxml <supports attribute="new-system"> tag) and only if this is
            the outermost Stream being shown
            ''',
        'definesExplicitPageBreaks': '''
            Boolean that says whether all page breaks in the piece are
            explicitly defined.  Only used on musicxml output (maps to the
            musicxml <supports attribute="new-page"> tag) and only if this is
            the outermost Stream being shown
            ''',
    }

    def __init__(self, givenElements=None, *args, **keywords):
        base.Music21Object.__init__(self, **keywords)
        core.StreamCoreMixin.__init__(self)

        self.streamStatus = streamStatus.StreamStatus(self)
        self._unlinkedDuration = None

        self.autoSort = True

        # these should become part of style or something else...
        self.definesExplicitSystemBreaks = False
        self.definesExplicitPageBreaks = False

        # property for transposition status;
        self._atSoundingPitch = 'unknown'  # True, False, or unknown

        # experimental
        self._mutable = True

        if givenElements and common.isIterable(givenElements):
            # TODO: perhaps convert a single element into a list?
            for e in givenElements:
                try:
                    self.coreInsert(e.offset, e)
                except (AttributeError, TypeError):
                    raise StreamException(f'Unable to insert {e}')
            self.coreElementsChanged()

    def _reprInternal(self):
        if self.id is not None:
            if self.id != id(self) and str(self.id) != str(id(self)):
                return str(self.id)
            else:
                return hex(self.id)
        else:  # pragma: no cover
            return ''

    def write(self, fmt=None, fp=None, **keywords):
        # ...    --- see base.py calls .write(
        if self.isSorted is False and self.autoSort:  # pragma: no cover
            self.sort()
        return super().write(fmt=fmt, fp=fp, **keywords)

    def show(self, fmt=None, app=None, **keywords):
        # ...    --- see base.py calls .write(
        if self.isSorted is False and self.autoSort:
            self.sort()
        return super().show(fmt=fmt, app=app, **keywords)

    # --------------------------------------------------------------------------
    # sequence like operations

    def __len__(self):
        '''
        Get the total number of elements in the Stream.
        This method does not recurse into and count elements in contained Streams.

        >>> import copy
        >>> a = stream.Stream()
        >>> for x in range(4):
        ...     n = note.Note('G#')
        ...     n.offset = x * 3
        ...     a.insert(n)
        >>> len(a)
        4

        >>> b = stream.Stream()
        >>> for x in range(4):
        ...     b.insert(copy.deepcopy(a))  # append streams
        >>> len(b)
        4
        >>> len(b.flat)
        16

        Includes end elements:

        >>> b.storeAtEnd(bar.Barline('double'))
        >>> len(b)
        5
        '''
        return len(self._elements) + len(self._endElements)

    def __iter__(self):
        '''
        The Stream iterator, used in all for
        loops and similar iteration routines. This method returns the
        specialized :class:`music21.stream.StreamIterator` class, which
        adds necessary Stream-specific features.
        '''
        return iterator.StreamIterator(self)

    @property
    def iter(self):
        '''
        The Stream iterator, used in all for
        loops and similar iteration routines. This method returns the
        specialized :class:`music21.stream.StreamIterator` class, which
        adds necessary Stream-specific features.

        Generally you don't need this, just iterate over a stream, but it is necessary
        to add custom filters to an iterative search before iterating.
        '''
        return self.__iter__()

    def __getitem__(self, k):
        '''
        Get a Music21Object from the Stream using a variety of keys or indices.

        If an int is given, the Music21Object at the index is returned. If the Stream is sorted
        (if isSorted is True), the elements are returned in order.

        If a class name is given (as a string or name),
        :meth:`~music21.stream.Stream.getElementsByClass` is used to return a Stream of the
        elements that match the requested class.

        If a string is given, :meth:`~music21.stream.Stream.getElementById` first, then
        (if no results are found) :meth:`~music21.stream.Stream.getElementsByGroup` is used to
        collect and return elements as a Stream.

        >>> a = stream.Part(id='hello')
        >>> a.repeatInsert(note.Rest(), [0, 1, 2, 3, 4, 5])
        >>> subslice = a[2:5]
        >>> subslice
        <music21.stream.Part hello>
        >>> subslice.derivation
        <Derivation of <music21.stream.Part hello> from
            <music21.stream.Part hello> via '__getitem__'>
        >>> len(subslice)
        3
        >>> a[1].offset
        1.0
        >>> subslice[1].offset
        3.0

        >>> b = note.Note()
        >>> b.id = 'green'
        >>> b.groups.append('violin')
        >>> a.insert(b)

        Get an item by class

        >>> allNotes = a[note.Note]
        >>> allNotes
        <music21.stream.Part hello>
        >>> allNotes.derivation
        <Derivation of <music21.stream.Part hello> from
            <music21.stream.Part hello> via 'getElementsByClass'>
        >>> allNotes[0] is b
        True

        Get items by groups:

        >>> violinGroup = a['violin']
        >>> violinGroup
        <music21.stream.Part hello>
        >>> violinGroup.derivation
        <Derivation of <music21.stream.Part hello> from
            <music21.stream.Part hello> via 'getElementsByGroup'>
        >>> violinGroup[0] is b
        True

        Get a single element by id:

        >>> a['green'] is b
        True

        If not found, a KeyError will be raised:

        >>> a['purple']
        Traceback (most recent call last):
        KeyError: 'provided key (purple) does not match any id or group'

        >>> a[layout.StaffLayout]
        Traceback (most recent call last):
        KeyError: "provided class (<class 'music21.layout.StaffLayout'>) does
            not match any contained Objects"

        '''
        # need to sort if not sorted, as this call may rely on index positions
        if not self.isSorted and self.autoSort:
            self.sort()  # will set isSorted to True

        if common.isNum(k):
            match = None
            # handle easy and most common case first
            if 0 <= k < len(self._elements):
                match = self._elements[k]
            # if using negative indices, or trying to access end elements,
            # then need to use .elements property
            else:
                try:
                    match = self.elements[k]
                except IndexError:
                    raise StreamException(
                        f'attempting to access index {k} '
                        + f'while elements is of size {len(self.elements)}'
                    )
            # setting active site as cautionary measure
            self.coreSelfActiveSite(match)
            return match

        elif isinstance(k, slice):  # get a slice of index values
            # manually inserting elements is critical to setting the element
            # locations
            try:
                found = self.cloneEmpty(derivationMethod='__getitem__')
            except TypeError:  # pragma: no cover
                raise StreamException(f'Error in defining class: {self.__class__}'
                                      + 'Stream subclasses and Music21Objects cannot have required '
                                      + 'arguments in __init__')
            for e in self.elements[k]:
                found.coreInsert(self.elementOffset(e), e)

            found.coreElementsChanged(clearIsSorted=False)
            return found

        elif isinstance(k, str):
            # first search id, then search groups
            idMatch = self.getElementById(k)
            if idMatch is not None:
                return idMatch
            else:  # search groups, return first element match
                groupStream = self.iter.getElementsByGroup(k)
                if groupStream:
                    return groupStream.stream()
                else:
                    raise KeyError(f'provided key ({k}) does not match any id or group')
        elif isinstance(k, type(type)):
            # assume it is a class name
            classStream = self.iter.getElementsByClass(k)
            if classStream:
                return classStream.stream()
            else:
                raise KeyError(f'provided class ({k}) does not match any contained Objects')

    def __contains__(self, el):
        '''
        Returns True if `el` definitely is in the stream and False otherwise.

        >>> nC = note.Note('C4')
        >>> nD = note.Note('D4')
        >>> s = stream.Stream()
        >>> s.append(nC)
        >>> nC in s
        True
        >>> nD in s
        False

        Note that we match on actual `id()` equality (`x is y`) and not on
        `==` equality.

        >>> nC2 = note.Note('C4')
        >>> nC == nC2
        True
        >>> nC2 in s
        False

        To get the latter, compare on `.elements` which uses Python's
        default `__contains__` for tuples.

        >>> nC2 in s.elements
        True
        '''
        for sEl in self.elements:  # for speed do not set active sites
            if el is sEl:
                return True
        return False

    @property
    def elements(self) -> Tuple[base.Music21Object]:
        '''
        .elements is a Tuple representing the elements contained in the Stream.

        Directly getting, setting, and manipulating this Tuple is
        reserved for advanced usage. Instead, use the the
        provided high-level methods.  The elements retrieved here may not
        have this stream as an activeSite, therefore they might not be properly ordered.

        In other words:  Don't use unless you really know what you're doing.
        Treat a Stream like a list!

        When setting .elements, a list of Music21Objects can be provided, or a complete Stream.
        If a complete Stream is provided, elements are extracted
        from that Stream. This has the advantage of transferring
        offset correctly and getting elements stored at the end.

        >>> a = stream.Stream()
        >>> a.repeatInsert(note.Note('C'), list(range(10)))
        >>> b = stream.Stream()
        >>> b.repeatInsert(note.Note('D'), list(range(10)))
        >>> b.offset = 6
        >>> c = stream.Stream()
        >>> c.repeatInsert(note.Note('E'), list(range(10)))
        >>> c.offset = 12
        >>> b.insert(c)
        >>> b.isFlat
        False

        >>> a.isFlat
        True

        Assigning from a Stream works well, and is actually much safer than assigning
        from `.elements` of the other Stream, since the active sites may have changed
        of that stream's elements in the meantime.

        >>> a.elements = b
        >>> a.isFlat
        False

        >>> len(a.flat.notes) == len(b.flat.notes) == 20
        True
        '''
        # combines _elements and _endElements into one.
        if 'elements' not in self._cache or self._cache['elements'] is None:
            # this list concatenation may take time; thus, only do when
            # coreElementsChanged has been called
            if not self.isSorted and self.autoSort:
                self.sort()  # will set isSorted to True
            self._cache['elements'] = self._elements + self._endElements
        return tuple(self._cache['elements'])

    @elements.setter
    def elements(self, value: Union['Stream', Sequence[base.Music21Object]]):
        '''
        Sets this streams elements to the elements in another stream (just give
        the stream, not the stream's .elements), or to a list of elements.

        Safe:

        newStream.elements = oldStream

        Unsafe:

        newStream.elements = oldStream.elements

        Why?

        The activeSites of some elements may have changed between retrieving
        and setting (esp. if a lot else has happened in the meantime). Where
        are we going to get the new stream's elements' offsets from? why
        from their active sites! So don't do this!
        '''
        if (not common.isListLike(value)
                and hasattr(value, 'isStream')
                and value.isStream):
            # set from a Stream. Best way to do it
            self._offsetDict = {}
            self._elements = list(value._elements)  # copy list.
            for e in self._elements:
                self.setElementOffset(e, value.elementOffset(e), addElement=True)
                e.sites.add(self)
                self.coreSelfActiveSite(e)
            self._endElements = list(value._endElements)
            for e in self._endElements:
                self.setElementOffset(e,
                                      value.elementOffset(e, stringReturns=True),
                                      addElement=True)
                e.sites.add(self)
                self.coreSelfActiveSite(e)
        else:
            # replace the complete elements list
            self._elements = list(value)
            self._endElements = []
            self._offsetDict = {}
            for e in self._elements:
                self.setElementOffset(e, e.offset, addElement=True)
                e.sites.add(self)
                self.coreSelfActiveSite(e)
        self.coreElementsChanged()


    def __setitem__(self, k, value):
        '''
        Insert an item at a currently filled index position,
        as represented in the elements list.

        >>> a = stream.Stream()
        >>> a.repeatInsert(note.Note('C'), list(range(10)))
        >>> b = stream.Stream()
        >>> b.repeatInsert(note.Note('C'), list(range(10)))
        >>> b.offset = 6
        >>> c = stream.Stream()
        >>> c.repeatInsert(note.Note('C'), list(range(10)))
        >>> c.offset = 12
        >>> b.insert(c)
        >>> a.isFlat
        True
        >>> a[3] = b
        >>> a.isFlat
        False
        '''
        # remove old value at this position
        oldValue = self._elements[k]
        del self._offsetDict[id(oldValue)]
        oldValue.sites.remove(self)
        oldValue.activeSite = None

        # assign in new position
        self._elements[k] = value
        self.setElementOffset(value, value.offset, addElement=True)
        self.coreSelfActiveSite(value)
        # must get native offset

        value.sites.add(self)

        if isinstance(value, Stream):
            # know that this is now not flat
            self.coreElementsChanged(updateIsFlat=False)  # set manually
            self.isFlat = False
        else:
            # cannot be sure if this is flat, as we do not know if
            # we replaced a Stream at this index
            self.coreElementsChanged()

    def __delitem__(self, k):
        '''
        Delete element at an index position. Index positions are based
        on positions in self.elements.

        >>> a = stream.Stream()
        >>> a.repeatInsert(note.Note('C'), list(range(10)))
        >>> del a[0]
        >>> len(a)
        9
        '''
        del self._elements[k]
        self.coreElementsChanged()

    def __add__(self, other):
        '''
        Add, or concatenate, two Streams.

        Presently, this does not manipulate the offsets of the incoming elements
        to actually be at the end of the Stream. This may be a problem that
        makes this method not so useful?

        >>> a = stream.Part()
        >>> a.repeatInsert(note.Note('C'), [0, 1])
        >>> b = stream.Stream()
        >>> b.repeatInsert(note.Note('G'), [0, 1, 2])
        >>> c = a + b
        >>> c.pitches  # autoSort is True, thus a sorted version results
        [<music21.pitch.Pitch C>,
         <music21.pitch.Pitch G>,
         <music21.pitch.Pitch C>,
         <music21.pitch.Pitch G>,
         <music21.pitch.Pitch G>]
        >>> len(c.notes)
        5

        The autoSort of the first stream becomes the autoSort of the
        destination.  The class of the first becomes the class of the destination.

        >>> a.autoSort = False
        >>> d = a + b
        >>> [str(p) for p in d.pitches]
        ['C', 'C', 'G', 'G', 'G']
        >>> d.__class__.__name__
        'Part'

        Works with Streams with Store at end, which does put both at the end.

        >>> a.autoSort = True
        >>> a.storeAtEnd(bar.Barline('final'))
        >>> b.storeAtEnd(clef.TrebleClef())
        >>> f = a + b
        >>> f.show('text')
        {0.0} <music21.note.Note C>
        {0.0} <music21.note.Note G>
        {1.0} <music21.note.Note C>
        {1.0} <music21.note.Note G>
        {2.0} <music21.note.Note G>
        {3.0} <music21.bar.Barline type=final>
        {3.0} <music21.clef.TrebleClef>
        '''
        if other is None or not isinstance(other, Stream):
            raise TypeError('cannot concatenate a Stream with a non-Stream')

        s = self.cloneEmpty(derivationMethod='__add__')
        # may want to keep activeSite of source Stream?
        # s.elements = self._elements + other._elements
        # need to iterate over elements and re-assign to create new locations
        for e in self._elements:
            s.insert(self.elementOffset(e), e)
        for e in other._elements:
            s.insert(other.elementOffset(e), e)

        for e in self._endElements:
            s.storeAtEnd(e)
        for e in other._endElements:
            s.storeAtEnd(e)

        # s.coreElementsChanged()
        return s

    def __bool__(self):
        '''
        As a container class, Streams return True if they are non-empty
        and False if they are empty:

        >>> def testBool(s):
        ...    if s:
        ...        return True
        ...    else:
        ...        return False

        >>> s = stream.Stream()
        >>> testBool(s)
        False
        >>> s.append(note.Note())
        >>> testBool(s)
        True
        >>> s.append(note.Note())
        >>> testBool(s)
        True

        >>> s = stream.Stream()
        >>> s.storeAtEnd(bar.Barline('final'))
        >>> testBool(s)
        True
        '''
        if self._elements:  # blindingly faster than if len(self._elements) > 0
            return True    # and even about 5x faster than if any(self._elements)
        if self._endElements:
            return True
        return False

    # ------------------------------
    @property
    def clef(self) -> Optional['music21.clef.Clef']:
        '''
        Finds or sets a :class:`~music21.clef.Clef` at offset 0.0 in the Stream
        (generally a Measure):

        >>> m = stream.Measure()
        >>> m.number = 10
        >>> m.clef = clef.TrebleClef()
        >>> thisTrebleClef = m.clef
        >>> thisTrebleClef.sign
        'G'
        >>> thisTrebleClef.getOffsetBySite(m)
        0.0

        Setting the clef for the measure a second time removes the previous clef
        from the measure and replaces it with the new one:

        >>> m.clef = clef.BassClef()
        >>> m.clef.sign
        'F'

        And the TrebleClef is no longer in the measure:

        >>> thisTrebleClef.getOffsetBySite(m)
        Traceback (most recent call last):
        music21.sites.SitesException: an entry for this object <music21.clef.TrebleClef> is not
              stored in stream <music21.stream.Measure 10 offset=0.0>

        The `.clef` appears in a `.show()` or other call
        just like any other element

        >>> m.append(note.Note('D#', type='whole'))
        >>> m.show('text')
        {0.0} <music21.clef.BassClef>
        {0.0} <music21.note.Note D#>
        '''
        clefList = self.iter.getElementsByClass('Clef').getElementsByOffset(0)
        # casting to list added 20microseconds...
        if not clefList:
            return None
        else:
            return clefList[0]

    @clef.setter
    def clef(self, clefObj: Optional['music21.clef.Clef']):
        # if clef is None; remove object?
        oldClef = self.clef
        if oldClef is not None:
            # environLocal.printDebug(['removing clef', oldClef])
            junk = self.pop(self.index(oldClef))
        if clefObj is None:
            # all that is needed is to remove the old clef
            # there is no new clef - suppresses the clef of a stream
            return
        self.insert(0.0, clefObj)
        # for some reason needed to make sure that sorting of Clef happens before TimeSignature
        # TODO: Test if this can be deleted...
        self.coreElementsChanged()

    @property
    def timeSignature(self):
        '''
        Gets or sets the timeSignature at offset 0.0 of the Stream (generally a Measure)

        >>> m1 = stream.Measure(number=1)
        >>> m1.timeSignature = meter.TimeSignature('2/4')
        >>> m1.timeSignature.numerator, m1.timeSignature.denominator
        (2, 4)
        >>> m1.show('text')
        {0.0} <music21.meter.TimeSignature 2/4>

        Setting timeSignature to None removes any TimeSignature at offset 0.0:

        >>> m1.timeSignature = None
        >>> m1.elements
        ()


        Only the time signature at offset 0 is found:

        >>> m2 = stream.Measure(number=2)
        >>> m2.insert(0.0, meter.TimeSignature('5/4'))
        >>> m2.insert(2.0, meter.TimeSignature('7/4'))
        >>> ts = m2.timeSignature
        >>> ts.numerator, ts.denominator
        (5, 4)

        >>> m2.timeSignature = meter.TimeSignature('2/8')
        >>> m2.timeSignature
        <music21.meter.TimeSignature 2/8>

        After setting a new `.timeSignature`, the old one is no longer in the Stream:

        >>> ts in m2
        False

        This property is not recursive, so a Part will not have the time signature of
        the measure within it:

        >>> p = stream.Part()
        >>> p.append(m2)
        >>> p.timeSignature is None
        True
        '''
        # there could be more than one
        tsList = self.iter.getElementsByClass('TimeSignature').getElementsByOffset(0)
        # environLocal.printDebug([
        #    'matched Measure classes of type TimeSignature', tsList, len(tsList)])
        # only return timeSignatures at offset = 0.0
        if not tsList:
            return None
        else:
            return tsList[0]

    @timeSignature.setter
    def timeSignature(self, tsObj: Optional['music21.meter.TimeSignature']):
        oldTimeSignature = self.timeSignature
        if oldTimeSignature is not None:
            # environLocal.printDebug(['removing ts', oldTimeSignature])
            junk = self.pop(self.index(oldTimeSignature))
        if tsObj is None:
            # all that is needed is to remove the old time signature
            # there is no new time signature - suppresses the time signature of a stream
            return
        self.insert(0, tsObj)

    @property
    def keySignature(self) -> Optional['music21.key.KeySignature']:
        '''
        Find or set a Key or KeySignature at offset 0.0 of a stream.

        >>> a = stream.Measure()
        >>> a.keySignature = key.KeySignature(-2)
        >>> ks = a.keySignature
        >>> ks.sharps
        -2
        >>> a.show('text')
        {0.0} <music21.key.KeySignature of 2 flats>

        A key.Key object can be used instead of key.KeySignature,
        since the former derives from the latter.

        >>> a.keySignature = key.Key('E', 'major')
        >>> for k in a:
        ...     print(k.offset, repr(k))
        0.0 <music21.key.Key of E major>

        Notice that setting a new key signature replaces any previous ones:

        >>> len(a.getElementsByClass('KeySignature'))
        1

        `.keySignature` can be set to None:

        >>> a.keySignature = None
        >>> a.keySignature is None
        True
        '''
        try:
            return next(self.iter.getElementsByClass('KeySignature').getElementsByOffset(0))
        except StopIteration:
            return None

    @keySignature.setter
    def keySignature(self, keyObj: Optional['music21.key.KeySignature']):
        '''
        >>> a = stream.Measure()
        >>> a.keySignature = key.KeySignature(6)
        >>> a.keySignature.sharps
        6
        '''
        oldKey = self.keySignature
        if oldKey is not None:
            # environLocal.printDebug(['removing key', oldKey])
            junk = self.pop(self.index(oldKey))
        if keyObj is None:
            # all that is needed is to remove the old key signature
            # there is no new key signature - suppresses the key signature of a stream
            return
        self.insert(0, keyObj)

    @property
    def staffLines(self) -> int:
        '''
        Returns the number of staffLines for the Stream, as defined by
        the first StaffLayout object found at offset 0 that defines staffLines

        >>> m = stream.Measure()
        >>> m.staffLines
        5
        >>> m.staffLines = 4
        >>> m.staffLines
        4
        >>> m.show('text')
        {0.0} <music21.layout.StaffLayout distance None, staffNumber None,
                  staffSize None, staffLines 4>

        >>> staffLayout = m.getElementsByClass('StaffLayout')[0]
        >>> staffLayout.staffLines = 1
        >>> m.staffLines
        1

        >>> p = stream.Part()
        >>> p.insert(0, m)
        >>> p.staffLines
        1

        >>> p2 = stream.Part()
        >>> m0 = stream.Measure()
        >>> m0.insert(0, note.Note(type='whole'))
        >>> p2.append(m0)
        >>> p2.append(m)
        >>> p2.staffLines
        5

        '''
        staffLayouts = self.recurse().getElementsByClass('StaffLayout')
        sl: 'music21.layout.StaffLayout'
        for sl in staffLayouts:
            if sl.getOffsetInHierarchy(self) > 0:
                break
            if sl.staffLines is not None:
                return sl.staffLines
        return 5

    @staffLines.setter
    def staffLines(self, newStaffLines: int):
        staffLayouts = self.recurse().getElementsByOffset(0.0).getElementsByClass('StaffLayout')
        if not staffLayouts:
            from music21 import layout
            sl: 'music21.layout.StaffLayout' = layout.StaffLayout(staffLines=newStaffLines)
            self.insert(0.0, sl)
        else:
            firstLayout = staffLayouts[0]
            firstLayout.staffLines = newStaffLines



    def clear(self):
        '''
        Remove all elements in a stream.

        >>> m = stream.Measure(number=3)
        >>> m.append(note.Note('C'))
        >>> m.storeAtEnd(bar.Barline('final'))
        >>> len(m)
        2
        >>> m.clear()
        >>> len(m)
        0

        Does not remove any other attributes

        >>> m.number
        3
        '''
        self.elements = []

    def cloneEmpty(self, derivationMethod=None):
        '''
        Create a Stream that is identical to this one except that the elements are empty
        and set derivation (Should this be deleted???)

        >>> p = stream.Part()
        >>> p.autoSort = False
        >>> p.id = 'hi'
        >>> p.insert(0, note.Note())
        >>> q = p.cloneEmpty(derivationMethod='demo')
        >>> q.autoSort
        False
        >>> q
        <music21.stream.Part hi>
        >>> q.derivation.origin is p
        True
        >>> q.derivation.method
        'demo'
        >>> len(q)
        0
        '''
        returnObj = self.__class__()
        returnObj.derivation.client = returnObj
        returnObj.derivation.origin = self
        if derivationMethod is not None:
            returnObj.derivation.method = derivationMethod
        returnObj.mergeAttributes(self)  # get groups, optional id
        return returnObj

    def mergeAttributes(self, other: 'Stream'):
        '''
        Merge relevant attributes from the Other stream into this one.

        >>> s = stream.Stream()
        >>> s.append(note.Note())
        >>> s.autoSort = False
        >>> s.id = 'hi'
        >>> t = stream.Stream()
        >>> t.mergeAttributes(s)
        >>> t.autoSort
        False
        >>> t
        <music21.stream.Stream hi>
        >>> len(t)
        0
        '''
        super().mergeAttributes(other)

        for attr in ('autoSort', 'isSorted', 'definesExplicitSystemBreaks',
                     'definesExplicitPageBreaks', '_atSoundingPitch', '_mutable'):
            if hasattr(other, attr):
                setattr(self, attr, getattr(other, attr))

    def hasElement(self, obj):
        '''
        Return True if an element, provided as an argument, is contained in
        this Stream.

        This method is based on object equivalence, not parameter equivalence
        of different objects.

        >>> s = stream.Stream()
        >>> n1 = note.Note('g')
        >>> n2 = note.Note('g#')
        >>> s.append(n1)
        >>> s.hasElement(n1)
        True
        '''
        objId = id(obj)
        return self.coreHasElementByMemoryLocation(objId)

    def hasElementOfClass(self, className, forceFlat=False):
        '''
        Given a single class name as string,
        return True or False if an element with the
        specified class is found.

        Only a single class name can be given.

        >>> s = stream.Stream()
        >>> s.append(meter.TimeSignature('5/8'))
        >>> s.append(note.Note('d-2'))
        >>> s.insert(dynamics.Dynamic('fff'))
        >>> s.hasElementOfClass('TimeSignature')
        True
        >>> s.hasElementOfClass('Measure')
        False

        To be deprecated in v.7 -- to be removed in version 8, use:

        >>> bool(s.getElementsByClass('TimeSignature'))
        True
        >>> bool(s.getElementsByClass('Measure'))
        False

        forceFlat does nothing, while getElementsByClass can be done on recurse()
        '''
        # environLocal.printDebug(['calling hasElementOfClass()', className])
        for e in self.elements:
            if className in e.classSet:
                return True
        return False

    def mergeElements(self, other, classFilterList=None):
        '''
        Given another Stream, store references of each element
        in the other Stream in this Stream. This does not make
        copies of any elements, but simply stores all of them in this Stream.

        Optionally, provide a list of classes to exclude with the `classFilter` list.

        This method provides functionality like a shallow copy,
        but manages locations properly, only copies elements,
        and permits filtering by class type.


        >>> s1 = stream.Stream()
        >>> s2 = stream.Stream()
        >>> n1 = note.Note('f#')
        >>> n2 = note.Note('g')
        >>> s1.append(n1)
        >>> s1.append(n2)
        >>> s2.mergeElements(s1)
        >>> len(s2)
        2
        >>> s1[0] is s2[0]
        True
        >>> s1[1] is s2[1]
        True
        '''
        if classFilterList is not None:
            classFilterSet = set(classFilterList)
        else:
            classFilterSet = None

        for e in other._elements:
            # self.insert(other.offset, e)
            if classFilterList is not None:
                if classFilterSet.intersection(e.classSet):
                    self.coreInsert(other.elementOffset(e), e)
            else:
                self.coreInsert(other.elementOffset(e), e)

            # for c in classFilterList:
            #     if c in e.classes:
            #         match = True
            #         break
            #
            # if len(classFilterList) == 0 or match:
            #     self.insert(e.getOffsetBySite(other), e)

        for e in other._endElements:
            if classFilterList is not None:
                if classFilterSet.intersection(e.classSet):
                    self.coreStoreAtEnd(e)
            else:
                self.coreStoreAtEnd(e)

            # match = False
            # for c in classFilterList:
            #     if c in e.classes:
            #         match = True
            #         break
            # if len(classFilterList) == 0 or match:
            #     self.storeAtEnd(e)
        self.coreElementsChanged()

    def index(self, el):
        '''
        Return the first matched index for
        the specified object.

        Raises a StreamException if cannot
        be found.

        >>> s = stream.Stream()
        >>> n1 = note.Note('g')
        >>> n2 = note.Note('g#')

        >>> s.insert(0, n1)
        >>> s.insert(5, n2)
        >>> len(s)
        2
        >>> s.index(n1)
        0
        >>> s.index(n2)
        1

        >>> n3 = note.Note('a')
        >>> s.index(n3)
        Traceback (most recent call last):
        music21.exceptions21.StreamException: cannot find object (<music21.note.Note A>) in Stream
        '''
        if not self.isSorted and self.autoSort:
            self.sort()  # will set isSorted to True

        if 'index' in self._cache and self._cache['index'] is not None:
            try:
                return self._cache['index'][id(el)]
            except KeyError:
                pass  # not in cache
        else:
            self._cache['index'] = {}

        objId = id(el)

        count = 0

        for e in self._elements:
            if e is el:
                self._cache['index'][objId] = count
                return count
            count += 1
        for e in self._endElements:
            if e is el:
                self._cache['index'][objId] = count
                return count  # this is the index
            count += 1  # cumulative indices
        raise StreamException(f'cannot find object ({el}) in Stream')

    def remove(self,
               targetOrList,
               *,
               shiftOffsets=False,
               recurse=False):
        # noinspection PyShadowingNames
        '''
        Remove an object from this Stream. Additionally, this Stream is
        removed from the object's sites in :class:`~music21.sites.Sites`.

        If a list of objects is passed, they will all be removed.
        If shiftOffsets is True, then offsets will be
        corrected after object removal. It is more efficient to pass
        a list of objects than to call remove on
        each object individually if shiftOffsets is True.

        >>> import copy
        >>> s = stream.Stream()
        >>> n1 = note.Note('g')
        >>> n2 = note.Note('g#')

        Copies of an object are not the same as the object

        >>> n3 = copy.deepcopy(n2)
        >>> s.insert(10, n1)
        >>> s.insert(5, n2)
        >>> s.remove(n1)
        >>> len(s)
        1
        >>> s.insert(20, n3)
        >>> s.remove(n3)
        >>> [e for e in s] == [n2]
        True

        No error is raised if the target is not found.

        >>> s.remove(n3)

        >>> s2 = stream.Stream()
        >>> c = clef.TrebleClef()
        >>> n1, n2, n3, n4 = note.Note('a'), note.Note('b'), note.Note('c'), note.Note('d')
        >>> n5, n6, n7, n8 = note.Note('e'), note.Note('f'), note.Note('g'), note.Note('a')
        >>> s2.insert(0.0, c)
        >>> s2.append([n1, n2, n3, n4, n5, n6, n7, n8])
        >>> s2.remove(n1, shiftOffsets=True)
        >>> s2.show('text')
        {0.0} <music21.clef.TrebleClef>
        {0.0} <music21.note.Note B>
        {1.0} <music21.note.Note C>
        {2.0} <music21.note.Note D>
        {3.0} <music21.note.Note E>
        {4.0} <music21.note.Note F>
        {5.0} <music21.note.Note G>
        {6.0} <music21.note.Note A>

        >>> s2.remove([n3, n6, n4], shiftOffsets=True)
        >>> s2.show('text')
        {0.0} <music21.clef.TrebleClef>
        {0.0} <music21.note.Note B>
        {1.0} <music21.note.Note E>
        {2.0} <music21.note.Note G>
        {3.0} <music21.note.Note A>

        With the recurse=True parameter, we can remove elements deeply nested.
        However, shiftOffsets
        does not work with recurse=True yet.

        >>> p1 = stream.Part()
        >>> m1 = stream.Measure(number=1)
        >>> c = clef.BassClef()
        >>> m1.insert(0, c)
        >>> m1.append(note.Note(type='whole'))
        >>> p1.append(m1)
        >>> m2 = stream.Measure(number=2)
        >>> n2 = note.Note('D', type='half')
        >>> m2.append(n2)
        >>> n3 = note.Note(type='half')
        >>> m2.append(n3)
        >>> p1.append(m2)
        >>> p1.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.BassClef>
            {0.0} <music21.note.Note C>
        {4.0} <music21.stream.Measure 2 offset=4.0>
            {0.0} <music21.note.Note D>
            {2.0} <music21.note.Note C>

        Without recurse=True:

        >>> p1.remove(n2)
        >>> p1.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.BassClef>
            {0.0} <music21.note.Note C>
        {4.0} <music21.stream.Measure 2 offset=4.0>
            {0.0} <music21.note.Note D>
            {2.0} <music21.note.Note C>

        With recurse=True:

        >>> p1.remove(n2, recurse=True)
        >>> p1.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.BassClef>
            {0.0} <music21.note.Note C>
        {4.0} <music21.stream.Measure 2 offset=4.0>
            {2.0} <music21.note.Note C>

        With recurse=True and a list to remove:

        >>> p1.remove([c, n3], recurse=True)
        >>> p1.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.note.Note C>
        {4.0} <music21.stream.Measure 2 offset=4.0>
        <BLANKLINE>


        Can also remove elements stored at end:

        >>> streamWithBarline = stream.Stream(note.Note())
        >>> barline = bar.Barline('final')
        >>> streamWithBarline.storeAtEnd(barline)
        >>> barline in streamWithBarline
        True
        >>> streamWithBarline.remove(barline)
        >>> barline in streamWithBarline
        False

        Changed in v5.3 -- firstMatchOnly removed -- impossible to have element
        in stream twice.  recurse and shiftOffsets changed to keywordOnly arguments
        '''
        # experimental
        if self._mutable is False:  # pragma: no cover
            raise ImmutableStreamException('Cannot remove from an immutable stream')
        # TODO: Next to clean up... a doozy -- filter out all the different options.

        # TODO: Add a renumber measures option
        # TODO: Shift offsets if recurse is True
        if shiftOffsets is True and recurse is True:
            raise StreamException(
                'Cannot do both shiftOffsets and recurse search at the same time...yet')

        if not common.isListLike(targetOrList):
            targetList = [targetOrList]
        elif len(targetOrList) > 1:
            try:
                targetList = sorted(targetOrList, key=self.elementOffset)
            except sites.SitesException:
                # will not be found if recursing, it's not such a big deal...
                targetList = targetOrList
        else:
            targetList = targetOrList

        shiftDur = 0.0  # for shiftOffsets

        for i, target in enumerate(targetList):
            try:
                indexInStream = self.index(target)
            except StreamException:
                if recurse is True:
                    for s in self.recurse(streamsOnly=True):
                        try:
                            indexInStream = s.index(target)
                            s.remove(target)
                            break
                        except (StreamException, sites.SitesException):
                            continue
                # recursion matched or didn't or wasn't run. either way no need for rest...
                continue

            # TODO: Anything that messes with ._elements or ._endElements should be in core.py
            # move it...
            match = None
            matchedEndElement = False
            baseElementCount = len(self._elements)
            matchOffset = 0.0  # to avoid possibility of undefined

            if indexInStream < baseElementCount:
                match = self._elements.pop(indexInStream)
            else:
                match = self._endElements.pop(indexInStream - baseElementCount)
                matchedEndElement = True

            if match is not None:
                if shiftOffsets is True:
                    matchOffset = self.elementOffset(match)

                try:
                    del self._offsetDict[id(match)]
                except KeyError:  # pragma: no cover
                    pass
                self.coreElementsChanged(clearIsSorted=False)
                match.sites.remove(self)
                match.activeSite = None

            if shiftOffsets is True and matchedEndElement is False:
                matchDuration = match.duration.quarterLength
                shiftedRegionStart = matchOffset + matchDuration
                if (i + 1) < len(targetList):
                    shiftedRegionEnd = self.elementOffset(targetList[i + 1])
                else:
                    shiftedRegionEnd = self.duration.quarterLength

                shiftDur += matchDuration
                if shiftDur != 0.0:
                    # can this be done with recurse???
                    for e in self.iter.getElementsByOffset(shiftedRegionStart,
                                                           shiftedRegionEnd,
                                                           includeEndBoundary=False,
                                                           mustFinishInSpan=False,
                                                           mustBeginInSpan=True):

                        elementOffset = self.elementOffset(e)
                        self.setElementOffset(e, elementOffset - shiftDur)
            # if renumberMeasures is True and matchedEndElement is False:
            #     pass  # This should maybe just call a function renumberMeasures
        self.coreElementsChanged(clearIsSorted=False)

    def pop(self, index: int) -> base.Music21Object:
        '''
        Return and remove the object found at the
        user-specified index value. Index values are
        those found in `elements` and are not necessary offset order.

        >>> a = stream.Stream()
        >>> a.repeatInsert(note.Note('C'), list(range(10)))
        >>> junk = a.pop(0)
        >>> len(a)
        9
        '''
        eLen = len(self._elements)
        # if less then base length, its in _elements
        if index < eLen:
            post = self._elements.pop(index)
        else:  # it is in the _endElements
            post = self._endElements.pop(index - eLen)

        self.coreElementsChanged(clearIsSorted=False)

        try:
            del self._offsetDict[id(post)]
        except KeyError:  # pragma: no cover
            pass

        post.sites.remove(self)
        post.activeSite = None
        return post

    def _removeIteration(self, streamIterator):
        '''
        helper method to remove different kinds of elements.
        '''
        popDict = {'_elements': [],
                   '_endElements': []
                   }
        for unused_el in streamIterator:
            ai = streamIterator.activeInformation
            popDict[ai['iterSection']].append(ai['sectionIndex'])

        # do not pop while iterating...

        for section in popDict:
            sectionList = getattr(self, section)  # self._elements or self._endElements
            popList = popDict[section]
            for popIndex in reversed(popList):
                removeElement = sectionList.pop(popIndex)
                try:
                    del self._offsetDict[id(removeElement)]
                except KeyError:  # pragma: no cover
                    pass

                # TODO: to make recursive, store a tuple of active sites and index
                removeElement.sites.remove(self)
                removeElement.activeSite = None

        # call elements changed once; sorted arrangement has not changed
        self.coreElementsChanged(clearIsSorted=False)

    def removeByClass(self, classFilterList) -> None:
        '''
        Remove all elements from the Stream
        based on one or more classes given
        in a list.

        >>> s = stream.Stream()
        >>> s.append(meter.TimeSignature('4/4'))
        >>> s.repeatAppend(note.Note('C'), 8)
        >>> len(s)
        9
        >>> s.removeByClass('GeneralNote')
        >>> len(s)
        1
        >>> len(s.notes)
        0

        Test that removing from end elements works.

        >>> s = stream.Measure()
        >>> s.append(meter.TimeSignature('4/4'))
        >>> s.repeatAppend(note.Note('C'), 4)
        >>> s.rightBarline = bar.Barline('final')
        >>> len(s)
        6
        >>> s.removeByClass('Barline')
        >>> len(s)
        5
        '''
        elFilter = self.iter.getElementsByClass(classFilterList)
        self._removeIteration(elFilter)

    def removeByNotOfClass(self, classFilterList):
        '''
        Remove all elements not of the specified
        class or subclass in the Steam in place.

        >>> s = stream.Stream()
        >>> s.append(meter.TimeSignature('4/4'))
        >>> s.repeatAppend(note.Note('C'), 8)
        >>> len(s)
        9
        >>> s.removeByNotOfClass('TimeSignature')
        >>> len(s)
        1
        >>> len(s.notes)
        0
        '''
        elFilter = self.iter.getElementsNotOfClass(classFilterList)
        return self._removeIteration(elFilter)

    def _deepcopySubclassable(self, memo=None, ignoreAttributes=None, removeFromIgnore=None):
        # NOTE: this is a performance critical operation
        defaultIgnoreSet = {'_offsetDict', 'streamStatus', '_elements', '_endElements', '_cache',
                            }
        if ignoreAttributes is None:
            ignoreAttributes = defaultIgnoreSet
        else:
            ignoreAttributes = ignoreAttributes | defaultIgnoreSet
        new = super()._deepcopySubclassable(memo, ignoreAttributes, removeFromIgnore)

        if removeFromIgnore is not None:
            ignoreAttributes = ignoreAttributes - removeFromIgnore

        if '_offsetDict' in ignoreAttributes:
            newValue = {}
            setattr(new, '_offsetDict', newValue)
        # all subclasses of Music21Object that define their own
        # __deepcopy__ methods must be sure to not try to copy activeSite
        if '_offsetDict' in self.__dict__:
            newValue = {}
            setattr(new, '_offsetDict', newValue)
        if 'streamStatus' in ignoreAttributes:
            # update the client
            if self.streamStatus is not None:
                # storedClient = self.streamStatus.client  # should be self
                # self.streamStatus.client = None
                newValue = copy.deepcopy(self.streamStatus)
                newValue.client = new
                setattr(new, 'streamStatus', newValue)
                # self.streamStatus.client = storedClient
        if '_elements' in ignoreAttributes:
            # must manually add elements to new Stream
            for e in self._elements:
                # environLocal.printDebug(['deepcopy()', e, 'old', old, 'id(old)', id(old),
                #     'new', new, 'id(new)', id(new), 'old.hasElement(e)', old.hasElement(e),
                #     'e.activeSite', e.activeSite, 'e.getSites()', e.getSites(), 'e.getSiteIds()',
                #     e.getSiteIds()], format='block')
                #
                # this will work for all with __deepcopy___
                # get the old offset from the activeSite Stream
                # user here to provide new offset
                #
                # new.insert(e.getOffsetBySite(old), newElement,
                #            ignoreSort=True)
                offset = self.elementOffset(e)
                if not e.isStream:
                    # noinspection PyArgumentList
                    newElement = copy.deepcopy(e, memo)
                else:  # this prevents needing to make multiple replacements of spanner bundles
                    newElement = e._deepcopySubclassable(memo)

                # ## TEST on copying!!!!
                # if 'Note' in newElement.classes:
                #     newElement.pitch.ps += 2.0
                new.coreInsert(offset, newElement, ignoreSort=True)
        if '_endElements' in ignoreAttributes:
            # must manually add elements to
            for e in self._endElements:
                # this will work for all with __deepcopy___
                # get the old offset from the activeSite Stream
                # user here to provide new offset

                # noinspection PyArgumentList
                new.coreStoreAtEnd(copy.deepcopy(e, memo))

        return new

    def __deepcopy__(self, memo=None):
        '''
        Deepcopy the stream from copy.deepcopy()
        '''
        # does not purgeOrphans -- q: is that a bug or by design?
        new = self._deepcopySubclassable(memo)
        self._replaceSpannerBundleForDeepcopy(new)

        # purging these orphans works in nearly all cases, but there are a few
        # cases where we rely on a Stream having access to Stream it was
        # part of after deepcopying
        # new.purgeOrphans()
        return new

    def _replaceSpannerBundleForDeepcopy(self, new):
        # perform the spanner bundle replacement on the outer stream.
        # caching this is CRUCIAL! using new.spannerBundle ever time below added
        # 40% to the test suite time!
        newSpannerBundle = new.spannerBundle
        # only proceed if there are spanners, otherwise creating semiFlat
        if not newSpannerBundle:
            return
        # iterate over complete semi-flat (need containers); find
        # all new/old pairs
        for e in new.recurse(includeSelf=False):
            # update based on id of old object, and ref to new object
            if 'Spanner' in e.classes:
                continue
            if e.derivation.method != '__deepcopy__':
                continue

            origin = e.derivation.origin
            if origin is None:  # pragma: no cover
                continue  # should not happen...

            if origin.sites.hasSpannerSite():
                # environLocal.printDebug(['Stream.__deepcopy__', 'replacing component to', e])
                # this will clear and replace the proper locations on
                # the SpannerStorage Stream
                newSpannerBundle.replaceSpannedElement(origin, e)

                # need to remove the old SpannerStorage Stream from this element;
                # however, all we have here is the new Spanner and new elements
                # this must be done here, not when originally copying
                e.purgeOrphans(excludeStorageStreams=False)

    def setElementOffset(
        self,
        element: base.Music21Object,
        offset: Union[int, float, Fraction, str],
        *,
        addElement=False,
        setActiveSite=True
    ):
        '''
        Sets the Offset for an element, very quickly.

        >>> s = stream.Stream()
        >>> s.id = 'Stream1'
        >>> n = note.Note('B-4')
        >>> s.insert(10, n)
        >>> n.offset
        10.0
        >>> s.setElementOffset(n, 20.0)
        >>> n.offset
        20.0
        >>> n.getOffsetBySite(s)
        20.0

        If the element is not in the Stream, raises a StreamException:

        >>> n2 = note.Note('D')
        >>> s.setElementOffset(n2, 30.0)
        Traceback (most recent call last):
        music21.exceptions21.StreamException: Cannot set the offset for element
            <music21.note.Note D>, not in Stream <music21.stream.Stream Stream1>.

        ...unless addElement is explicitly set to True (this is a core function that should NOT be
        used in normal situations.
        it is used by .insert() and .append() and other core functions; other things
        must also be done to
        properly add an element, such as append sites.)

        >>> n2 = note.Note('D')
        >>> s.setElementOffset(n2, 30.0, addElement=True)

        Changed in v5.5 -- also sets .activeSite for the element unless setActiveSite is False
        '''
        # Note: not documenting 'highestTime' is on purpose, since can only be done for
        # elements already stored at end.  Infinite loop.
        try:
            offset = opFrac(offset)
        except TypeError:
            if offset not in core.OFFSET_STRING_VALUES:  # pragma: no cover
                raise StreamException(f'Cannot set offset to {offset!r} for {element}')

        idEl = id(element)
        if not addElement and idEl not in self._offsetDict:
            raise StreamException(
                f'Cannot set the offset for element {element}, not in Stream {self}.')
        self._offsetDict[idEl] = (offset, element)  # fast
        if setActiveSite:
            self.coreSelfActiveSite(element)

    def elementOffset(self, element, stringReturns=False):
        '''
        Return the offset as an opFrac (float or Fraction) from the offsetMap.
        highly optimized for speed.

        >>> m = stream.Measure(number=1)
        >>> m.append(note.Note('C'))
        >>> d = note.Note('D')
        >>> m.append(d)
        >>> m.elementOffset(d)
        1.0

        If stringReturns is True then returns like 'highestOffset' are allowed.

        >>> b = bar.Barline()
        >>> m.storeAtEnd(b)
        >>> m.elementOffset(b)
        2.0
        >>> m.elementOffset(b, stringReturns=True)
        'highestTime'

        Unlike element.getOffsetBySite(self), this method will NOT follow derivation chains
        and in fact will raise a sites.SitesException

        >>> import copy
        >>> p = stream.Part(id='sPart')
        >>> p.insert(20, m)
        >>> m.getOffsetBySite(p)
        20.0
        >>> p.elementOffset(m)
        20.0

        >>> mCopy = copy.deepcopy(m)
        >>> mCopy.number = 10
        >>> mCopy.derivation
        <Derivation of <music21.stream.Measure 10 offset=0.0> from
            <music21.stream.Measure 1 offset=20.0> via '__deepcopy__'>
        >>> mCopy.getOffsetBySite(p)
        20.0
        >>> p.elementOffset(mCopy)
        Traceback (most recent call last):
        music21.sites.SitesException: an entry for this object 0x... is not stored in
            stream <music21.stream.Part sPart>

        Performance note: because it will not follow derivation chains, and does
        not need to unwrap a weakref, this method
        should usually be about 3x faster than element.getOffsetBySite(self) --
        currently 600ns instead of 1.5 microseconds.
        '''
        try:
            # 2.3 million times found in TestStream
            o = self._offsetDict[id(element)][0]
            # if returnedElement is not element:  # stale reference...
            #    o = None  #  0 in TestStream -- not worth testing
        except KeyError:  # 445k - 442,443 = 3k in TestStream
            for idElement in self._offsetDict:  # slower search
                o, returnedElement = self._offsetDict[idElement]
                if element is returnedElement:
                    break
            else:
                raise base.SitesException(
                    f'an entry for this object 0x{id(element):x} is not stored in stream {self}')

        if stringReturns is False and o in core.OFFSET_STRING_VALUES:
            try:
                return getattr(self, o)
            except AttributeError:  # pragma: no cover
                raise base.SitesException(
                    'attempted to retrieve a bound offset with a string '
                    + f'attribute that is not supported: {o}')
        else:
            return o

    def insert(self,
               offsetOrItemOrList,
               itemOrNone=None,
               *,
               ignoreSort=False,
               setActiveSite=True
               ):
        '''
        Inserts an item(s) at the given offset(s).

        If `ignoreSort` is True then the inserting does not
        change whether the Stream is sorted or not (much faster if you're
        going to be inserting dozens
        of items that don't change the sort status)

        The `setActiveSite` parameter should nearly always be True; only for
        advanced Stream manipulation would you not change
        the activeSite after inserting an element.

        Has three forms: in the two argument form, inserts an element at the given offset:

        >>> st1 = stream.Stream()
        >>> st1.insert(32, note.Note('B-'))
        >>> st1.highestOffset
        32.0

        In the single argument form with an object, inserts the element at its stored offset:

        >>> n1 = note.Note('C#')
        >>> n1.offset = 30.0
        >>> st1 = stream.Stream()
        >>> st1.insert(n1)
        >>> st2 = stream.Stream()
        >>> st2.insert(40.0, n1)
        >>> n1.getOffsetBySite(st1)
        30.0

        In single argument form with a list, the list should contain pairs that alternate
        offsets and items; the method then, obviously, inserts the items
        at the specified offsets:

        >>> n1 = note.Note('G')
        >>> n2 = note.Note('F#')
        >>> st3 = stream.Stream()
        >>> st3.insert([1.0, n1, 2.0, n2])
        >>> n1.getOffsetBySite(st3)
        1.0
        >>> n2.getOffsetBySite(st3)
        2.0
        >>> len(st3)
        2

        Raises an error if offset is not a number

        >>> stream.Stream().insert('l', note.Note('B'))
        Traceback (most recent call last):
        music21.exceptions21.StreamException: Offset 'l' must be a number.

        ...or if the object is not a music21 object (or a list of them)

        >>> stream.Stream().insert(3.3, 'hello')
        Traceback (most recent call last):
        music21.exceptions21.StreamException: to put a non Music21Object in a stream,
            create a music21.ElementWrapper for the item

        The error message is slightly different in the one-element form:

        >>> stream.Stream().insert('hello')
        Traceback (most recent call last):
        music21.exceptions21.StreamException: Cannot insert item 'hello' to
            stream -- is it a music21 object?
        '''
        # environLocal.printDebug(['self', self, 'offsetOrItemOrList',
        #              offsetOrItemOrList, 'itemOrNone', itemOrNone,
        #             'ignoreSort', ignoreSort, 'setActiveSite', setActiveSite])
        # normal approach: provide offset and item
        if itemOrNone is not None:
            offset = offsetOrItemOrList
            item = itemOrNone
        elif itemOrNone is None and isinstance(offsetOrItemOrList, list):
            i = 0
            while i < len(offsetOrItemOrList):
                offset = offsetOrItemOrList[i]
                item = offsetOrItemOrList[i + 1]
                # recursively calling insert() here
                self.insert(offset, item, ignoreSort=ignoreSort)
                i += 2
            return
        # assume first arg is item, and that offset is local offset of object
        else:
            item = offsetOrItemOrList
            # offset = item.offset
            # this is equivalent to:
            try:
                activeSite = item.activeSite
                offset = item.getOffsetBySite(activeSite)
            except AttributeError:
                raise StreamException(f'Cannot insert item {item!r} to stream '
                                      + '-- is it a music21 object?')

        # if not common.isNum(offset):
        try:  # using float conversion instead of isNum for performance
            offset = float(offset)
        except (ValueError, TypeError):
            if offset is None:
                offset = 0.0
            else:
                raise StreamException(f'Offset {offset!r} must be a number.')

        if not isinstance(item, base.Music21Object):
            raise StreamException('to put a non Music21Object in a stream, '
                                  + 'create a music21.ElementWrapper for the item')
        element = item

        # checks of element is self; possibly performs additional checks
        self.coreGuardBeforeAddElement(element)
        # main insert procedure here

        storeSorted = self.coreInsert(offset, element,
                                      ignoreSort=ignoreSort, setActiveSite=setActiveSite)
        updateIsFlat = False
        if element.isStream:
            updateIsFlat = True
        self.coreElementsChanged(updateIsFlat=updateIsFlat)
        if ignoreSort is False:
            self.isSorted = storeSorted

    def insertIntoNoteOrChord(self, offset, noteOrChord, chordsOnly=False):
        # noinspection PyShadowingNames
        '''
        Insert a Note or Chord into an offset position in this Stream.
        If there is another Note or Chord in this position,
        create a new Note or Chord that combines the pitches of the
        inserted chord. If there is a Rest in this position,
        the Rest is replaced by the Note or Chord. The duration of the
        previously-found chord will remain the same in the new Chord.

        >>> n1 = note.Note('D4')
        >>> n1.duration.quarterLength = 2.0
        >>> r1 = note.Rest()
        >>> r1.duration.quarterLength = 2.0
        >>> c1 = chord.Chord(['C4', 'E4'])
        >>> s = stream.Stream()
        >>> s.append(n1)
        >>> s.append(r1)
        >>> s.append(c1)
        >>> s.show('text')
        {0.0} <music21.note.Note D>
        {2.0} <music21.note.Rest rest>
        {4.0} <music21.chord.Chord C4 E4>

        Save the original Streams for later

        >>> import copy
        >>> s2 = copy.deepcopy(s)
        >>> s3 = copy.deepcopy(s)
        >>> s4 = copy.deepcopy(s)

        Notice that the duration of the inserted element is not taken into
        consideration and the original element is not broken up,
        as it would be in chordify().  But Chords and Notes are created...

        >>> for i in [0.0, 2.0, 4.0]:
        ...     s.insertIntoNoteOrChord(i, note.Note('F#4'))
        >>> s.show('text')
        {0.0} <music21.chord.Chord D4 F#4>
        {2.0} <music21.note.Note F#>
        {4.0} <music21.chord.Chord C4 E4 F#4>

        if chordsOnly is set to True then no notes are returned, only chords, but
        untouched notes are left alone:

        >>> s2.insert(5.0, note.Note('E##4'))
        >>> for i in [0.0, 2.0, 4.0]:
        ...     s2.insertIntoNoteOrChord(i, note.Note('F#4'), chordsOnly=True)
        >>> s2.show('text')
        {0.0} <music21.chord.Chord D4 F#4>
        {2.0} <music21.chord.Chord F#4>
        {4.0} <music21.chord.Chord C4 E4 F#4>
        {5.0} <music21.note.Note E##>

        A chord inserted on top of a note always changes the note into a chord:

        >>> s2.insertIntoNoteOrChord(5.0, chord.Chord('F#4 G-4'))
        >>> s2.show('text')
        {0.0} <music21.chord.Chord D4 F#4>
        {2.0} <music21.chord.Chord F#4>
        {4.0} <music21.chord.Chord C4 E4 F#4>
        {5.0} <music21.chord.Chord E##4 F#4 G-4>

        Chords can also be inserted into rests:

        >>> s3.getElementsByOffset(2.0)[0]
        <music21.note.Rest rest>
        >>> s3.insertIntoNoteOrChord(2.0, chord.Chord('C4 E4 G#4'))
        >>> s3.show('text')
        {0.0} <music21.note.Note D>
        {2.0} <music21.chord.Chord C4 E4 G#4>
        {4.0} <music21.chord.Chord C4 E4>

        Despite the variable name, a rest could be inserted into a noteOrChord.
        It does nothing to existing notes or chords, and just adds a new rest
        afterwards.

        >>> s4.show('text', addEndTimes=True)
        {0.0 - 2.0} <music21.note.Note D>
        {2.0 - 4.0} <music21.note.Rest rest>
        {4.0 - 5.0} <music21.chord.Chord C4 E4>

        >>> for i in [0.0, 4.0, 6.0]:  # skipping 2.0 for now
        ...     r = note.Rest(type='quarter')
        ...     s4.insertIntoNoteOrChord(i, r)
        >>> r2 = note.Rest(type='quarter')
        >>> s4.insertIntoNoteOrChord(2.0, r)
        >>> s4.show('text', addEndTimes=True)
        {0.0 - 2.0} <music21.note.Note D>
        {2.0 - 4.0} <music21.note.Rest rest>
        {4.0 - 5.0} <music21.chord.Chord C4 E4>
        {6.0 - 7.0} <music21.note.Rest rest>

        Notice that (1) the original duration and not the new duration is used, unless
        there is no element at that place, and (2) if an element is put into a place where
        no existing element was found, then it will be found in the new Stream, but if it
        is placed on top of an existing element, the original element or a new copy will remain:

        >>> r in s4
        True
        >>> r2 in s4
        False

        If a Stream has more than one note, chord, or rest at that position,
        currently an error is raised.  This may change later:

        >>> s5 = stream.Stream()
        >>> s5.insert(0, note.Note('C##4'))
        >>> s5.insert(0, note.Note('E--4'))
        >>> s5.insertIntoNoteOrChord(0, note.Note('D4'))
        Traceback (most recent call last):
        music21.exceptions21.StreamException: more than one element found at the specified offset
        '''
        # could use duration of Note to get end offset span
        targets = list(
            self.iter.getElementsByOffset(
                offset,
                offset + noteOrChord.quarterLength,  # set end to dur of supplied
                includeEndBoundary=False,
                mustFinishInSpan=False,
                mustBeginInSpan=True
            ).notesAndRests
        )
        removeTarget = None
        # environLocal.printDebug(['insertIntoNoteOrChord', [e for e in targets]])
        if len(targets) == 1:
            pitches = []      # avoid an undefined variable warning...
            components = []   # ditto

            target = targets[0]  # assume first
            removeTarget = target
            if 'Rest' in target.classes:
                if 'Note' in noteOrChord.classes:
                    pitches = [noteOrChord.pitch]
                    components = [noteOrChord]
                elif 'Chord' in noteOrChord.classes:
                    pitches = list(noteOrChord.pitches)
                    components = list(noteOrChord)
            if 'Note' in target.classes:
                # if a note, make it into a chord
                if 'Note' in noteOrChord.classes:
                    pitches = [target.pitch, noteOrChord.pitch]
                    components = [target, noteOrChord]
                elif 'Chord' in noteOrChord.classes:
                    pitches = [target.pitch] + list(noteOrChord.pitches)
                    components = [target] + list(noteOrChord)
                else:
                    pitches = [target.pitch]
                    components = [target]
            if 'Chord' in target.classes:
                # if a chord, make it into a chord
                if 'Note' in noteOrChord.classes:
                    pitches = list(target.pitches) + [noteOrChord.pitch]
                    components = list(target) + [noteOrChord]
                elif 'Chord' in noteOrChord.classes:
                    pitches = list(target.pitches) + list(noteOrChord.pitches)
                    components = list(target) + list(noteOrChord)
                else:
                    pitches = list(target.pitches)
                    components = list(target)

            if len(pitches) > 1 or chordsOnly is True:
                finalTarget = chord.Chord(pitches)
            elif len(pitches) == 1:
                finalTarget = note.Note(pitches[0])
            else:
                finalTarget = note.Rest()

            finalTarget.expressions = target.expressions
            finalTarget.articulations = target.articulations
            finalTarget.duration = target.duration
            # append lyrics list
            if hasattr(target, 'lyrics'):
                for ly in target.lyrics:
                    if ly.text not in ('', None):
                        finalTarget.addLyric(ly.text)
            # finalTarget.lyrics = target.lyrics
            if hasattr(finalTarget, 'stemDirection') and hasattr(target, 'stemDirection'):
                finalTarget.stemDirection = target.stemDirection
            if hasattr(finalTarget, 'noteheadFill') and hasattr(target, 'noteheadFill'):
                finalTarget.noteheadFill = target.noteheadFill

            # fill component details
            if 'Chord' in finalTarget.classes:
                for i, n in enumerate(finalTarget):
                    nPrevious = components[i]
                    n.noteheadFill = nPrevious.noteheadFill

        elif len(targets) > 1:
            raise StreamException('more than one element found at the specified offset')
        else:
            finalTarget = noteOrChord

        if removeTarget is not None:
            self.remove(removeTarget)
        # insert normally, nothing to handle
        self.insert(offset, finalTarget, ignoreSort=False, setActiveSite=True)

    def append(self, others):
        '''
        Add a Music21Object (including another Stream) to the end of the current Stream.

        If given a list, will append each element in order after the previous one.

        The "end" of the stream is determined by the `highestTime` property
        (that is the latest "release" of an object, or directly after the last
        element ends).

        Runs fast for multiple addition and will preserve isSorted if True

        >>> a = stream.Stream()
        >>> notes = []
        >>> for x in range(3):
        ...     n = note.Note('G#')
        ...     n.duration.quarterLength = 3
        ...     notes.append(n)
        >>> a.append(notes[0])
        >>> a.highestOffset, a.highestTime
        (0.0, 3.0)
        >>> a.append(notes[1])
        >>> a.highestOffset, a.highestTime
        (3.0, 6.0)
        >>> a.append(notes[2])
        >>> a.highestOffset, a.highestTime
        (6.0, 9.0)
        >>> notes2 = []

        Notes' naive offsets will
        change when they are added to a stream.

        >>> for x in range(3):
        ...     n = note.Note('A-')
        ...     n.duration.quarterLength = 3
        ...     n.offset = 0
        ...     notes2.append(n)
        >>> a.append(notes2)  # add em all again
        >>> a.highestOffset, a.highestTime
        (15.0, 18.0)
        >>> a.isSequence()
        True

        Adding a note that already has an offset set does nothing different
        from above! That is, it is still added to the end of the Stream:

        >>> n3 = note.Note('B-')
        >>> n3.offset = 1
        >>> n3.duration.quarterLength = 3
        >>> a.append(n3)
        >>> a.highestOffset, a.highestTime
        (18.0, 21.0)
        >>> n3.getOffsetBySite(a)
        18.0

        Prior to v5.7 there was a bug where appending a `Clef` after a `KeySignature`
        or a `Measure` after a `KeySignature`, etc. would not cause sorting to be re-run.
        This bug is now fixed.

        >>> s = stream.Stream()
        >>> s.append([meter.TimeSignature('4/4'),
        ...           clef.TrebleClef()])
        >>> s.elements[0]
        <music21.clef.TrebleClef>
        >>> s.show('text')
        {0.0} <music21.clef.TrebleClef>
        {0.0} <music21.meter.TimeSignature 4/4>

        >>> s.append(metadata.Metadata(composer='Cage'))
        >>> s.show('text')
        {0.0} <music21.metadata.Metadata object at 0x11ca356a0>
        {0.0} <music21.clef.TrebleClef>
        {0.0} <music21.meter.TimeSignature 4/4>
        '''
        # store and increment highest time for insert offset
        highestTime = self.highestTime
        if not common.isListLike(others):
            # back into a list for list processing if single
            others = [others]

        clearIsSorted = False
        if self._elements:
            lastElement = self._elements[-1]
        else:
            lastElement = None

        updateIsFlat = False
        for e in others:
            try:
                if e.isStream:  # any on that is a Stream req update
                    updateIsFlat = True
            except AttributeError:
                raise StreamException(
                    f'The object you tried to add to the Stream, {e!r}, '
                    + 'is not a Music21Object.  Use an ElementWrapper object '
                    + 'if this is what you intend')
            self.coreGuardBeforeAddElement(e)
            # add this Stream as a location for the new elements, with the
            # the offset set to the current highestTime
            self.setElementOffset(e, highestTime, addElement=True)
            e.sites.add(self)
            # need to explicitly set the activeSite of the element
            self.coreSelfActiveSite(e)
            self._elements.append(e)

            if e.duration.quarterLength != 0:
                # environLocal.printDebug(['incrementing highest time',
                #                         'e.duration.quarterLength',
                #                          e.duration.quarterLength])
                highestTime += e.duration.quarterLength
            if lastElement is not None and not lastElement.duration.quarterLength:
                if (e.priority < lastElement.priority
                        or e.classSortOrder < lastElement.classSortOrder):
                    clearIsSorted = True
            lastElement = e

        # does not normally change sorted state
        if clearIsSorted:
            storeSorted = False
        else:
            storeSorted = self.isSorted

        # we cannot keep the index cache here b/c we might
        self.coreElementsChanged(updateIsFlat=updateIsFlat)
        self.isSorted = storeSorted
        self._setHighestTime(opFrac(highestTime))  # call after to store in cache

    def storeAtEnd(self, itemOrList, ignoreSort=False):
        '''
        Inserts an item or items at the end of the Stream,
        stored in the special box (called _endElements).

        This method is useful for putting things such as
        right bar lines or courtesy clefs that should always
        be at the end of a Stream no matter what else is appended
        to it.

        As sorting is done only by priority and class,
        it cannot avoid setting isSorted to False.

        >>> s = stream.Stream()
        >>> b = bar.Repeat()
        >>> s.storeAtEnd(b)
        >>> b in s
        True
        >>> s.elementOffset(b)
        0.0
        >>> s.elementOffset(b, stringReturns=True)
        'highestTime'

        Only elements of zero duration can be stored.  Otherwise a
        `StreamException` is raised.
        '''
        if isinstance(itemOrList, list):
            for item in itemOrList:
                # recursively calling insert() here
                self.storeAtEnd(item, ignoreSort=ignoreSort)
            return
        else:
            item = itemOrList

        try:
            unused = item.isStream  # will raise attribute error
            element = item
        except AttributeError:  # need to wrap
            # element = music21.ElementWrapper(item)
            raise StreamException('to put a non Music21Object in a stream, '
                                  + 'create a music21.ElementWrapper for the item')
        # # if not a Music21Object, embed
        # if not isinstance(item, music21.Music21Object):
        #     element = music21.ElementWrapper(item)
        # else:
        #     element = item

        # cannot support elements with Durations in the highest time list
        if element.duration.quarterLength != 0:
            raise StreamException('cannot insert an object with a non-zero '
                                  + 'Duration into the highest time elements list')

        # checks of element is self; possibly performs additional checks
        self.coreGuardBeforeAddElement(element)

        # element.sites.add(self, 'highestTime')
        # # need to explicitly set the activeSite of the element
        # self.coreSelfActiveSite(element)
        # self._endElements.append(element)

        self.coreStoreAtEnd(element)
        # Streams cannot reside in end elements, thus do not update is flat
        self.coreElementsChanged(updateIsFlat=False)

    # --------------------------------------------------------------------------
    # all the following call either insert() or append()

    def insertAndShift(self, offsetOrItemOrList, itemOrNone=None):
        '''
        Insert an item at a specified or native offset,
        and shift any elements found in the Stream to start at
        the end of the added elements.

        This presently does not shift elements that have durations
        that extend into the lowest insert position.

        >>> st1 = stream.Stream()
        >>> st1.insertAndShift(32, note.Note('B'))
        >>> st1.highestOffset
        32.0
        >>> st1.insertAndShift(32, note.Note('C'))
        >>> st1.highestOffset
        33.0
        >>> st1.show('text', addEndTimes=True)
        {32.0 - 33.0} <music21.note.Note C>
        {33.0 - 34.0} <music21.note.Note B>

        Let's insert an item at the beginning, note that
        since the C and B are not affected, they do not shift.

        >>> st1.insertAndShift(0, note.Note('D'))
        >>> st1.show('text', addEndTimes=True)
        {0.0 - 1.0} <music21.note.Note D>
        {32.0 - 33.0} <music21.note.Note C>
        {33.0 - 34.0} <music21.note.Note B>

        But if we insert something again at the beginning of the stream,
        everything after the first shifted note begins shifting, so the
        C and the B shift even though there is a gap there.  Normally
        there's no gaps in a stream, so this will not be a factor:

        >>> st1.insertAndShift(0, note.Note('E'))
        >>> st1.show('text', addEndTimes=True)
        {0.0 - 1.0} <music21.note.Note E>
        {1.0 - 2.0} <music21.note.Note D>
        {33.0 - 34.0} <music21.note.Note C>
        {34.0 - 35.0} <music21.note.Note B>

        In the single argument form with an object, inserts the element at its stored offset:

        >>> n1 = note.Note('C#')
        >>> n1.offset = 30.0
        >>> n2 = note.Note('D#')
        >>> n2.offset = 30.0
        >>> st1 = stream.Stream()
        >>> st1.insertAndShift(n1)
        >>> st1.insertAndShift(n2)  # will shift offset of n1
        >>> n1.getOffsetBySite(st1)
        31.0
        >>> n2.getOffsetBySite(st1)
        30.0
        >>> st1.show('text', addEndTimes=True)
        {30.0 - 31.0} <music21.note.Note D#>
        {31.0 - 32.0} <music21.note.Note C#>

        >>> st2 = stream.Stream()
        >>> st2.insertAndShift(40.0, n1)
        >>> st2.insertAndShift(40.0, n2)
        >>> n1.getOffsetBySite(st2)
        41.0

        In single argument form with a list, the list should contain pairs that alternate
        offsets and items; the method then, obviously, inserts the items
        at the specified offsets:

        >>> n1 = note.Note('G-')
        >>> n2 = note.Note('F-')
        >>> st3 = stream.Stream()
        >>> st3.insertAndShift([1.0, n1, 2.0, n2])
        >>> n1.getOffsetBySite(st3)
        1.0
        >>> n2.getOffsetBySite(st3)
        2.0
        >>> len(st3)
        2
        >>> st3.show('text', addEndTimes=True)
        {1.0 - 2.0} <music21.note.Note G->
        {2.0 - 3.0} <music21.note.Note F->

        N.B. -- using this method on a list assumes that you'll be inserting
        contiguous objects; you can't shift things that are separated, as this
        following FAILED example shows.

        >>> n1 = note.Note('G', type='half')
        >>> st4 = stream.Stream()
        >>> st4.repeatAppend(n1, 3)
        >>> st4.insertAndShift([2.0, note.Note('e'), 4.0, note.Note('f')])
        >>> st4.show('text')
        {0.0} <music21.note.Note G>
        {2.0} <music21.note.Note E>
        {4.0} <music21.note.Note F>
        {5.0} <music21.note.Note G>
        {7.0} <music21.note.Note G>

        As an FYI, there is no removeAndShift() function, so the opposite of
        insertAndShift(el) is remove(el, shiftOffsets=True).
        '''
        # need to find the highest time after the insert
        if itemOrNone is not None:  # we have an offset and an element
            insertObject = itemOrNone
            qL = insertObject.duration.quarterLength
            offset = offsetOrItemOrList
            lowestOffsetInsert = offset
            highestTimeInsert = offset + qL
        elif itemOrNone is None and isinstance(offsetOrItemOrList, list):
            # need to find which has the highest endtime (combined offset and dur)
            insertList = offsetOrItemOrList
            highestTimeInsert = 0.0
            lowestOffsetInsert = None
            i = 0
            while i < len(insertList):
                o = insertList[i]
                e = insertList[i + 1]
                qL = e.duration.quarterLength
                if o + qL > highestTimeInsert:
                    highestTimeInsert = o + qL
                if lowestOffsetInsert is None or o < lowestOffsetInsert:
                    lowestOffsetInsert = o
                i += 2
        else:  # using native offset
            # if hasattr(offsetOrItemOrList, 'duration'):
            insertObject = offsetOrItemOrList
            qL = insertObject.duration.quarterLength
            # should this be getOffsetBySite(None)?
            highestTimeInsert = insertObject.offset + qL
            lowestOffsetInsert = insertObject.offset

        # this shift is the additional time to move due to the duration
        # of the newly inserted elements

        # environLocal.printDebug(['insertAndShift()',
        #                         'adding one or more elements',
        #                         'lowestOffsetInsert', lowestOffsetInsert,
        #                         'highestTimeInsert', highestTimeInsert])

        # are not assuming that elements are ordered
        # use getElementAtOrAfter() in the future
        lowestElementToShift = None
        lowestGap = None
        for e in self._elements:
            o = self.elementOffset(e)
            # gap is distance from offset to insert point; tells if shift is
            # necessary
            gap = o - lowestOffsetInsert
            if gap < 0:  # no shifting necessary
                continue
            # only process elements whose offsets are after the lowest insert
            if lowestGap is None or gap < lowestGap:
                lowestGap = gap
                lowestElementToShift = e

        if lowestElementToShift is not None:
            lowestOffsetToShift = self.elementOffset(lowestElementToShift)
            shiftPos = highestTimeInsert - lowestOffsetToShift
        else:
            shiftPos = 0

        if shiftPos <= 0:
            pass  # no need to move any objects
        # See testStream.py - testInsertAndShiftNoDuration clef
        #      insertion at offset 3 which gives shiftPos < 0
        else:
            # need to move all the elements already in this stream
            for e in self._elements:
                o = self.elementOffset(e)
                # gap is distance from offset to insert point; tells if shift is
                # necessary
                gap = o - lowestOffsetInsert
                # only process elements whose offsets are after the lowest insert
                if gap >= 0.0:
                    # environLocal.printDebug(['insertAndShift()', e, 'offset', o,
                    #                         'gap:', gap, 'shiftDur:', shiftDur,
                    #                         'shiftPos:', shiftPos, 'o+shiftDur', o+shiftDur,
                    #                         'o+shiftPos', o+shiftPos])

                    # need original offset, shiftDur, plus the distance from the start
                    self.setElementOffset(e, o + shiftPos)
        # after shifting all the necessary elements, append new ones
        # these will not be in order
        self.insert(offsetOrItemOrList, itemOrNone)
        # call this is elements are now out of order
        self.coreElementsChanged()

    # --------------------------------------------------------------------------
    # searching and replacing routines

    def setDerivationMethod(self, derivationMethod, recurse=False):
        '''
        Sets the .derivation.method for each element in the Stream
        if it has a .derivation object.

        >>> import copy
        >>> s = converter.parse('tinyNotation: 2/4 c2 d e f')
        >>> s2 = copy.deepcopy(s)
        >>> s2.recurse().notes[-1].derivation
        <Derivation of <music21.note.Note F> from <music21.note.Note F> via '__deepcopy__'>
        >>> s2.setDerivationMethod('exampleCopy', recurse=True)
        >>> s2.recurse().notes[-1].derivation
        <Derivation of <music21.note.Note F> from <music21.note.Note F> via 'exampleCopy'>

        Without recurse:

        >>> s = converter.parse('tinyNotation: 2/4 c2 d e f')
        >>> s2 = copy.deepcopy(s)
        >>> s2.setDerivationMethod('exampleCopy')
        >>> s2.recurse().notes[-1].derivation
        <Derivation of <music21.note.Note F> from <music21.note.Note F> via '__deepcopy__'>
        '''
        if recurse:
            sIter = self.recurse()
        else:
            sIter = self.iter

        for el in sIter:
            if el.derivation is not None:
                el.derivation.method = derivationMethod

    def replace(self,
                target: base.Music21Object,
                replacement: base.Music21Object,
                *,
                recurse: bool = False,
                allDerived: bool = True) -> None:
        '''
        Given a `target` object, replace it with
        the supplied `replacement` object.

        Does nothing if target cannot be found.

        If `allDerived` is True (as it is by default), all sites (stream) that
        this this stream derives from and also
        have a reference for the replacement will be similarly changed.
        This is useful for altering both a flat and nested representation.

        >>> cSharp = note.Note('C#4')
        >>> s = stream.Stream()
        >>> s.insert(0, cSharp)
        >>> dFlat = note.Note('D-4')
        >>> s.replace(cSharp, dFlat)
        >>> s.show('t')
        {0.0} <music21.note.Note D->

        If allDerived is True then all streams that this stream comes from get changed
        (but not non-derived streams)

        >>> otherStream = stream.Stream()
        >>> otherStream.insert(0, dFlat)
        >>> f = note.Note('F4')
        >>> sf = s.flat
        >>> sf is not s
        True
        >>> sf.replace(dFlat, f, allDerived=True)
        >>> sf[0] is f
        True
        >>> s[0] is f
        True
        >>> otherStream[0] is dFlat
        True

        Note that it does not work the other way: if we made the replacement on `s`
        then `sf`, the flattened representation, would not be changed, since `s`
        does not derive from `sf` but vice-versa.

        With `recurse=True`, a stream can replace an element that is
        further down in the hierarchy.  First let's set up a
        nested score:

        >>> s = stream.Score()
        >>> p = stream.Part(id='part1')
        >>> s.append(p)
        >>> m = stream.Measure()
        >>> p.append(m)
        >>> cSharp = note.Note('C#4')
        >>> m.append(cSharp)
        >>> s.show('text')
        {0.0} <music21.stream.Part part1>
            {0.0} <music21.stream.Measure 0 offset=0.0>
                {0.0} <music21.note.Note C#>

        Now make a deep-nested replacement

        >>> dFlat = note.Note('D-4')
        >>> s.replace(cSharp, dFlat, recurse=True)
        >>> s.show('text')
        {0.0} <music21.stream.Part part1>
            {0.0} <music21.stream.Measure 0 offset=0.0>
                {0.0} <music21.note.Note D->

        Changed by v.5:

        allTargetSites RENAMED to allDerived -- only searches in derivation chain.

        Changed in v5.3 -- firstMatchOnly removed -- impossible to have element
        in stream twice.  recurse and shiftOffsets changed to keywordOnly arguments

        Changed in v6 -- recurse works
        '''
        def replaceDerived(startSite=self):
            if not allDerived:
                return
            for derivedSite in startSite.derivation.chain():
                for subsite in derivedSite.recurse(streamsOnly=True, includeSelf=True):
                    if subsite in target.sites:
                        subsite.replace(target,
                                        replacement,
                                        recurse=recurse,
                                        allDerived=False)

        try:
            i = self.index(target)
        except StreamException:
            if recurse:
                container = self.containerInHierarchy(target, setActiveSite=False)
                if container is not None:
                    container.replace(target, replacement, allDerived=allDerived)
                replaceDerived()
                if container is not None:
                    replaceDerived(startSite=container)
            return  # do nothing if no match

        eLen = len(self._elements)
        if i < eLen:
            target = self._elements[i]  # target may have been obj id; re-classing
            self._elements[i] = replacement
            # place the replacement at the old objects offset for this site
            self.setElementOffset(replacement, self.elementOffset(target), addElement=True)
            replacement.sites.add(self)
        else:
            # target may have been obj id; reassign
            target = self._endElements[i - eLen]
            self._endElements[i - eLen] = replacement

            self.setElementOffset(replacement, 'highestTime', addElement=True)
            replacement.sites.add(self)

        target.sites.remove(self)
        target.activeSite = None
        if id(target) in self._offsetDict:
            del(self._offsetDict[id(target)])

        updateIsFlat = False
        if replacement.isStream:
            updateIsFlat = True
        # elements have changed: sort order may change b/c have diff classes
        self.coreElementsChanged(updateIsFlat=updateIsFlat)

        replaceDerived()

    def splitAtQuarterLength(self,
                             quarterLength,
                             retainOrigin=True,
                             addTies=True,
                             displayTiedAccidentals=False,
                             searchContext=True):
        '''
        This method overrides the method on Music21Object to provide
        similar functionality for Streams.

        Most arguments are passed to Music21Object.splitAtQuarterLength.
        '''
        # pylint: disable=attribute-defined-outside-init
        quarterLength = opFrac(quarterLength)
        if retainOrigin:
            sLeft = self
        else:
            sLeft = copy.deepcopy(self)
        # create empty container for right-hand side
        sRight = self.__class__()

        # if this is a Measure or Part, transfer clefs, ts, and key
        if sLeft.isMeasure:
            timeSignatures = sLeft.getTimeSignatures(
                searchContext=searchContext)
            if timeSignatures:
                sRight.keySignature = copy.deepcopy(timeSignatures[0])
            keySignatures = sLeft.getKeySignatures(searchContext=searchContext)
            if keySignatures:
                sRight.keySignature = copy.deepcopy(keySignatures[0])
            endClef = sLeft.getContextByClass('Clef')
            if endClef is not None:
                sRight.clef = copy.deepcopy(endClef)

        if quarterLength > sLeft.highestTime:  # nothing to do
            return sLeft, sRight

        # use quarterLength as start time
        targets = sLeft.getElementsByOffset(
            quarterLength,
            sLeft.highestTime,
            includeEndBoundary=True,
            mustFinishInSpan=False,
            includeElementsThatEndAtStart=False,
            mustBeginInSpan=False
        )

        targetSplit = []
        targetMove = []
        # find all those that need to split v. those that need to be moved
        for t in targets:
            # if target starts before the boundary, it needs to be split
            if sLeft.elementOffset(t) < quarterLength:
                targetSplit.append(t)
            else:
                targetMove.append(t)

        # environLocal.printDebug(['split', targetSplit, 'move', targetMove])

        for t in targetSplit:
            # must retain original, as a deepcopy, if necessary, has
            # already been made

            # the split point needs to be relative to this element's start
            qlSplit = quarterLength - sLeft.elementOffset(t)
            unused_eLeft, eRight = t.splitAtQuarterLength(
                qlSplit,
                retainOrigin=True,
                addTies=addTies,
                displayTiedAccidentals=displayTiedAccidentals)
            # do not need to insert eLeft, as already positioned and
            # altered in-place above
            # it is assumed that anything cut will start at zero
            sRight.insert(0, eRight)

        for t in targetMove:
            sRight.insert(t.getOffsetBySite(sLeft) - quarterLength, t)
            sLeft.remove(t)

        return sLeft, sRight

    # --------------------------------------------------------------------------
    def _recurseRepr(self,
                     thisStream,
                     prefixSpaces=0,
                     addBreaks=True,
                     addIndent=True,
                     addEndTimes=False,
                     useMixedNumerals=False):
        '''
        Used by .show('text') to display a stream's contents with offsets.

        >>> s1 = stream.Stream()
        >>> s2 = stream.Stream()
        >>> s3 = stream.Stream()
        >>> n1 = note.Note()
        >>> s3.append(n1)
        >>> s2.append(s3)
        >>> s1.append(s2)
        >>> post = s1._recurseRepr(s1, addBreaks=False, addIndent=False)
        >>> post
        '{0.0} <music21.stream.Stream ...> / {0.0} <...> / {0.0} <music21.note.Note C>'
        '''
        def singleElement(in_element,
                          in_indent,
                          ) -> str:
            offGet = in_element.getOffsetBySite(thisStream)
            if useMixedNumerals:
                off = common.mixedNumeral(offGet)
            else:
                off = common.strTrimFloat(offGet)
            if addEndTimes is False:
                return in_indent + '{' + off + '} ' + repr(in_element)
            else:
                ql = offGet + in_element.duration.quarterLength
                if useMixedNumerals:
                    qlStr = common.mixedNumeral(ql)
                else:
                    qlStr = common.strTrimFloat(ql)
                return in_indent + '{' + off + ' - ' + qlStr + '} ' + repr(in_element)

        msg = []
        insertSpaces = 4
        for element in thisStream:
            if addIndent:
                indent = ' ' * prefixSpaces
            else:
                indent = ''

            # if isinstance(element, Stream):
            if element.isStream:
                msg.append(singleElement(element, indent))
                msg.append(self._recurseRepr(element,
                                             prefixSpaces + insertSpaces,
                                             addBreaks=addBreaks,
                                             addIndent=addIndent,
                                             addEndTimes=addEndTimes,
                                             useMixedNumerals=useMixedNumerals))
            else:
                msg.append(singleElement(element, indent))
        if addBreaks:
            msg = '\n'.join(msg)
        else:  # use slashes with spaces
            msg = ' / '.join(msg)
        return msg

    def _reprText(self, **keywords):
        '''
        Return a text representation. This methods can be overridden by
        subclasses to provide alternative text representations.

        This is used by .show('text')
        '''
        if 'addEndTimes' in keywords:
            addEndTimes = keywords['addEndTimes']
        else:
            addEndTimes = False
        if 'useMixedNumerals' in keywords:
            useMixedNumerals = keywords['useMixedNumerals']
        else:
            useMixedNumerals = False

        return self._recurseRepr(self,
                                 addEndTimes=addEndTimes,
                                 useMixedNumerals=useMixedNumerals)

    def _reprTextLine(self, **keywords):
        '''
        Return a text representation without line breaks.
        This methods can be overridden by subclasses to
        provide alternative text representations.
        '''
        if 'addEndTimes' in keywords:
            addEndTimes = keywords['addEndTimes']
        else:
            addEndTimes = False
        if 'useMixedNumerals' in keywords:
            useMixedNumerals = keywords['useMixedNumerals']
        else:
            useMixedNumerals = False

        return self._recurseRepr(self,
                                 addEndTimes=addEndTimes,
                                 useMixedNumerals=useMixedNumerals,
                                 addBreaks=False,
                                 addIndent=False)

    # --------------------------------------------------------------------------
    # display methods; in the same manner as show() and write()

    def plot(self, *args, **keywords):
        '''
        Given a method and keyword configuration arguments, create and display a plot.

        Note: plot() requires the Python package matplotlib to be installed.

        For details on arguments this function takes, see
        :ref:`User's Guide, Chapter 22: Graphing <usersGuide_22_graphing>`.

        >>> s = corpus.parse('demos/two-parts.xml') #_DOCS_HIDE
        >>> s.plot('pianoroll', doneAction=None) #_DOCS_HIDE
        >>> #_DOCS_SHOW s = corpus.parse('bach/bwv57.8')
        >>> #_DOCS_SHOW s.plot('pianoroll')

        .. image:: images/HorizontalBarPitchSpaceOffset.*
            :width: 600

        '''
        # import is here to avoid import of matplotlib problems
        from music21 import graph
        # first ordered arg can be method type
        graph.plotStream(self, *args, **keywords)

    def analyze(self, *args, **keywords):
        '''
        Runs a particular analytical method on the contents of the
        stream to find its ambitus (range) or key.

        *  ambitus -- runs :class:`~music21.analysis.discrete.Ambitus`
        *  key -- runs :class:`~music21.analysis.discrete.KrumhanslSchmuckler`

        Some of these methods can take additional arguments.  For details on
        these arguments, see
        :func:`~music21.analysis.discrete.analyzeStream`.

        Example:


        >>> s = corpus.parse('bach/bwv66.6')
        >>> s.analyze('ambitus')
        <music21.interval.Interval m21>
        >>> s.analyze('key')
        <music21.key.Key of f# minor>

        Example: music21 allows you to automatically run an
        analysis to get the key of a piece or excerpt not
        based on the key signature but instead on the
        frequency with which some notes are used as opposed
        to others (first described by Carol Krumhansl).  For
        instance, a piece with mostly Cs and Gs, some Fs,
        and Ds, but fewer G#s, C#s, etc. is more likely to
        be in the key of C major than in D-flat major
        (or A minor, etc.).  You can easily get this analysis
        from a stream by running:

        >>> myStream = corpus.parse('luca/gloria')
        >>> analyzedKey = myStream.analyze('key')
        >>> analyzedKey
        <music21.key.Key of F major>

        analyzedKey is a :class:`~music21.key.Key`
        object with a few extra parameters.
        correlationCoefficient shows how well this key fits the
        profile of a piece in that key:

        >>> analyzedKey.correlationCoefficient
        0.86715...

        `alternateInterpretations` is a list of the other
        possible interpretations sorted from most likely to least:

        >>> analyzedKey.alternateInterpretations
        [<music21.key.Key of d minor>,
         <music21.key.Key of C major>,
         <music21.key.Key of g minor>,
         ...]

        Each of these can be examined in turn to see its correlation coefficient:

        >>> analyzedKey.alternateInterpretations[1].correlationCoefficient
        0.788528...
        >>> analyzedKey.alternateInterpretations[22].correlationCoefficient
        -0.86728...
        '''

        from music21.analysis import discrete
        # pass this stream to the analysis procedure
        return discrete.analyzeStream(self, *args, **keywords)

    # --------------------------------------------------------------------------
    # methods that act on individual elements without requiring
    # @ coreElementsChanged to fire

    def addGroupForElements(self, group, classFilter=None):
        '''
        Add the group to the groups attribute of all elements.
        if `classFilter` is set then only those elements whose objects
        belong to a certain class (or for Streams which are themselves of
        a certain class) are set.

        >>> a = stream.Stream()
        >>> a.repeatAppend(note.Note('A-'), 30)
        >>> a.repeatAppend(note.Rest(), 30)
        >>> a.addGroupForElements('flute')
        >>> a[0].groups
        ['flute']
        >>> a.addGroupForElements('quietTime', note.Rest)
        >>> a[0].groups
        ['flute']
        >>> a[50].groups
        ['flute', 'quietTime']
        >>> a[1].groups.append('quietTime')  # set one note to it
        >>> a[1].step = 'B'
        >>> b = a.getElementsByGroup('quietTime')
        >>> len(b)
        31
        >>> c = b.getElementsByClass(note.Note)
        >>> len(c)
        1
        >>> c[0].name
        'B-'
        '''
        sIterator = self.iter
        if classFilter is not None:
            sIterator = sIterator.addFilter(filters.ClassFilter(classFilter))
        for el in sIterator:
            el.groups.append(group)

    # --------------------------------------------------------------------------
    # getElementsByX(self): anything that returns a collection of Elements
    #  formerly always returned a Stream; turning to Iterators in September 2015

    def getElementsByClass(self, classFilterList) -> iterator.StreamIterator:
        '''
        Return a StreamIterator that will iterate over Elements that match one
        or more classes in the `classFilterList`. A single class
        can also used for the `classFilterList` parameter instead of a List.

        >>> a = stream.Score()
        >>> a.repeatInsert(note.Rest(), list(range(10)))
        >>> for x in range(4):
        ...     n = note.Note('G#')
        ...     n.offset = x * 3
        ...     a.insert(n)
        >>> found = a.getElementsByClass(note.Note)
        >>> found
        <music21.stream.iterator.StreamIterator for Score:0x104f2f400 @:0>

        >>> len(found)
        4
        >>> found[0].pitch.accidental.name
        'sharp'

        >>> foundStream = found.stream()
        >>> 'Score' in foundStream.classes
        True


        Notice that we do not find elements that are in
        sub-streams of the main Stream.  We'll add 15 more rests
        in a sub-stream and they won't be found:

        >>> b = stream.Stream()
        >>> b.repeatInsert(note.Rest(), list(range(15)))
        >>> a.insert(b)
        >>> found = a.getElementsByClass(note.Rest)
        >>> len(found)
        10

        To find them either (1) use `.flat` to get at everything:

        >>> found = a.flat.getElementsByClass(note.Rest)
        >>> len(found)
        25

        Or, (2) recurse over the main stream and call .getElementsByClass
        on each one.  Notice that the first subStream is actually the outermost
        Stream:

        >>> totalFound = 0
        >>> for subStream in a.recurse(streamsOnly=True, includeSelf=True):
        ...     found = subStream.getElementsByClass(note.Rest)
        ...     totalFound += len(found)
        >>> totalFound
        25

        The class name of the Stream created is the same as the original:

        >>> found = a.getElementsByClass(note.Note).stream()
        >>> found.__class__.__name__
        'Score'

        ...except if `returnStreamSubClass` is False, which makes the method
        return a generic Stream:

        >>> found = a.getElementsByClass(note.Rest).stream(returnStreamSubClass=False)
        >>> found.__class__.__name__
        'Stream'


        Make a list from a StreamIterator:

        >>> foundList = list(a.flat.getElementsByClass(note.Rest))
        >>> len(foundList)
        25
        '''
        return self.iter.getElementsByClass(classFilterList)

    def getElementsNotOfClass(self, classFilterList) -> iterator.StreamIterator:
        '''
        Return a list of all Elements that do not
        match the one or more classes in the `classFilterList`.

        In lieu of a list, a single class can be used as the `classFilterList` parameter.

        >>> a = stream.Stream()
        >>> a.repeatInsert(note.Rest(), list(range(10)))
        >>> for x in range(4):
        ...     n = note.Note('G#')
        ...     n.offset = x * 3
        ...     a.insert(n)
        >>> found = a.getElementsNotOfClass(note.Note)
        >>> len(found)
        10

        >>> b = stream.Stream()
        >>> b.repeatInsert(note.Rest(), list(range(15)))
        >>> a.insert(b)

        Here, it gets elements from within a stream
        this probably should not do this, as it is one layer lower

        >>> found = a.flat.getElementsNotOfClass(note.Rest)
        >>> len(found)
        4
        >>> found = a.flat.getElementsNotOfClass(note.Note)
        >>> len(found)
        25
        '''
        return self.iter.getElementsNotOfClass(classFilterList)

    def getElementsByGroup(self, groupFilterList) -> iterator.StreamIterator:
        '''
        >>> n1 = note.Note('C')
        >>> n1.groups.append('trombone')
        >>> n2 = note.Note('D')
        >>> n2.groups.append('trombone')
        >>> n2.groups.append('tuba')
        >>> n3 = note.Note('E')
        >>> n3.groups.append('tuba')
        >>> s1 = stream.Stream()
        >>> s1.append(n1)
        >>> s1.append(n2)
        >>> s1.append(n3)
        >>> tboneSubStream = s1.getElementsByGroup('trombone')
        >>> for thisNote in tboneSubStream:
        ...     print(thisNote.name)
        C
        D
        >>> tubaSubStream = s1.getElementsByGroup('tuba')
        >>> for thisNote in tubaSubStream:
        ...     print(thisNote.name)
        D
        E

        OMIT_FROM_DOCS
        # TODO: group comparisons are not YET case insensitive.
        '''
        return self.iter.getElementsByGroup(groupFilterList).stream()

    def getElementById(self, elementId, classFilter=None) -> Optional[base.Music21Object]:
        '''
        Returns the first encountered element for a given id. Return None
        if no match. Note: this uses the id attribute stored on elements,
        which may not be the same as id(e).

        >>> a = stream.Stream()
        >>> ew = note.Note()
        >>> a.insert(0, ew)
        >>> a[0].id = 'green'
        >>> None == a.getElementById(3)
        True
        >>> a.getElementById('green').id
        'green'
        >>> a.getElementById('Green').id  # case does not matter
        'green'

        Getting an element by getElementById changes its activeSite

        >>> b = stream.Stream()
        >>> b.append(ew)
        >>> ew.activeSite is b
        True
        >>> ew2 = a.getElementById('green')
        >>> ew2 is ew
        True
        >>> ew2.activeSite is a
        True
        >>> ew.activeSite is a
        True
        '''
        sIterator = self.iter.addFilter(filters.IdFilter(elementId))
        if classFilter is not None:
            sIterator = sIterator.getElementsByClass(classFilter)
        for e in sIterator:
            return e
        return None

    def getElementsByOffset(
        self,
        offsetStart,
        offsetEnd=None,

        *,

        includeEndBoundary=True,
        mustFinishInSpan=False,
        mustBeginInSpan=True,
        includeElementsThatEndAtStart=True,
        classList=None
    ) -> iterator.StreamIterator:
        '''
        Returns a StreamIterator containing all Music21Objects that
        are found at a certain offset or within a certain
        offset time range (given the `offsetStart` and (optional) `offsetEnd` values).

        There are several attributes that govern how this range is
        determined:


        If `mustFinishInSpan` is True then an event that begins
        between offsetStart and offsetEnd but which ends after offsetEnd
        will not be included.  The default is False.


        For instance, a half note at offset 2.0 will be found in
        getElementsByOffset(1.5, 2.5) or getElementsByOffset(1.5, 2.5,
        mustFinishInSpan=False) but not by getElementsByOffset(1.5, 2.5,
        mustFinishInSpan=True).

        The `includeEndBoundary` option determines if an element
        begun just at the offsetEnd should be included.  For instance,
        the half note at offset 2.0 above would be found by
        getElementsByOffset(0, 2.0) or by getElementsByOffset(0, 2.0,
        includeEndBoundary=True) but not by getElementsByOffset(0, 2.0,
        includeEndBoundary=False).

        Setting includeEndBoundary to False at the same time as
        mustFinishInSpan is set to True is probably NOT what you want to do
        unless you want to find things like clefs at the end of the region
        to display as courtesy clefs.

        The `mustBeginInSpan` option determines whether notes or other
        objects that do not begin in the region but are still sounding
        at the beginning of the region are excluded.  The default is
        True -- that is, these notes will not be included.
        For instance the half note at offset 2.0 from above would not be found by
        getElementsByOffset(3.0, 3.5) or getElementsByOffset(3.0, 3.5,
        mustBeginInSpan=True) but it would be found by
        getElementsByOffset(3.0, 3.5, mustBeginInSpan=False)

        Setting includeElementsThatEndAtStart to False is useful for zeroLength
        searches that set mustBeginInSpan == False to not catch notes that were
        playing before the search but that end just before the end of the search type.
        This setting is *ignored* for zero-length searches.
        See the code for allPlayingWhileSounding for a demonstration.

        This chart, and the examples below, demonstrate the various
        features of getElementsByOffset.  It is one of the most complex
        methods of music21 but also one of the most powerful, so it
        is worth learning at least the basics.

            .. image:: images/getElementsByOffset.*
                :width: 600


        >>> st1 = stream.Stream()
        >>> n0 = note.Note('C')
        >>> n0.duration.type = 'half'
        >>> n0.offset = 0
        >>> st1.insert(n0)
        >>> n2 = note.Note('D')
        >>> n2.duration.type = 'half'
        >>> n2.offset = 2
        >>> st1.insert(n2)
        >>> out1 = st1.getElementsByOffset(2)
        >>> len(out1)
        1
        >>> out1[0].step
        'D'

        >>> out2 = st1.getElementsByOffset(1, 3)
        >>> len(out2)
        1
        >>> out2[0].step
        'D'
        >>> out3 = st1.getElementsByOffset(1, 3, mustFinishInSpan=True)
        >>> len(out3)
        0
        >>> out4 = st1.getElementsByOffset(1, 2)
        >>> len(out4)
        1
        >>> out4[0].step
        'D'
        >>> out5 = st1.getElementsByOffset(1, 2, includeEndBoundary=False)
        >>> len(out5)
        0
        >>> out6 = st1.getElementsByOffset(1, 2, includeEndBoundary=False, mustBeginInSpan=False)
        >>> len(out6)
        1
        >>> out6[0].step
        'C'
        >>> out7 = st1.getElementsByOffset(1, 3, mustBeginInSpan=False)
        >>> len(out7)
        2
        >>> [el.step for el in out7]
        ['C', 'D']


        Note, that elements that end at the start offset are included if mustBeginInSpan is False

        >>> out8 = st1.getElementsByOffset(2, 4, mustBeginInSpan=False)
        >>> len(out8)
        2
        >>> [el.step for el in out8]
        ['C', 'D']

        To change this behavior set includeElementsThatEndAtStart=False

        >>> out9 = st1.getElementsByOffset(2, 4,
        ...                     mustBeginInSpan=False, includeElementsThatEndAtStart=False)
        >>> len(out9)
        1
        >>> [el.step for el in out9]
        ['D']


        Note how zeroLengthSearches implicitly set includeElementsThatEndAtStart=False.
        These two are the same:

        >>> out1 = st1.getElementsByOffset(2, mustBeginInSpan=False)
        >>> out2 = st1.getElementsByOffset(2, 2, mustBeginInSpan=False)
        >>> len(out1) == len(out2) == 1
        True
        >>> out1[0] is out2[0] is n2
        True

        But this is different:

        >>> out3 = st1.getElementsByOffset(2, 2.1, mustBeginInSpan=False)
        >>> len(out3)
        2
        >>> out3[0] is n0
        True

        Explicitly setting includeElementsThatEndAtStart=False does not get the
        first note:

        >>> out4 = st1.getElementsByOffset(2, 2.1, mustBeginInSpan=False,
        ...                                includeElementsThatEndAtStart=False)
        >>> len(out4)
        1
        >>> out4[0] is n2
        True



        Testing multiple zero-length elements with mustBeginInSpan:

        >>> tc = clef.TrebleClef()
        >>> ts = meter.TimeSignature('4/4')
        >>> ks = key.KeySignature(2)
        >>> s = stream.Stream()
        >>> s.insert(0.0, tc)
        >>> s.insert(0.0, ts)
        >>> s.insert(0.0, ks)
        >>> len(s.getElementsByOffset(0.0, mustBeginInSpan=True))
        3
        >>> len(s.getElementsByOffset(0.0, mustBeginInSpan=False))
        3

        OMIT_FROM_DOCS

        >>> a = stream.Stream()
        >>> n = note.Note('G')
        >>> n.quarterLength = 0.5
        >>> a.repeatInsert(n, list(range(8)))
        >>> b = stream.Stream()
        >>> b.repeatInsert(a, [0, 3, 6])
        >>> c = b.getElementsByOffset(2, 6.9)
        >>> len(c)
        2
        >>> c = b.flat.getElementsByOffset(2, 6.9)
        >>> len(c)
        10


        Same test as above, but with floats

        >>> out1 = st1.getElementsByOffset(2.0)
        >>> len(out1)
        1
        >>> out1[0].step
        'D'
        >>> out2 = st1.getElementsByOffset(1.0, 3.0)
        >>> len(out2)
        1
        >>> out2[0].step
        'D'
        >>> out3 = st1.getElementsByOffset(1.0, 3.0, mustFinishInSpan=True)
        >>> len(out3)
        0
        >>> out3b = st1.getElementsByOffset(0.0, 3.001, mustFinishInSpan=True)
        >>> len(out3b)
        1
        >>> out3b[0].step
        'C'
        >>> out3b = st1.getElementsByOffset(1.0, 3.001,
        ...                                 mustFinishInSpan=True, mustBeginInSpan=False)
        >>> len(out3b)
        1
        >>> out3b[0].step
        'C'


        >>> out4 = st1.getElementsByOffset(1.0, 2.0)
        >>> len(out4)
        1
        >>> out4[0].step
        'D'
        >>> out5 = st1.getElementsByOffset(1.0, 2.0, includeEndBoundary=False)
        >>> len(out5)
        0
        >>> out6 = st1.getElementsByOffset(1.0, 2.0,
        ...                                includeEndBoundary=False, mustBeginInSpan=False)
        >>> len(out6)
        1
        >>> out6[0].step
        'C'
        >>> out7 = st1.getElementsByOffset(1.0, 3.0, mustBeginInSpan=False)
        >>> len(out7)
        2
        >>> [el.step for el in out7]
        ['C', 'D']

        Changed in v5.5: all arguments changing behavior are keyword only.
        '''
        sIterator = self.iter.getElementsByOffset(
            offsetStart=offsetStart,
            offsetEnd=offsetEnd,
            includeEndBoundary=includeEndBoundary,
            mustFinishInSpan=mustFinishInSpan,
            mustBeginInSpan=mustBeginInSpan,
            includeElementsThatEndAtStart=includeElementsThatEndAtStart)
        if classList is not None:
            sIterator = sIterator.getElementsByClass(classList)
        return sIterator

    def getElementAtOrBefore(self, offset, classList=None):
        # noinspection PyShadowingNames
        '''
        Given an offset, find the element at this offset,
        or with the offset less than and nearest to.

        Return one element or None if no elements are at or preceded by this
        offset.

        If the `classList` parameter is used, it should be a
        list of class names or strings, and only objects that
        are instances of
        these classes or subclasses of these classes will be returned.

        >>> stream1 = stream.Stream()
        >>> x = note.Note('D4')
        >>> x.id = 'x'
        >>> y = note.Note('E4')
        >>> y.id = 'y'
        >>> z = note.Rest()
        >>> z.id = 'z'

        >>> stream1.insert(20, x)
        >>> stream1.insert(10, y)
        >>> stream1.insert( 0, z)

        >>> b = stream1.getElementAtOrBefore(21)
        >>> b.offset, b.id
        (20.0, 'x')

        >>> b = stream1.getElementAtOrBefore(19)
        >>> b.offset, b.id
        (10.0, 'y')

        >>> b = stream1.getElementAtOrBefore(0)
        >>> b.offset, b.id
        (0.0, 'z')
        >>> b = stream1.getElementAtOrBefore(0.1)
        >>> b.offset, b.id
        (0.0, 'z')


        You can give a list of acceptable classes to return, and non-matching
        elements will be ignored

        >>> c = stream1.getElementAtOrBefore(100, [clef.TrebleClef, note.Rest])
        >>> c.offset, c.id
        (0.0, 'z')

        Getting an object via getElementAtOrBefore sets the activeSite
        for that object to the Stream, and thus sets its offset

        >>> stream2 = stream.Stream()
        >>> stream2.insert(100.5, x)
        >>> x.offset
        100.5
        >>> d = stream1.getElementAtOrBefore(20)
        >>> d is x
        True
        >>> x.activeSite is stream1
        True
        >>> x.offset
        20.0


        If no element is before the offset, returns None

        >>> s = stream.Stream()
        >>> s.insert(10, note.Note('E'))
        >>> print(s.getElementAtOrBefore(9))
        None

        The sort order of returned items is the reverse
        of the normal sort order, so that, for instance,
        if there's a clef and a note at offset 20,
        getting the object before offset 21 will give
        you the note, and not the clef, since clefs
        sort before notes:

        >>> clef1 = clef.BassClef()
        >>> stream1.insert(20, clef1)
        >>> e = stream1.getElementAtOrBefore(21)
        >>> e
        <music21.note.Note D>
        '''
        # NOTE: this is a performance critical method

        # TODO: switch to trees
        candidates = []
        offset = opFrac(offset)
        nearestTrailSpan = offset  # start with max time

        sIterator = self.iter
        if classList:
            sIterator = sIterator.getElementsByClass(classList)

        # need both _elements and _endElements
        for e in sIterator:
            span = opFrac(offset - self.elementOffset(e))
            # environLocal.printDebug(['e span check', span, 'offset', offset,
            #   'e.offset', e.offset, 'self.elementOffset(e)', self.elementOffset(e), 'e', e])
            if span < 0:
                continue
            elif span == 0:
                candidates.append((span, e))
                nearestTrailSpan = span
            else:
                # do this comparison because may be out of order
                if span <= nearestTrailSpan:
                    candidates.append((span, e))
                    nearestTrailSpan = span
        # environLocal.printDebug(['getElementAtOrBefore(), e candidates', candidates])
        if candidates:
            candidates.sort(key=lambda x: (-1 * x[0], x[1].sortTuple()))
            # TODO: this sort has side effects -- see ICMC2011 -- sorting clef vs. note, etc.
            self.coreSelfActiveSite(candidates[-1][1])
            return candidates[-1][1]
        else:
            return None

    def getElementBeforeOffset(self, offset, classList=None):
        '''
        Get element before (and not at) a provided offset.

        If the `classList` parameter is used, it should be a
        list of class names or strings, and only objects that
        are instances of
        these classes or subclasses of these classes will be returned.

        >>> stream1 = stream.Stream()
        >>> x = note.Note('D4')
        >>> x.id = 'x'
        >>> y = note.Note('E4')
        >>> y.id = 'y'
        >>> z = note.Rest()
        >>> z.id = 'z'
        >>> stream1.insert(20, x)
        >>> stream1.insert(10, y)
        >>> stream1.insert( 0, z)

        >>> b = stream1.getElementBeforeOffset(21)
        >>> b.offset, b.id
        (20.0, 'x')
        >>> b = stream1.getElementBeforeOffset(20)
        >>> b.offset, b.id
        (10.0, 'y')

        >>> b = stream1.getElementBeforeOffset(10)
        >>> b.offset, b.id
        (0.0, 'z')

        >>> b = stream1.getElementBeforeOffset(0)
        >>> b is None
        True
        >>> b = stream1.getElementBeforeOffset(0.1)
        >>> b.offset, b.id
        (0.0, 'z')

        >>> w = note.Note('F4')
        >>> w.id = 'w'
        >>> stream1.insert( 0, w)

        This should get w because it was inserted last.

        >>> b = stream1.getElementBeforeOffset(0.1)
        >>> b.offset, b.id
        (0.0, 'w')

        But if we give it a lower priority than z then z will appear first.

        >>> w.priority = z.priority - 1
        >>> b = stream1.getElementBeforeOffset(0.1)
        >>> b.offset, b.id
        (0.0, 'z')
        '''
        # NOTE: this is a performance critical method
        candidates = []
        offset = opFrac(offset)
        nearestTrailSpan = offset  # start with max time

        sIterator = self.iter
        if classList:
            sIterator = sIterator.getElementsByClass(classList)

        for e in sIterator:
            span = opFrac(offset - self.elementOffset(e))
            # environLocal.printDebug(['e span check', span, 'offset', offset,
            #     'e.offset', e.offset, 'self.elementOffset(e)', self.elementOffset(e), 'e', e])
            # by forcing <= here, we are sure to get offsets not at zero
            if span <= 0:  # the e is after this offset
                continue
            else:  # do this comparison because may be out of order
                if span <= nearestTrailSpan:
                    candidates.append((span, e))
                    nearestTrailSpan = span
        # environLocal.printDebug(['getElementBeforeOffset(), e candidates', candidates])
        if candidates:
            candidates.sort(key=lambda x: (-1 * x[0], x[1].sortTuple()))
            self.coreSelfActiveSite(candidates[-1][1])
            return candidates[-1][1]
        else:
            return None

    # def getElementAfterOffset(self, offset, classList=None):
    #    '''Get element after a provided offset
    #
    #    TODO: write this
    #    '''
    #    raise Exception('not yet implemented')
    #
    #
    # def getElementBeforeElement(self, element, classList=None):
    #    '''given an element, get the element before
    #
    #    TODO: write this
    #    '''
    #    raise Exception('not yet implemented')

    def getElementAfterElement(self, element, classList=None):
        '''
        Given an element, get the next element.  If classList is specified,
        check to make sure that the element is an instance of the class list

        >>> st1 = stream.Stream()
        >>> n1 = note.Note('C4')
        >>> n2 = note.Note('D4')
        >>> r3 = note.Rest()
        >>> st1.append([n1, n2, r3])
        >>> t2 = st1.getElementAfterElement(n1)
        >>> t2 is n2
        True
        >>> t3 = st1.getElementAfterElement(t2)
        >>> t3 is r3
        True
        >>> t4 = st1.getElementAfterElement(t3)
        >>> t4

        >>> t5 = st1.getElementAfterElement(n1, [note.Rest])
        >>> t5
        <music21.note.Rest rest>
        >>> t5 is r3
        True
        >>> t6 = st1.getElementAfterElement(n1, [note.Rest, note.Note])
        >>> t6 is n2
        True

        >>> t7 = st1.getElementAfterElement(r3)
        >>> t7 is None
        True


        If the element is not in the stream, it will raise a StreamException:

        >>> st1.getElementAfterElement(note.Note('C#'))
        Traceback (most recent call last):
        music21.exceptions21.StreamException:
            cannot find object (<music21.note.Note C#>) in Stream

        '''
        if classList is not None:
            classSet = set(classList)
        else:
            classSet = None

        # index() ultimately does an autoSort check, so no check here or
        # sorting is necessary
        # also, this will raise a StreamException if element is not in the Stream
        elPos = self.index(element)

        # store once as a property call concatenates
        elements = self.elements
        if classList is None:
            if elPos == len(elements) - 1:
                return None
            else:
                e = elements[elPos + 1]
                self.coreSelfActiveSite(e)
                return e
        else:
            for i in range(elPos + 1, len(elements)):
                if classList is None or classSet.intersection(elements[i].classSet):
                    e = elements[i]
                    self.coreSelfActiveSite(e)
                    return e

    # ----------------------------------------------------
    # end .getElement filters

    # -------------------------------------------------------------------------
    # routines for obtaining specific types of elements from a Stream
    # _getNotes and _getPitches are found with the interval routines

    def measures(self,
                 numberStart,
                 numberEnd,
                 collect=('Clef', 'TimeSignature', 'Instrument', 'KeySignature'),
                 gatherSpanners=True,
                 indicesNotNumbers=False):
        '''
        Get a region of Measures based on a start and end Measure number
        where the boundary numbers are both included.

        That is, a request for measures 4 through 10 will return 7 Measures, numbers 4 through 10.

        Additionally, any number of associated classes can be gathered from the context
        and put into the measure.  By default we collect the Clef, TimeSignature, KeySignature,
        and Instrument so that there is enough context to perform.  (See getContextByClass()
        and .previous() for definitions of the context)

        While all elements in the source are the original elements in the extracted region,
        new Measure objects are created and returned.

        >>> bachIn = corpus.parse('bach/bwv66.6')
        >>> bachExcerpt = bachIn.parts[0].measures(1, 3)
        >>> len(bachExcerpt.getElementsByClass('Measure'))
        3

        Because bwv66.6 has a pickup measure, and we requested to start at measure 1,
        this is NOT true:

        >>> firstExcerptMeasure = bachExcerpt.getElementsByClass('Measure')[0]
        >>> firstBachMeasure = bachIn.parts[0].getElementsByClass('Measure')[0]
        >>> firstExcerptMeasure is firstBachMeasure
        False
        >>> firstBachMeasure.number
        0
        >>> firstExcerptMeasure.number
        1



        To get all measures from the beginning, go ahead and always request measure 0 to x,
        there will be no error if there is not a pickup measure.

        >>> bachExcerpt = bachIn.parts[0].measures(0, 3)
        >>> excerptNote = bachExcerpt.getElementsByClass('Measure')[0].notes[0]
        >>> originalNote = bachIn.parts[0].flat.notes[0]
        >>> excerptNote is originalNote
        True

        if `indicesNotNumbers` is True, then it ignores defined measureNumbers and
        uses 0-indexed measure objects and half-open range.  For instance, if you have a piece
        that goes "m1, m2, m3, m4, ..." (like a standard piece without pickups, then
        `.measures(1, 3, indicesNotNumbers=True)` would return measures 2 and 3, because
        it is interpreted as the slice from object with index 1, which is measure 2 (m1 has
        index 0) up to but NOT including the object with index 3, which is measure 4.
        IndicesNotNumbers is like a Python-slice.

        >>> bachExcerpt2 = bachIn.parts[0].measures(0, 2, indicesNotNumbers=True)
        >>> for m in bachExcerpt2.getElementsByClass('Measure'):
        ...     print(m)
        ...     print(m.number)
        <music21.stream.Measure 0 offset=0.0>
        0
        <music21.stream.Measure 1 offset=1.0>
        1


        If `numberEnd=None` then it is interpreted as the last measure of the stream:

        >>> bachExcerpt3 = bachIn.parts[0].measures(7, None)
        >>> for m in bachExcerpt3.getElementsByClass('Measure'):
        ...     print(m)
        <music21.stream.Measure 7 offset=0.0>
        <music21.stream.Measure 8 offset=4.0>
        <music21.stream.Measure 9 offset=8.0>

        Note that the offsets in the new stream are shifted so that the first measure
        in the excerpt begins at 0.0

        The measure elements are the same objects as the original:

        >>> lastExcerptMeasure = bachExcerpt3.getElementsByClass('Measure')[-1]
        >>> lastOriginalMeasure = bachIn.parts[0].getElementsByClass('Measure')[-1]
        >>> lastExcerptMeasure is lastOriginalMeasure
        True

        At the beginning of the Stream returned, before the measures will be some additional
        objects so that the context is properly preserved:

        >>> for thing in bachExcerpt3:
        ...     print(thing)
        P1: Soprano: Instrument 1
        <music21.clef.TrebleClef>
        f# minor
        <music21.meter.TimeSignature 4/4>
        <music21.stream.Measure 7 offset=0.0>
        <music21.stream.Measure 8 offset=4.0>
        <music21.stream.Measure 9 offset=8.0>

        Collecting gets the most recent element in the context of the stream:

        >>> bachIn.parts[0].insert(10, key.Key('D-'))
        >>> bachExcerpt4 = bachIn.parts[0].measures(7, None)
        >>> for thing in bachExcerpt4:
        ...     print(thing)
        P1: Soprano: Instrument 1
        <music21.clef.TrebleClef>
        D- major
        ...


        What is collected is determined by the "collect" iterable.  To collect nothing
        send an empty list:

        >>> bachExcerpt5 = bachIn.parts[0].measures(8, None, collect=[])
        >>> for thing in bachExcerpt5:
        ...     print(thing)
        <music21.stream.Measure 8 offset=0.0>
        <music21.stream.Measure 9 offset=4.0>

        if gatherSpanners is True then all spanners in
        the score are gathered and
        included. (this behavior may change in the future)

        '''
        # TODO: make True only return spanners from the region.  Use core.gatherMissingSpanners()
        #     to do so.  But make sure that StaffGroups export properly.

        def hasMeasureNumberInformation(measureIterator):
            '''
            Many people create streams where every number is zero.
            This will check for that as quickly as possible.
            '''
            uniqueMeasureNumbers = set()
            for m in measureIterator:
                try:
                    mNumber = int(m.number)
                except ValueError:  # pragma: no cover
                    # should never happen.
                    raise StreamException(f'found problematic measure for numbering: {m}')
                uniqueMeasureNumbers.add(mNumber)
                if len(uniqueMeasureNumbers) > 1:
                    break
            if len(uniqueMeasureNumbers) > 1:
                return True
            elif len(uniqueMeasureNumbers.union({0})) > 1:
                return True  # is there a number other than zero? (or any number at all
            else:
                return False

        returnObj = self.cloneEmpty(derivationMethod='measures')
        srcObj = self

        mStreamIter = self.getElementsByClass('Measure')
        # if we have no Measures defined, call makeNotation
        # this will  return a deepcopy of all objects
        if not mStreamIter:
            srcObj = self.makeNotation(inPlace=False)
            # need to set srcObj to this new stream
            mStreamIter = srcObj.getElementsByClass('Measure')
            # get spanners from make notation, as this will be a copy
            # TODO: make sure that makeNotation copies spanners
            # mStreamSpanners = mStream.spanners

        # spanners may be stored at the container/Part level, not within a measure
        # if they are within the Measure, or a voice, they will be transferred
        # below

        # create empty bundle in case not created by other means
        spannerBundle = spanner.SpannerBundle()
        if gatherSpanners:
            spannerBundle = srcObj.spannerBundle

        # FIND THE CORRECT ORIGINAL MEASURE OBJECTS
        # for indicesNotNumbers, this is simple...
        if indicesNotNumbers:
            matches = mStreamIter[numberStart:numberEnd]
        else:
            hasUniqueMeasureNumbers = hasMeasureNumberInformation(mStreamIter)

            # unused...
            # originalNumberStart = numberStart
            # originalNumberEnd = numberEnd
            startSuffix = None
            endSuffix = None
            if isinstance(numberStart, str):
                numberStart, startSuffixTemp = common.getNumFromStr(numberStart)
                if startSuffixTemp:
                    startSuffix = startSuffixTemp
                numberStart = int(numberStart)

            if isinstance(numberEnd, str):
                numberEnd, endSuffixTemp = common.getNumFromStr(numberEnd)
                if endSuffixTemp:
                    endSuffix = endSuffixTemp
                numberEnd = int(numberEnd)

            if numberEnd is not None:
                matchingMeasureNumbers = set(range(numberStart, numberEnd + 1))

                if hasUniqueMeasureNumbers:
                    matches = [m for m in mStreamIter if m.number in matchingMeasureNumbers]
                else:
                    matches = [m for i, m in enumerate(mStreamIter)
                                    if i + 1 in matchingMeasureNumbers]
            else:
                if hasUniqueMeasureNumbers:
                    matches = [m for m in mStreamIter if m.number >= numberStart]
                else:
                    matches = [m for i, m in enumerate(mStreamIter)
                                        if i + 1 >= numberStart]

            if startSuffix is not None:
                oldMatches = matches
                matches = []
                for m in oldMatches:
                    if m.number != numberStart:
                        matches.append(m)
                    elif not m.numberSuffix:
                        matches.append(m)
                    elif m.numberSuffix >= startSuffix:
                        matches.append(m)

            if endSuffix is not None:
                oldMatches = matches
                matches = []
                for m in oldMatches:
                    if m.number != numberEnd:
                        matches.append(m)
                    elif not m.numberSuffix:
                        matches.append(m)
                    elif m.numberSuffix <= endSuffix:
                        matches.append(m)

        if not matches:
            startMeasure = None
            startOffset = 0  # does not matter; could be any number...
        else:
            startMeasure = matches[0]
            startOffset = startMeasure.getOffsetBySite(srcObj)

        for className in collect:
            # first, see if it is in this Measure
            if (startMeasure is None
                    or startMeasure.recurse().getElementsByClass(className).getElementsByOffset(0)):
                continue

            # placing missing objects in outer container, not Measure
            found = startMeasure.getContextByClass(className)
            if found is not None:
                if startMeasure is not None:
                    found.priority = startMeasure.priority - 1
                    # TODO: This should not change global priority on found, but
                    #   instead priority, like offset, should be a per-site attribute
                returnObj.coreInsert(0, found)

        for m in matches:
            mOffset = m.getOffsetBySite(srcObj) - startOffset
            returnObj.coreInsert(mOffset, m)

        if gatherSpanners:
            sf = srcObj.flat
            for sp in spannerBundle:
                # can use old offsets of spanners, even though components
                # have been updated
                # returnObj.insert(sp.getOffsetBySite(mStreamSpanners), sp)
                returnObj.coreInsert(sf.elementOffset(sp), sp)

                # environLocal.printDebug(['Stream.measures: copying spanners:', sp])

        # used coreInsert
        returnObj.coreElementsChanged()
        # environLocal.printDebug(['len(returnObj.flat)', len(returnObj.flat)])
        return returnObj

    def measure(self,
                measureNumber,
                collect=('Clef', 'TimeSignature', 'Instrument', 'KeySignature'),
                indicesNotNumbers=False):
        '''
        Given a measure number, return a single
        :class:`~music21.stream.Measure` object if the Measure number exists, otherwise return None.

        This method is distinguished from :meth:`~music21.stream.Stream.measures`
        in that this method returns a single Measure object, not a Stream containing
        one or more Measure objects.


        >>> a = corpus.parse('bach/bwv324.xml')
        >>> a.parts[0].measure(3)
        <music21.stream.Measure 3 offset=8.0>

        See :meth:`~music21.stream.Stream.measures` for an explanation of collect and
        indicesNotNumbers

        To get the last measure of a piece, use -1 as a measureNumber -- this will turn
        on indicesNotNumbers if it is off:

        >>> a.parts[0].measure(-1)
        <music21.stream.Measure 9 offset=38.0>

        Getting a non-existent measure will return None:

        >>> print(a.parts[0].measure(99))
        None
        '''
        if not isinstance(measureNumber, str) and measureNumber < 0:
            indicesNotNumbers = True

        startMeasureNumber = measureNumber
        endMeasureNumber = measureNumber
        if indicesNotNumbers:
            endMeasureNumber += 1
            if startMeasureNumber == -1:
                endMeasureNumber = None

        # we must be able to obtain a measure from this (not a flat)
        # representation (e.g., this is a Stream or Part, not a Score)
        if self.getElementsByClass('Measure'):
            # environLocal.printDebug(['got measures from getElementsByClass'])
            s = self.measures(startMeasureNumber,
                              endMeasureNumber,
                              collect=collect,
                              indicesNotNumbers=indicesNotNumbers)
            measureIter = s.getElementsByClass('Measure')
            if not measureIter:
                return None
            else:
                m = measureIter[0]
                self.coreSelfActiveSite(m)
                # ^^ this sets its offset to something meaningful...
                return m
        else:
            # environLocal.printDebug(['got not measures from getElementsByClass'])
            return None

    def template(self,
                 fillWithRests=True,
                 removeClasses=None,
                 retainVoices=True,
                 ):
        '''
        Return a new Stream based on this one, but without the notes and other elements
        but keeping instruments, clefs, keys, etc.

        Classes to remove are specified in `removeClasses`.

        If this Stream contains measures, return a new Stream
        with new Measures populated with the same characteristics of those found in this Stream.


        >>> b = corpus.parse('bwv66.6')
        >>> sopr = b.parts[0]
        >>> soprEmpty = sopr.template()
        >>> soprEmpty.show('text')
        {0.0} <music21.instrument.Instrument 'P1: Soprano: Instrument 1'>
        {0.0} <music21.stream.Measure 0 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            {0.0} <music21.key.Key of f# minor>
            {0.0} <music21.meter.TimeSignature 4/4>
            {0.0} <music21.note.Rest rest>
        {1.0} <music21.stream.Measure 1 offset=1.0>
            {0.0} <music21.note.Rest rest>
        {5.0} <music21.stream.Measure 2 offset=5.0>
            {0.0} <music21.note.Rest rest>
        {9.0} <music21.stream.Measure 3 offset=9.0>
            {0.0} <music21.layout.SystemLayout>
            {0.0} <music21.note.Rest rest>
        {13.0} <music21.stream.Measure 4 offset=13.0>
        ...


        Really make empty with `fillWithRests=False`

        >>> alto = b.parts[1]
        >>> altoEmpty = alto.template(fillWithRests=False)
        >>> altoEmpty.show('text')
        {0.0} <music21.instrument.Instrument 'P2: Alto: Instrument 2'>
        {0.0} <music21.stream.Measure 0 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            {0.0} <music21.key.Key of f# minor>
            {0.0} <music21.meter.TimeSignature 4/4>
        {1.0} <music21.stream.Measure 1 offset=1.0>
        <BLANKLINE>
        {5.0} <music21.stream.Measure 2 offset=5.0>
        <BLANKLINE>
        {9.0} <music21.stream.Measure 3 offset=9.0>
            {0.0} <music21.layout.SystemLayout>
        ...


        removeClasses can be a list or set of classes to remove.  By default it is
        ['GeneralNote', 'Dynamic', 'Expression']

        >>> tenor = b.parts[2]
        >>> tenorNoClefsSignatures = tenor.template(fillWithRests=False,
        ...       removeClasses=['Clef', 'KeySignature', 'TimeSignature', 'Instrument'])
        >>> tenorNoClefsSignatures.show('text')
        {0.0} <music21.stream.Measure 0 offset=0.0>
            {0.0} <music21.note.Note A>
            {0.5} <music21.note.Note B>
        {1.0} <music21.stream.Measure 1 offset=1.0>
            {0.0} <music21.note.Note C#>
            {1.0} <music21.note.Note B>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note B>
        {5.0} <music21.stream.Measure 2 offset=5.0>
        ...

        Setting removeClasses to True removes everything that is not a Stream:

        >>> bass = b.parts[3]
        >>> bassEmpty = bass.template(fillWithRests=False, removeClasses=True)
        >>> bassEmpty.show('text')
        {0.0} <music21.stream.Measure 0 offset=0.0>
        <BLANKLINE>
        {1.0} <music21.stream.Measure 1 offset=1.0>
        <BLANKLINE>
        {5.0} <music21.stream.Measure 2 offset=5.0>
        <BLANKLINE>
        {9.0} <music21.stream.Measure 3 offset=9.0>
        <BLANKLINE>
        {13.0} <music21.stream.Measure 4 offset=13.0>
        <BLANKLINE>
        ...

        On the whole score:

        >>> b.template().show('text')
        {0.0} <music21.metadata.Metadata object at 0x106151940>
        {0.0} <music21.stream.Part Soprano>
            {0.0} <music21.instrument.Instrument 'P1: Soprano: Instrument 1'>
            {0.0} <music21.stream.Measure 0 offset=0.0>
                {0.0} <music21.clef.TrebleClef>
                {0.0} <music21.key.Key of f# minor>
                {0.0} <music21.meter.TimeSignature 4/4>
                {0.0} <music21.note.Rest rest>
            {1.0} <music21.stream.Measure 1 offset=1.0>
                {0.0} <music21.note.Rest rest>
                ...
            {33.0} <music21.stream.Measure 9 offset=33.0>
                {0.0} <music21.note.Rest rest>
                {3.0} <music21.bar.Barline type=final>
        {0.0} <music21.stream.Part Alto>
            {0.0} <music21.instrument.Instrument 'P2: Alto: Instrument 2'>
            {0.0} <music21.stream.Measure 0 offset=0.0>
                {0.0} <music21.clef.TrebleClef>
                {0.0} <music21.key.Key of f# minor>
                {0.0} <music21.meter.TimeSignature 4/4>
                {0.0} <music21.note.Rest rest>
            {1.0} <music21.stream.Measure 1 offset=1.0>
                {0.0} <music21.note.Rest rest>
            ...
            {33.0} <music21.stream.Measure 9 offset=33.0>
                {0.0} <music21.note.Rest rest>
                {3.0} <music21.bar.Barline type=final>
        {0.0} <music21.layout.StaffGroup ...>


        If retainVoices is False (default True) then Voice streams are treated
        differently from all other Streams and are removed.  All elements in the
        voice are removed even if they do not match the classList:

        >>> p = stream.Part(id='part0')
        >>> m1 = stream.Measure(number=1)
        >>> v1 = stream.Voice(id='voice1')
        >>> v1.insert(0, note.Note('E', quarterLength=4.0))
        >>> v2 = stream.Voice(id='voice2')
        >>> v2.insert(0, note.Note('G', quarterLength=2.0))
        >>> m1.insert(0, v1)
        >>> m1.insert(0, v2)
        >>> m2 = stream.Measure(number=2)
        >>> m2.insert(0, note.Note('D', quarterLength=4.0))
        >>> p.append([m1, m2])
        >>> pt = p.template(retainVoices=False)
        >>> pt.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.note.Rest rest>
        {4.0} <music21.stream.Measure 2 offset=4.0>
            {0.0} <music21.note.Rest rest>
        >>> pt[0][0].quarterLength
        4.0

        Developer note -- if you just want a copy of a Score with new
        Part and Measure objects, but you don't care that the notes, etc.
        inside are the same objects as the original (i.e., you do not
        plan to manipulate them, or you want the manipulations to
        return to the original objects), using .template() is several
        times faster than a deepcopy of the stream (about 4x faster
        on bwv66.6)
        '''
        out = self.cloneEmpty('template')
        if removeClasses is None:
            removeClasses = {'GeneralNote', 'Dynamic', 'Expression'}
        elif common.isIterable(removeClasses):
            removeClasses = set(removeClasses)

        restInfo = {'offset': None, 'endTime': None}

        def optionalAddRest():
            # six.PY3  nonlocal currentRest  would remove the need for restInfo struct
            if not fillWithRests:
                return
            if restInfo['offset'] is None:
                return

            restQL = restInfo['endTime'] - restInfo['offset']
            restObj = note.Rest(quarterLength=restQL)
            out.insert(restInfo['offset'], restObj)
            restInfo['offset'] = None
            restInfo['endTime'] = None

        for el in self:
            if el.isStream and (retainVoices or ('Voice' not in el.classes)):
                optionalAddRest()
                outEl = el.template(fillWithRests=fillWithRests,
                                    removeClasses=removeClasses,
                                    retainVoices=retainVoices)
                out.insert(el.offset, outEl)

            elif (removeClasses is True
                    or el.classSet.intersection(removeClasses)
                    or (not retainVoices and 'Voice' in el.classes)):
                # remove this element
                if fillWithRests and el.duration.quarterLength:
                    endTime = el.offset + el.duration.quarterLength
                    if restInfo['offset'] is None:
                        restInfo['offset'] = el.offset
                        restInfo['endTime'] = endTime
                    elif endTime > restInfo['endTime']:
                        restInfo['endTime'] = endTime
            else:
                optionalAddRest()
                elNew = copy.deepcopy(el)
                out.insert(el.offset, elNew)

        optionalAddRest()

        return out

    def measureOffsetMap(self, classFilterList=None):
        '''
        If this Stream contains Measures, provide an OrderedDict
        whose keys are the offsets of the start of each measure
        and whose values are a list of references
        to the :class:`~music21.stream.Measure` objects that start
        at that offset.

        Even in normal music there may be more than
        one Measure starting at each offset because each
        :class:`~music21.stream.Part` might define its own Measure.
        However, you are unlikely to encounter such things unless you
        run Score.semiFlat, which retains all the containers found in
        the score.

        The offsets are always measured relative to the
        calling Stream (self).

        You can specify a `classFilterList` argument as a list of classes
        to find instead of Measures.  But the default will of course
        find Measure objects.

        Example 1: This Bach chorale is in 4/4 without a pickup, so
        as expected, measures are found every 4 offsets, until the
        weird recitation in m. 7 which in our edition lasts 10 beats
        and thus causes a gap in measureOffsetMap from 24.0 to 34.0.

        .. image:: images/streamMeasureOffsetMapBWV324.*
            :width: 572


        >>> chorale = corpus.parse('bach/bwv324.xml')
        >>> alto = chorale.parts['alto']
        >>> altoMeasures = alto.measureOffsetMap()
        >>> list(altoMeasures.keys())
        [0.0, 4.0, 8.0, 12.0, 16.0, 20.0, 24.0, 34.0, 38.0]

        altoMeasures is a dictionary (hash) of the measures
        that are found in the alto part, so we can get
        the measure beginning on offset 4.0 (measure 2)
        and display it (though it's the only measure
        found at offset 4.0, there might be others as
        in example 2, so we need to call altoMeasures[4.0][0]
        to get this measure.):

        >>> altoMeasures[4.0]
        [<music21.stream.Measure 2 offset=4.0>]
        >>> altoMeasures[4.0][0].show('text')
        {0.0} <music21.note.Note D>
        {1.0} <music21.note.Note D#>
        {2.0} <music21.note.Note E>
        {3.0} <music21.note.Note F#>

        Example 2: How to get all the measures from all parts (not the
        most efficient way, but it works!).  Note that
        you first need to call semiFlat, which finds all containers
        (and other elements) nested inside all parts:

        >>> choraleSemiFlat = chorale.semiFlat
        >>> choraleMeasures = choraleSemiFlat.measureOffsetMap()
        >>> choraleMeasures[4.0]
        [<music21.stream.Measure 2 offset=4.0>,
         <music21.stream.Measure 2 offset=4.0>,
         <music21.stream.Measure 2 offset=4.0>,
         <music21.stream.Measure 2 offset=4.0>]

        OMIT_FROM_DOCS
        see important examples in testMeasureOffsetMap() and testMeasureOffsetMapPostTie()
        '''
        if classFilterList is None:
            classFilterList = [Measure]
        elif not isinstance(classFilterList, (list, tuple)):
            classFilterList = [classFilterList]

        # environLocal.printDebug(['calling measure offsetMap()'])

        # environLocal.printDebug([classFilterList])
        offsetMap = {}
        # first, try to get measures
        # this works best of this is a Part or Score
        if Measure in classFilterList or 'Measure' in classFilterList:
            for m in self.getElementsByClass('Measure'):
                offset = self.elementOffset(m)
                if offset not in offsetMap:
                    offsetMap[offset] = []
                # there may be more than one measure at the same offset
                offsetMap[offset].append(m)

        # try other classes
        for className in classFilterList:
            if className in [Measure or 'Measure']:  # do not redo
                continue
            for e in self.getElementsByClass(className):
                # environLocal.printDebug(['calling measure offsetMap(); e:', e])
                # NOTE: if this is done on Notes, this can take an extremely
                # long time to process
                # 'reverse' here is a reverse sort, where oldest objects are returned
                # first
                m = e.getContextByClass('Measure')  # , sortByCreationTime='reverse')
                if m is None:
                    continue
                # assuming that the offset returns the proper offset context
                # this is, the current offset may not be the stream that
                # contains this Measure; its current activeSite
                offset = m.offset
                if offset not in offsetMap:
                    offsetMap[offset] = []
                if m not in offsetMap[offset]:
                    offsetMap[offset].append(m)

        orderedOffsetMap = collections.OrderedDict(sorted(offsetMap.items(), key=lambda o: o[0]))
        return orderedOffsetMap

    def _getFinalBarline(self):
        # if we have part-like streams, process each part
        if self.hasPartLikeStreams():
            post = []
            for p in self.getElementsByClass('Stream'):
                post.append(p._getFinalBarline())
            return post  # a list of barlines
        # core routines for a single Stream
        else:
            if self.hasMeasures():
                return self.getElementsByClass('Measure')[-1].rightBarline
            elif hasattr(self, 'rightBarline'):
                return self.rightBarline
            else:
                return None

    def _setFinalBarline(self, value):
        # if we have part-like streams, process each part
        if self.hasPartLikeStreams():
            if not common.isListLike(value):
                value = [value]
            for i, p in enumerate(self.getElementsByClass('Stream')):
                # set final barline w/ mod iteration of value list
                bl = value[i % len(value)]
                # environLocal.printDebug(['enumerating measures', i, p, 'setting barline', bl])
                p._setFinalBarline(bl)
            return

        # core routines for a single Stream
        if self.hasMeasures():
            self.getElementsByClass('Measure')[-1].rightBarline = value
        elif hasattr(self, 'rightBarline'):
            self.rightBarline = value  # pylint: disable=attribute-defined-outside-init
        # do nothing for other streams

    finalBarline = property(_getFinalBarline, _setFinalBarline, doc='''
        Get or set the final barline of this Stream's Measures,
        if and only if there are Measures defined as elements in this Stream.
        This method will not create Measures if none exist.

        >>> p = stream.Part()
        >>> m1 = stream.Measure()
        >>> m1.rightBarline = bar.Barline('double')
        >>> p.append(m1)
        >>> p.finalBarline
        <music21.bar.Barline type=double>
        >>> m2 = stream.Measure()
        >>> m2.rightBarline = bar.Barline('final')
        >>> p.append(m2)
        >>> p.finalBarline
        <music21.bar.Barline type=final>

        This property also works on Scores that contain one or more Parts.
        In that case a list of barlines can be used to set the final barline.

        >>> s = corpus.parse('bwv66.6')
        >>> s.finalBarline
        [<music21.bar.Barline type=final>,
         <music21.bar.Barline type=final>,
         <music21.bar.Barline type=final>,
         <music21.bar.Barline type=final>]

        >>> s.finalBarline = 'none'
        >>> s.finalBarline
        [<music21.bar.Barline type=none>,
         <music21.bar.Barline type=none>,
         <music21.bar.Barline type=none>,
         <music21.bar.Barline type=none>]


        Getting or setting a final barline on a Measure (or another Stream
        with a rightBarline attribute) is the same as getting or setting the rightBarline.

        >>> m = stream.Measure()
        >>> m.finalBarline is None
        True
        >>> m.finalBarline = 'final'
        >>> m.finalBarline
        <music21.bar.Barline type=final>
        >>> m.rightBarline
        <music21.bar.Barline type=final>

        Getting on a generic Stream, Voice, or Opus always returns a barline of None,
        and setting on a generic Stream, Voice, or Opus always returns None:

        >>> s = stream.Stream()
        >>> s.finalBarline is None
        True
        >>> s.finalBarline = 'final'
        >>> s.finalBarline is None
        True

        Changed in v6.3 -- does not raise an exception if queried or set on a measure-less stream.
        Previously raised a StreamException
        ''')

    @property
    def voices(self):
        '''
        Return all :class:`~music21.stream.Voices` objects
        in an iterator

        >>> s = stream.Stream()
        >>> s.insert(0, stream.Voice())
        >>> s.insert(0, stream.Voice())
        >>> len(s.voices)
        2
        '''
        return self.getElementsByClass('Voice')

    @property
    def spanners(self):
        '''
        Return all :class:`~music21.spanner.Spanner` objects
        (things such as Slurs, long trills, or anything that
        connects many objects)
        in an Iterator

        >>> s = stream.Stream()
        >>> s.insert(0, spanner.Slur())
        >>> s.insert(0, spanner.Slur())
        >>> len(s.spanners)
        2
        '''
        return self.getElementsByClass('Spanner')

    # --------------------------------------------------------------------------
    # handling transposition values and status

    @property
    def atSoundingPitch(self) -> Union[bool, str]:
        '''
        Get or set the atSoundingPitch status, that is whether the
        score is at concert pitch or may have transposing instruments
        that will not sound as notated.

        Valid values are True, False, and 'unknown'.

        Note that setting "atSoundingPitch" does not actually transpose the notes. See
        `toSoundingPitch()` for that information.

        >>> s = stream.Stream()
        >>> s.atSoundingPitch = True
        >>> s.atSoundingPitch = False
        >>> s.atSoundingPitch = 'unknown'
        >>> s.atSoundingPitch
        'unknown'
        >>> s.atSoundingPitch = 'junk'
        Traceback (most recent call last):
        music21.exceptions21.StreamException: not a valid at sounding pitch value: junk
        '''
        return self._atSoundingPitch

    @atSoundingPitch.setter
    def atSoundingPitch(self, value: Union[bool, str]):
        if value in [True, False, 'unknown']:
            self._atSoundingPitch = value
        else:
            raise StreamException(f'not a valid at sounding pitch value: {value}')


    def _transposeByInstrument(self,
                               reverse=False,
                               inPlace=False,
                               transposeKeySignature=True):
        '''
        Transpose the Stream according to each instrument's transposition.

        If reverse is False, the transposition will happen in the direction
        opposite of what is specified by the Instrument. for instance,
        for changing a concert score to a transposed score or for
        extracting parts.

        TODO: Fill out -- expose publicly?
        '''
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        # this will change the working Stream; not sure if a problem
        try:
            boundaries = returnObj.extendDurationAndGetBoundaries('Instrument')
        except StreamException:
            return returnObj  # there are no instruments in the Stream.

        # store class filter list for transposition
        if transposeKeySignature:
            classFilterList = ('Note', 'Chord', 'KeySignature')
        else:
            classFilterList = ('Note', 'Chord')

        for k in boundaries:
            i = boundaries[k]
            if i.transposition is None:
                continue
            start, end = k
            focus = returnObj.getElementsByOffset(start, end,
                                                  includeEndBoundary=False, mustFinishInSpan=False,
                                                  mustBeginInSpan=True).stream()
            trans = i.transposition
            if reverse:
                trans = trans.reverse()
            focus.transpose(trans, inPlace=True,
                            classFilterList=classFilterList)
            # print(k, i.transposition)
        return returnObj

    def toSoundingPitch(self, *, inPlace=False):
        # noinspection PyShadowingNames
        '''
        If not at sounding pitch, transpose all Pitch
        elements to sounding pitch. The atSoundingPitch property
        is used to determine if transposition is necessary.

        Affected by the presence of Instruments and by Ottava spanners

        v2.0.10 changes -- inPlace is False; v. 5 returns None if inPlace=True

        >>> sc = stream.Score()
        >>> p = stream.Part(id='barisax')
        >>> p.append(instrument.BaritoneSaxophone())
        >>> m = stream.Measure(number=1)
        >>> m.append(note.Note('A4'))
        >>> p.append(m)
        >>> sc.append(p)
        >>> sc.atSoundingPitch = False

        >>> scSounding = sc.toSoundingPitch()
        >>> scSounding.show('text')
        {0.0} <music21.stream.Part barisax>
            {0.0} <music21.instrument.BaritoneSaxophone 'Baritone Saxophone'>
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.note.Note C>

        >>> scSounding.atSoundingPitch
        True
        >>> scSounding.parts[0].atSoundingPitch
        True
        >>> scSounding.recurse().notes[0].nameWithOctave
        'C3'
        '''
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        if returnObj.hasPartLikeStreams():
            for p in returnObj.getElementsByClass('Stream'):
                # call on each part
                p.toSoundingPitch(inPlace=True)
                returnObj.atSoundingPitch = True
            return returnObj

        # else...
        atSoundingPitch = self.atSoundingPitch
        if atSoundingPitch == 'unknown':
            for site in self.sites:
                if hasattr(site, 'atSoundingPitch') and site.atSoundingPitch != 'unknown':
                    atSoundingPitch = site.atSoundingPitch
                    break
            else:
                raise StreamException('atSoundingPitch is unknown: cannot transpose')

        if atSoundingPitch is False:
            # transposition defined on instrument goes from written to sounding
            returnObj._transposeByInstrument(reverse=False, inPlace=True)
            returnObj.atSoundingPitch = True

        for ottava in returnObj.recurse().getElementsByClass('Ottava'):
            ottava.performTransposition()

        if not inPlace:
            return returnObj  # the Stream or None

    def toWrittenPitch(self, *, inPlace=False):
        '''
        If not at written pitch, transpose all Pitch elements to
        written pitch. The atSoundingPitch property is used to
        determine if transposition is necessary.

        music21 v.3 changes -- inPlace=False, v. 5 -- returns None if inPlace=True

        >>> sc = stream.Score()
        >>> p = stream.Part(id='baritoneSax')
        >>> p.append(instrument.BaritoneSaxophone())
        >>> m = stream.Measure(number=1)
        >>> m.append(note.Note('C3'))
        >>> p.append(m)
        >>> sc.append(p)
        >>> sc.atSoundingPitch = True
        >>> scWritten = sc.toWrittenPitch()
        >>> scWritten.show('text')
        {0.0} <music21.stream.Part baritoneSax>
            {0.0} <music21.instrument.BaritoneSaxophone 'Baritone Saxophone'>
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.note.Note A>
        >>> scWritten.atSoundingPitch
        False
        >>> scWritten.parts[0].atSoundingPitch
        False
        >>> scWritten.recurse().notes[0].nameWithOctave
        'A4'
        '''
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        if returnObj.hasPartLikeStreams():
            for partLike in returnObj.getElementsByClass('Stream'):
                # call on each part
                partLike.toWrittenPitch(inPlace=True)
            returnObj.atSoundingPitch = False
            if inPlace:
                return None
            else:
                return returnObj

        # else...
        atSoundingPitch = self.atSoundingPitch
        if atSoundingPitch == 'unknown':
            for site in self.sites:
                if hasattr(site, 'atSoundingPitch') and site.atSoundingPitch != 'unknown':
                    atSoundingPitch = site.atSoundingPitch
                    break
            else:
                raise StreamException('atSoundingPitch is unknown: cannot transpose')

        if atSoundingPitch is True:
            # transposition defined on instrument goes from written to sounding
            # need to reverse to go to written
            returnObj._transposeByInstrument(reverse=True, inPlace=True)
            returnObj.atSoundingPitch = False

        for ottava in returnObj.recurse().getElementsByClass('Ottava'):
            ottava.undoTransposition()

        if not inPlace:
            return returnObj

    # --------------------------------------------------------------------------
    def getTimeSignatures(self, *,
                          searchContext=True,
                          returnDefault=True,
                          sortByCreationTime=True):
        '''
        Collect all :class:`~music21.meter.TimeSignature` objects in this stream.
        If no TimeSignature objects are defined, get a default (4/4 or whatever
        is defined in the defaults.py file).

        >>> a = stream.Stream()
        >>> b = meter.TimeSignature('3/4')
        >>> a.insert(b)
        >>> a.repeatInsert(note.Note('C#'), list(range(10)))
        >>> c = a.getTimeSignatures()
        >>> len(c) == 1
        True
        '''
        # even if this is a Measure, the TimeSignature in the Stream will be
        # found
        # post = self.getElementsByClass(meter.TimeSignature)
        post = self.getElementsByClass('TimeSignature').stream()

        # search activeSite Streams through contexts
        if not post and searchContext:
            # returns a single value
            post = self.cloneEmpty(derivationMethod='getTimeSignatures')

            # sort by time to search the most recent objects
            obj = self.getContextByClass('TimeSignature', sortByCreationTime=sortByCreationTime)
            # obj = self.previous('TimeSignature')
            # environLocal.printDebug(['getTimeSignatures(): searching contexts: results', obj])
            if obj is not None:
                post.append(obj)

        # if there is no timeSignature, we will look at any stream at offset 0:
        if not post:
            streamsAtStart = self.getElementsByOffset(0.0).getElementsByClass('Stream')
            for s in streamsAtStart:
                tss = s.getElementsByOffset(0.0).getElementsByClass('TimeSignature')
                for ts in tss:
                    post.append(ts)

        # get a default and/or place default at zero if nothing at zero
        if returnDefault:
            if not post or post[0].offset > 0:
                ts = meter.TimeSignature('%s/%s' % (defaults.meterNumerator,
                                                    defaults.meterDenominatorBeatType))
                post.insert(0, ts)
        # environLocal.printDebug(['getTimeSignatures(): final result:', post[0]])
        return post

    def getInstruments(self, searchActiveSite=True, returnDefault=True, recurse=False):
        '''
        Search this stream or activeSite streams for
        :class:`~music21.instrument.Instrument` objects, otherwise
        return a default Instrument
        '''
        # environLocal.printDebug(['searching for instrument, called from:',
        #                        self])
        # TODO: Rename: getInstruments, and return a Stream of instruments
        # for cases when there is more than one instrument

        instObj = None
        if recurse:
            sIter = self.recurse()
        else:
            sIter = self.iter

        post = sIter.getElementsByClass('Instrument').stream()
        if post:
            # environLocal.printDebug(['found local instrument:', post[0]])
            instObj = post[0]  # get first
        else:
            if searchActiveSite:
                # if isinstance(self.activeSite, Stream) and self.activeSite != self:
                if (self.activeSite is not None
                    and self.activeSite.isStream
                        and self.activeSite is not self):
                    # environLocal.printDebug(['searching activeSite Stream',
                    #    self, self.activeSite])
                    instObj = self.activeSite.getInstrument(
                        searchActiveSite=searchActiveSite,
                        returnDefault=False)
                    if instObj is not None:
                        post.insert(0, instObj)

        # if still not defined, get default
        if returnDefault and instObj is None:
            from music21.instrument import Instrument
            instObj = Instrument()
            # instObj.partId = defaults.partId  # give a default id
            # TODO: should this be changed to None? MSC 2015-12
            instObj.partName = defaults.partName  # give a default id
            post.insert(0, instObj)

        # returns a Stream
        return post

    def getInstrument(self, searchActiveSite=True, returnDefault=True):
        '''
        Return the first Instrument found in this Stream.

        >>> s = stream.Score()
        >>> p1 = stream.Part()
        >>> p1.insert(instrument.Violin())
        >>> m1p1 = stream.Measure()
        >>> m1p1.append(note.Note('g'))
        >>> p1.append(m1p1)

        >>> p2 = stream.Part()
        >>> p2.insert(instrument.Viola())
        >>> m1p2 = stream.Measure()
        >>> m1p2.append(note.Note('f#'))
        >>> p2.append(m1p2)

        >>> s.insert(0, p1)
        >>> s.insert(0, p2)
        >>> p1.getInstrument(returnDefault=False).instrumentName
        'Violin'
        >>> p2.getInstrument(returnDefault=False).instrumentName
        'Viola'
        '''
        post = self.getInstruments(searchActiveSite=searchActiveSite,
                                   returnDefault=returnDefault)
        if post:
            return post[0]
        else:
            return None

    def getClefs(self, searchActiveSite=False, searchContext=True,
                 returnDefault=True):
        '''
        DEPRECATED...

        Collect all :class:`~music21.clef.Clef` objects in
        this Stream in a list. Optionally search the
        activeSite Stream and/or contexts.

        If no Clef objects are defined, get a default
        using :meth:`~music21.clef.bestClef`


        >>> a = stream.Stream()
        >>> b = clef.AltoClef()
        >>> a.insert(0, b)
        >>> a.repeatInsert(note.Note('C#'), list(range(10)))
        >>> c = a.getClefs()
        >>> len(c) == 1
        True
        '''
        # TODO: activeSite searching is not yet implemented
        # this may not be useful unless a stream is flat
        post = list(self.getElementsByClass('Clef'))

        # environLocal.printDebug(['getClefs(); count of local', len(post), post])
        if not post and searchActiveSite and self.activeSite is not None:
            # environLocal.printDebug(['getClefs(): search activeSite'])
            post = list(self.activeSite.getElementsByClass('Clef'))

        if not post and searchContext:
            # returns a single element match
            # post = self.__class__()
            obj = self.getContextByClass('Clef')
            if obj is not None:
                post.append(obj)

        # get a default and/or place default at zero if nothing at zero
        if returnDefault and (not post or post[0].offset > 0):
            # environLocal.printDebug(['getClefs(): using bestClef()'])
            post.insert(0, clef.bestClef(self))
        return post

    def getKeySignatures(self, searchActiveSite=True, searchContext=True):
        '''
        Collect all :class:`~music21.key.KeySignature` objects in this
        Stream in a new Stream. Optionally search the activeSite
        stream and/or contexts.

        If no KeySignature objects are defined, returns an empty Stream

        TO BE DEPRECATED...


        >>> a = stream.Stream()
        >>> b = key.KeySignature(3)
        >>> a.insert(0, b)
        >>> a.repeatInsert(note.Note('C#'), list(range(10)))
        >>> c = a.getKeySignatures()
        >>> len(c) == 1
        True
        '''
        # TODO: activeSite searching is not yet implemented
        # this may not be useful unless a stream is flat
        post = self.getElementsByClass('KeySignature')
        if not post and searchContext:
            # returns a single value
            post = self.cloneEmpty(derivationMethod='getKeySignatures')
            obj = self.getContextByClass(key.KeySignature)
            if obj is not None:
                post.append(obj)

        # do nothing if empty
        if not post or post[0].offset > 0:
            pass
        return post

    def invertDiatonic(self, inversionNote=note.Note('C4'), *, inPlace=False):
        '''
        inverts a stream diatonically around the given note (by default, middle C)

        For pieces where the key signature
        does not change throughout the piece it is MUCH faster than
        for pieces where the key signature changes.

        Here in this test, we put Ciconia's Quod Jactatur (a single voice
        piece that should have a canon solution: see trecento.quodJactatur)
        into 3 flats (instead of its original 1 flat) in measure 1, but
        into 5 sharps in measure 2 and then invert around F4, creating
        a new piece.

        Changed in v. 5 -- inPlace is False by default.


        >>> qj = corpus.parse('ciconia/quod_jactatur').parts[0].measures(1, 2)
        >>> qj.id = 'measureExcerpt'

        >>> qj.show('text')
        {0.0} <music21.instrument.Instrument 'P1: MusicXML Part: Grand Piano'>
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.layout.SystemLayout>
            {0.0} <music21.clef.Treble8vbClef>
            {0.0} <music21.key.Key of F major>
            {0.0} <music21.meter.TimeSignature 2/4>
            {0.0} <music21.note.Note C>
            {1.5} <music21.note.Note D>
        {2.0} <music21.stream.Measure 2 offset=2.0>
            {0.0} <music21.note.Note E>
            {0.5} <music21.note.Note D>
            {1.0} <music21.note.Note C>
            {1.5} <music21.note.Note D>

        >>> qjFlat = qj.flat
        >>> k1 = qjFlat.getElementsByClass(key.KeySignature)[0]
        >>> k3flats = key.KeySignature(-3)
        >>> qjFlat.replace(k1, k3flats, allDerived=True)
        >>> qj.getElementsByClass(stream.Measure)[1].insert(0, key.KeySignature(5))

        >>> qj2 = qj.invertDiatonic(note.Note('F4'), inPlace=False)
        >>> qj2.show('text')
        {0.0} <music21.instrument.Instrument 'P1: MusicXML Part: Grand Piano'>
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.layout.SystemLayout>
            {0.0} <music21.clef.Treble8vbClef>
            {0.0} <music21.key.KeySignature of 3 flats>
            {0.0} <music21.meter.TimeSignature 2/4>
            {0.0} <music21.note.Note B->
            {1.5} <music21.note.Note A->
        {2.0} <music21.stream.Measure 2 offset=2.0>
            {0.0} <music21.key.KeySignature of 5 sharps>
            {0.0} <music21.note.Note G#>
            {0.5} <music21.note.Note A#>
            {1.0} <music21.note.Note B>
            {1.5} <music21.note.Note A#>
        '''
        if inPlace:
            returnStream = self
        else:
            returnStream = copy.deepcopy(self)

        keySigSearch = returnStream.recurse().getElementsByClass(key.KeySignature)

        quickSearch = True
        if not keySigSearch:
            ourKey = key.Key('C')
        elif len(keySigSearch) == 1:
            ourKey = keySigSearch[0]
        else:
            ourKey = None  # for might be undefined warning
            quickSearch = False

        inversionDNN = inversionNote.pitch.diatonicNoteNum
        for n in returnStream.recurse().getElementsByClass('NotRest'):
            n.pitch.diatonicNoteNum = (2 * inversionDNN) - n.pitch.diatonicNoteNum
            if quickSearch:  # use previously found
                n.pitch.accidental = ourKey.accidentalByStep(n.pitch.step)
            else:  # use context search
                ksActive = n.getContextByClass(key.KeySignature)
                n.pitch.accidental = ksActive.accidentalByStep(n.pitch.step)

            if n.pitch.accidental is not None:
                n.pitch.accidental.displayStatus = None

        if not inPlace:
            return returnStream

    # -------------------------------------------------------------------------
    # offset manipulation

    def shiftElements(self, offset, startOffset=None, endOffset=None, classFilterList=None):
        '''
        Add the given offset value to every offset of
        the objects found in the Stream. Objects that are
        specifically placed at the end of the Stream via
        .storeAtEnd() (such as right barlines) are
        not affected.

        If startOffset is given then all elements before
        that offset will be shifted.  If endOffset is given
        then all elements at or after this offset will be
        shifted

        >>> a = stream.Stream()
        >>> a.repeatInsert(note.Note('C'), list(range(10)))
        >>> a.shiftElements(30)
        >>> a.lowestOffset
        30.0
        >>> a.shiftElements(-10)
        >>> a.lowestOffset
        20.0

        Use shiftElements to move elements after a change in
        duration:

        >>> st2 = stream.Stream()
        >>> st2.insert(0, note.Note('D4', type='whole'))
        >>> st2.repeatInsert(note.Note('C4'), list(range(4, 8)))
        >>> st2.show('text')
        {0.0} <music21.note.Note D>
        {4.0} <music21.note.Note C>
        {5.0} <music21.note.Note C>
        {6.0} <music21.note.Note C>
        {7.0} <music21.note.Note C>

        Now make the first note a dotted whole note and shift the rest by two quarters...

        >>> firstNote = st2[0]
        >>> firstNoteOldQL = firstNote.quarterLength
        >>> firstNote.duration.dots = 1
        >>> firstNoteNewQL = firstNote.quarterLength
        >>> shiftAmount = firstNoteNewQL - firstNoteOldQL
        >>> shiftAmount
        2.0

        >>> st2.shiftElements(shiftAmount, startOffset=4.0)
        >>> st2.show('text')
        {0.0} <music21.note.Note D>
        {6.0} <music21.note.Note C>
        {7.0} <music21.note.Note C>
        {8.0} <music21.note.Note C>
        {9.0} <music21.note.Note C>

        A class filter list may be given.  It must be an iterable.

        >>> st2.insert(7.5, key.Key('F'))
        >>> st2.shiftElements(2/3, startOffset=6.0, endOffset=8.0,
        ...                   classFilterList=[note.Note])
        >>> st2.show('text')
        {0.0} <music21.note.Note D>
        {6.6667} <music21.note.Note C>
        {7.5} <music21.key.Key of F major>
        {7.6667} <music21.note.Note C>
        {8.0} <music21.note.Note C>
        {9.0} <music21.note.Note C>
        '''
        # only want _elements, do not want _endElements
        for e in self._elements:

            if startOffset is not None and self.elementOffset(e) < startOffset:
                continue
            if endOffset is not None and self.elementOffset(e) >= endOffset:
                continue
            if classFilterList is not None and not e.isClassOrSubclass(classFilterList):
                continue

            self.setElementOffset(e, opFrac(self.elementOffset(e) + offset))

        self.coreElementsChanged()

    def transferOffsetToElements(self):
        '''
        Transfer the offset of this stream to all
        internal elements; then set
        the offset of this stream to zero.

        >>> a = stream.Stream()
        >>> a.repeatInsert(note.Note('C'), list(range(10)))
        >>> a.offset = 30
        >>> a.transferOffsetToElements()
        >>> a.lowestOffset
        30.0
        >>> a.offset
        0.0
        >>> a.offset = 20
        >>> a.transferOffsetToElements()
        >>> a.lowestOffset
        50.0
        '''
        self.shiftElements(self.offset)
        self.offset = 0.0
        self.coreElementsChanged()

    # -------------------------------------------------------------------------
    # utilities for creating large numbers of elements

    def repeatAppend(self, item, numberOfTimes):
        '''
        Given an object and a number, run append that many times on
        a deepcopy of the object.
        numberOfTimes should of course be a positive integer.

        >>> a = stream.Stream()
        >>> n = note.Note('D--')
        >>> n.duration.type = 'whole'
        >>> a.repeatAppend(n, 10)
        >>> a.show('text')
        {0.0} <music21.note.Note D-->
        {4.0} <music21.note.Note D-->
        {8.0} <music21.note.Note D-->
        {12.0} <music21.note.Note D-->
        ...
        {36.0} <music21.note.Note D-->

        >>> a.duration.quarterLength
        40.0
        >>> a[9].offset
        36.0
        '''
        try:
            unused = item.isStream
            element = item
        # if not isinstance(item, music21.Music21Object):
        except AttributeError:
            # element = music21.ElementWrapper(item)
            raise StreamException('to put a non Music21Object in a stream, '
                                  + 'create a music21.ElementWrapper for the item')
        # # if not an element, embed
        # if not isinstance(item, music21.Music21Object):
        #     element = music21.ElementWrapper(item)
        # else:
        #     element = item

        for unused_i in range(numberOfTimes):
            self.append(copy.deepcopy(element))

    def repeatInsert(self, item, offsets):
        '''
        Given an object, create a deep copy of each object at
        each positions specified by
        the offset list:

        >>> a = stream.Stream()
        >>> n = note.Note('G-')
        >>> n.quarterLength = 1

        >>> a.repeatInsert(n, [0, 2, 3, 4, 4.5, 5, 6, 7, 8, 9, 10, 11, 12])
        >>> len(a)
        13
        >>> a[10].offset
        10.0
        '''
        if not common.isIterable(offsets):
            raise StreamException(f'must provide an iterable of offsets, not {offsets}')

        try:
            unused = item.isStream
            element = item
        # if not isinstance(item, music21.Music21Object):
        except AttributeError:
            # if not an element, embed
            # element = music21.ElementWrapper(item)
            raise StreamException('to put a non Music21Object in a stream, '
                                  + 'create a music21.ElementWrapper for the item')
        # if not isinstance(item, music21.Music21Object):
        #     # if not an element, embed
        #     element = music21.ElementWrapper(item)
        # else:
        #     element = item

        for offset in offsets:
            elementCopy = copy.deepcopy(element)
            self.coreInsert(offset, elementCopy)
        self.coreElementsChanged()

    def extractContext(self, searchElement, before=4.0, after=4.0,
                       maxBefore=None, maxAfter=None, forceOutputClass=None):
        '''
        Extracts elements around the given element within (before)
        quarter notes and (after) quarter notes (default 4), and
        returns a new Stream.

        >>> qn = note.Note(type='quarter')
        >>> qtrStream = stream.Stream()
        >>> qtrStream.repeatInsert(qn, [0, 1, 2, 3, 4, 5])
        >>> hn = note.Note(type='half')
        >>> hn.name = 'B-'
        >>> qtrStream.append(hn)
        >>> qtrStream.repeatInsert(qn, [8, 9, 10, 11])
        >>> hnStream = qtrStream.extractContext(hn, 1.0, 1.0)
        >>> hnStream.show('text')
        {5.0} <music21.note.Note C>
        {6.0} <music21.note.Note B->
        {8.0} <music21.note.Note C>

        OMIT_FROM_DOCS
        TODO: maxBefore -- maximum number of elements to return before; etc.
        TODO: use .template to get new Stream

        NOTE: RENAME: this probably should be renamed, as we use Context in a special way.
        Perhaps better is extractNeighbors?

        '''
        if forceOutputClass is None:
            display = self.__class__()
        else:
            # is this ever used? prevents using .cloneEmpty('extractContext')
            display = forceOutputClass()

        display.derivation.origin = self
        display.derivation.method = 'extractContext'

        found = None
        foundOffset = 0
        foundEnd = 0
        elements = self.elements
        for i in range(len(elements)):
            b = elements[i]
            if b.id == searchElement.id:
                found = i
                foundOffset = self.elementOffset(elements[i])
                foundEnd = foundOffset + elements[i].duration.quarterLength
            elif b is searchElement:
                found = i
                foundOffset = self.elementOffset(elements[i])
                foundEnd = foundOffset + elements[i].duration.quarterLength
        if found is None:
            raise StreamException('Could not find the element in the stream')

        # handle _elements and _endElements independently
        for e in self._elements:
            o = self.elementOffset(e)
            if foundOffset - before <= o < foundEnd + after:
                display.coreInsert(o, e)

        for e in self._endElements:
            o = self.elementOffset(e)
            if foundOffset - before <= o < foundEnd + after:
                display.coreStoreAtEnd(e)
        display.coreElementsChanged()
        return display

    # --------------------------------------------------------------------------
    # transformations of self that return a new Stream

    def _uniqueOffsetsAndEndTimes(self, offsetsOnly=False, endTimesOnly=False):
        '''
        Get a list of all offsets and endtimes
        of notes and rests in this stream.

        Helper method for makeChords and Chordify
        run on .flat.notesAndRests


        >>> s = stream.Score()
        >>> p1 = stream.Part()
        >>> p1.insert(4, note.Note('C#'))
        >>> p1.insert(5.3, note.Rest())
        >>> p2 = stream.Part()
        >>> p2.insert(2.12, note.Note('D-', type='half'))
        >>> p2.insert(5.5, note.Rest())
        >>> s.insert(0, p1)
        >>> s.insert(0, p2)

        We will get a mix of float and fractions.Fraction() objects

        >>> [str(o) for o in s.flat._uniqueOffsetsAndEndTimes()]
        ['53/25', '4.0', '103/25', '5.0', '53/10', '5.5', '63/10', '6.5']

        Limit what is returned:

        >>> [str(o) for o in s.flat._uniqueOffsetsAndEndTimes(offsetsOnly=True)]
        ['53/25', '4.0', '53/10', '5.5']
        >>> [str(o) for o in s.flat._uniqueOffsetsAndEndTimes(endTimesOnly=True)]
        ['103/25', '5.0', '63/10', '6.5']

        And this is useless...  :-)

        >>> s.flat._uniqueOffsetsAndEndTimes(offsetsOnly=True, endTimesOnly=True)
        []

        '''
        offsetDictValues = self._offsetDict.values()
        if endTimesOnly:
            offsets = set()
        else:
            offsets = {opFrac(v[0]) for v in offsetDictValues}

        if offsetsOnly:
            endTimes = set()
        else:
            endTimes = {opFrac(v[0] + v[1].duration.quarterLength)
                            for v in offsetDictValues}
        return sorted(offsets.union(endTimes))

    def makeChords(self,
                   minimumWindowSize=0.125,
                   includePostWindow=True,
                   removeRedundantPitches=True,
                   useExactOffsets=False,
                   gatherArticulations=True,
                   gatherExpressions=True,
                   inPlace=False,
                   transferGroupsToPitches=False,
                   makeRests=True):
        '''
        TO BE DEPRECATED SOON!  Use Chordify instead!

        Gathers simultaneously sounding :class:`~music21.note.Note` objects
        into :class:`~music21.chord.Chord` objects, each of which
        contains all the pitches sounding together.

        If useExactOffsets is True (default=False), then do an exact
        makeChords using the offsets in the piece.
        If this parameter is set, then minimumWindowSize is ignored.

        This first example puts a part with three quarter notes (C4, D4, E4)
        together with a part consisting of a half note (C#5) and a
        quarter note (E#5) to make two Chords, the first containing the
        three :class:`~music21.pitch.Pitch` objects sounding at the
        beginning, the second consisting of the two Pitches sounding
        on offset 2.0 (beat 3):

        >>> p1 = stream.Part()
        >>> p1.append([note.Note('C4', type='quarter'),
        ...            note.Note('D4', type='quarter'),
        ...            note.Note('E4', type='quarter'),
        ...            note.Note('B2', type='quarter')])
        >>> p2 = stream.Part()
        >>> p2.append([note.Note('C#5', type='half'),
        ...            note.Note('E#5', type='quarter'),
        ...            chord.Chord(['E4', 'G5', 'C#7'])])
        >>> sc1 = stream.Score()
        >>> sc1.insert(0, p1)
        >>> sc1.insert(0, p2)
        >>> scChords = sc1.flat.makeChords()
        >>> scChords.show('text')
        {0.0} <music21.chord.Chord C4 C#5 D4>
        {2.0} <music21.chord.Chord E4 E#5>
        {3.0} <music21.chord.Chord B2 E4 G5 C#7>

        The gathering of elements, starting from offset 0.0, uses
        the `minimumWindowSize`, in quarter lengths, to collect
        all Notes that start between 0.0 and the minimum window
        size (this permits overlaps within a minimum tolerance).

        After collection, the maximum duration of collected elements
        is found; this duration is then used to set the new
        starting offset. A possible gap then results between the end
        of the window and offset specified by the maximum duration;
        these additional notes are gathered in a second pass if
        `includePostWindow` is True.

        The new start offset is shifted to the larger of either
        the minimum window or the maximum duration found in the
        collected group. The process is repeated until all offsets
        are covered.

        Each collection of Notes is formed into a Chord. The Chord
        is given the longest duration of all constituents, and is
        inserted at the start offset of the window from which it
        was gathered.

        Chords can gather both articulations and expressions from found Notes
        using `gatherArticulations` and `gatherExpressions`.

        If `transferGroupsToPitches` is True, and group defined on the source
        elements Groups object will be transferred to the Pitch
        objects contained in the resulting Chord.

        The resulting Stream, if not in-place, can also gather
        additional objects by placing class names in the `collect` list.
        By default, TimeSignature and KeySignature objects are collected.
        '''
        if not inPlace:  # make a copy
            # since we do not return Scores, this probably should always be
            # a Stream
            # returnObj = Stream()
            # returnObj = self.__class__()  # for output
            returnObj = copy.deepcopy(self)
            returnObj.derivation.origin = self
            returnObj.derivation.method = 'makeChords'
        else:
            returnObj = self

        def dealWithSubNotes(chordLength, noteList):
            # environLocal.printDebug(['creating chord from noteList',
            #   noteList, 'inPlace', inPlace])
            c = chord.Chord()
            c.duration.quarterLength = chordLength
            # these are references, not copies, for now
            tempComponents = []
            for n in noteList:
                if n.isChord:
                    cSub = list(n)
                else:
                    cSub = [n]

                if transferGroupsToPitches and n.groups:
                    for comp in cSub:
                        for g in n.groups:
                            comp.pitch.groups.append(g)

                for comp in cSub:
                    tempComponents.append(comp)

            c.pitches = [comp.pitch for comp in tempComponents]
            for comp in tempComponents:
                if comp.tie is not None:
                    c.setTie(comp.tie.type, comp.pitch)

            if gatherArticulations:
                for n in noteList:
                    c.articulations += n.articulations
            if gatherExpressions:
                for n in noteList:
                    c.expressions += n.expressions
            # always remove all the previous elements
            for n in noteList:
                returnObj.remove(n)
            # remove all rests found in source
            for r in list(returnObj.getElementsByClass('Rest')):
                returnObj.remove(r)

            if removeRedundantPitches:
                removedPitches = c.removeRedundantPitches(inPlace=True)

                if transferGroupsToPitches:
                    for rem_p, cn in itertools.product(removedPitches, c):
                        if cn.pitch.nameWithOctave == rem_p.nameWithOctave:
                            # print(cn.pitch, rem_p)
                            # print(cn.pitch.groups, rem_p.groups)
                            cn.pitch.groups.extend(rem_p.groups)
            return c
        # environLocal.printDebug(['makeChords():',
        #              'transferGroupsToPitches', transferGroupsToPitches])

        if returnObj.hasMeasures():
            # call on component measures
            for m in returnObj.getElementsByClass('Measure'):
                # offset values are not relative to measure; need to
                # shift by each measure's offset
                m.makeChords(
                    minimumWindowSize=minimumWindowSize,
                    includePostWindow=includePostWindow,
                    removeRedundantPitches=removeRedundantPitches,
                    gatherArticulations=gatherArticulations,
                    gatherExpressions=gatherExpressions,
                    transferGroupsToPitches=transferGroupsToPitches,
                    inPlace=True,
                    makeRests=makeRests
                )
            return returnObj  # exit

        if returnObj.hasPartLikeStreams():
            # must get Streams, not Parts here
            for p in returnObj.getElementsByClass('Stream'):
                p.makeChords(
                    minimumWindowSize=minimumWindowSize,
                    includePostWindow=includePostWindow,
                    removeRedundantPitches=removeRedundantPitches,
                    gatherArticulations=gatherArticulations,
                    gatherExpressions=gatherExpressions,
                    transferGroupsToPitches=transferGroupsToPitches,
                    inPlace=True,
                    makeRests=makeRests
                )
            return returnObj  # exit

        # TODO: gather lyrics as an option
        # define classes that are gathered; assume they have pitches
        # matchClasses = ['Note', 'Chord', 'Rest']
        matchClasses = ['Note', 'Chord']
        o = 0.0  # start at zero
        oTerminate = returnObj.highestOffset

        # get temporary boundaries for making rests
        preHighestTime = returnObj.highestTime
        preLowestOffset = returnObj.lowestOffset
        # environLocal.printDebug(['got preLowest, preHighest', preLowestOffset, preHighestTime])
        if useExactOffsets is False:
            while True:  # TODO: Remove while True always...
                # get all notes within the start and the min window size
                oStart = o
                oEnd = oStart + minimumWindowSize
                subNotes = list(returnObj.getElementsByOffset(
                    oStart,
                    oEnd,
                    includeEndBoundary=False,
                    mustFinishInSpan=False,
                    mustBeginInSpan=True
                ).getElementsByClass(matchClasses))  # get once for speed
                # environLocal.printDebug(['subNotes', subNotes])
                qlMax: Optional[float] = None
                # get the max duration found from within the window
                if subNotes:
                    # get largest duration, use for duration of Chord, next span
                    qlMax = max([n.quarterLength for n in subNotes])

                # if the max duration found in the window is greater than the min
                # window size, it is possible that there are notes that will not
                # be gathered; those starting at the end of this window but before
                # the max found duration (as that will become the start of the next
                # window
                # so: if ql > min window, gather notes between
                # oStart + minimumWindowSize and oStart + qlMax
                if (includePostWindow and qlMax is not None
                        and qlMax > minimumWindowSize):
                    subAdd = list(returnObj.getElementsByOffset(
                        oStart + minimumWindowSize,
                        oStart + qlMax,
                        includeEndBoundary=False,
                        mustFinishInSpan=False,
                        mustBeginInSpan=True
                    ).getElementsByClass(matchClasses))
                    # concatenate any additional notes found
                    subNotes += subAdd

                # make subNotes into a chord
                if subNotes:
                    cOut = dealWithSubNotes(qlMax, subNotes)
                    # insert chord at start location
                    returnObj.coreInsert(o, cOut)
                # environLocal.printDebug(['len of returnObj', len(returnObj)])
                # shift offset to qlMax or minimumWindowSize
                if qlMax is not None and qlMax >= minimumWindowSize:
                    # update start offset to what was old boundary
                    # note: this assumes that the start of the longest duration
                    # was at oStart; it could have been between oStart and oEnd
                    o += qlMax
                else:
                    o += minimumWindowSize
                # end While loop conditions
                if o > oTerminate:
                    break
        else:  # useExactOffsets is True:
            onAndOffOffsets = self.flat.notesAndRests.stream()._uniqueOffsetsAndEndTimes()
            # environLocal.printDebug(['makeChords: useExactOffsets=True;
            #   onAndOffOffsets:', onAndOffOffsets])

            for i in range(len(onAndOffOffsets) - 1):
                # get all notes within the start and the min window size
                oStart = onAndOffOffsets[i]
                oEnd = onAndOffOffsets[i + 1]
                subNotes = list(returnObj.getElementsByOffset(
                    oStart,
                    oEnd,
                    includeEndBoundary=False,
                    mustFinishInSpan=False,
                    mustBeginInSpan=True
                ).getElementsByClass(matchClasses))
                # environLocal.printDebug(['subNotes', subNotes])
                # subNotes.show('t')

                # make subNotes into a chord
                if subNotes:
                    cOut = dealWithSubNotes(oEnd - oStart, subNotes)
                    # insert chord at start location
                    returnObj.coreInsert(oStart, cOut)

        # makeRests to fill any gaps produced by stripping
        # environLocal.printDebug(['pre makeRests show()'])
        returnObj.coreElementsChanged()
        if makeRests:
            returnObj.makeRests(
                refStreamOrTimeRange=(preLowestOffset, preHighestTime),
                fillGaps=True, inPlace=True)
        return returnObj

    def chordify(
        self,
        *,
        addTies=True,
        addPartIdAsGroup=False,
        removeRedundantPitches=True,
        toSoundingPitch=True,
        copyPitches=True,
    ):
        # noinspection PyShadowingNames
        '''
        Create a chordal reduction of polyphonic music, where each
        change to a new pitch results in a new chord. If a Score or
        Part of Measures is provided, a Stream of Measures will be
        returned. If a flat Stream of notes, or a Score of such
        Streams is provided, no Measures will be returned.

        If using chordify with chord symbols, ensure that the chord symbols
        have durations (by default the duration of a chord symbol object is 0, unlike
        a chord object). If harmony objects are not provided a duration, they
        will not be included in the chordified output pitches but may appear as chord symbol
        in notation on the score. To realize the chord symbol durations on a score, call
        :meth:`music21.harmony.realizeChordSymbolDurations` and pass in the score.

        This functionality works by splitting all Durations in
        all parts, or if multi-part by all unique offsets. All
        simultaneous durations are then gathered into single chords.

        If `addPartIdAsGroup` is True, all elements found in the
        Stream will have their source Part id added to the
        element's pitches' Group.  These groups names are useful
        for partially "de-chordifying" the output.  If the element chordifies to
        a :class:`~music21.chord.Chord` object, then the group will be found in each
        :class:`~music21.pitch.Pitch` element's .groups in Chord.pitches.  If the
        element chordifies to a single :class:`~music21.note.Note` then .pitch.groups
        will hold the group name.

        The `addTies` parameter currently does not work for pitches in Chords.

        If `toSoundingPitch` is True, all parts that define one or
        more transpositions will be transposed to sounding pitch before chordification.
        True by default.

        >>> s = stream.Score()
        >>> p1 = stream.Part()
        >>> p1.id = 'part1'
        >>> p1.insert(4, note.Note('C#4'))
        >>> p1.insert(5.3, note.Rest())
        >>> p2 = stream.Part()
        >>> p2.id = 'part2'
        >>> p2.insert(2.12, note.Note('D-4', type='half'))
        >>> p2.insert(5.5, note.Rest())
        >>> s.insert(0, p1)
        >>> s.insert(0, p2)
        >>> s.show('text', addEndTimes=True)
        {0.0 - 6.3} <music21.stream.Part part1>
            {4.0 - 5.0} <music21.note.Note C#>
            {5.3 - 6.3} <music21.note.Rest rest>
        {0.0 - 6.5} <music21.stream.Part part2>
            {2.12 - 4.12} <music21.note.Note D->
            {5.5 - 6.5} <music21.note.Rest rest>

        >>> cc = s.chordify()

        >>> cc[3]
        <music21.chord.Chord C#4>
        >>> cc[3].duration.quarterLength
        Fraction(22, 25)

        >>> cc.show('text', addEndTimes=True)
        {0.0 - 2.12} <music21.note.Rest rest>
        {2.12 - 4.0} <music21.chord.Chord D-4>
        {4.0 - 4.12} <music21.chord.Chord C#4 D-4>
        {4.12 - 5.0} <music21.chord.Chord C#4>
        {5.0 - 6.5} <music21.note.Rest rest>

        Here's how addPartIdAsGroup works:

        >>> cc2 = s.chordify(addPartIdAsGroup=True)
        >>> cSharpDFlatChord = cc2[2]
        >>> for p in cSharpDFlatChord.pitches:
        ...     (str(p), p.groups)
        ('C#4', ['part1'])
        ('D-4', ['part2'])

        >>> s = stream.Stream()
        >>> p1 = stream.Part()
        >>> p1.insert(0, harmony.ChordSymbol('Cm', quarterLength=4.0))
        >>> p1.insert(2, note.Note('C2'))
        >>> p1.insert(4, harmony.ChordSymbol('D', quarterLength=4.0))
        >>> p1.insert(7, note.Note('A2'))
        >>> s.insert(0, p1)
        >>> s.chordify().show('text')
        {0.0} <music21.chord.Chord C3 E-3 G3>
        {2.0} <music21.chord.Chord C2 C3 E-3 G3>
        {3.0} <music21.chord.Chord C3 E-3 G3>
        {4.0} <music21.chord.Chord D3 F#3 A3>
        {7.0} <music21.chord.Chord A2 D3 F#3 A3>

        Note that :class:`~music21.harmony.ChordSymbol` objects can also be chordified:

        >>> s = stream.Stream()
        >>> p2 = stream.Part()
        >>> p1 = stream.Part()
        >>> p2.insert(0, harmony.ChordSymbol('Cm', quarterLength=4.0))
        >>> p1.insert(2, note.Note('C2'))
        >>> p2.insert(4, harmony.ChordSymbol('D', quarterLength=4.0))
        >>> p1.insert(7, note.Note('A2'))
        >>> s.insert(0, p1)
        >>> s.insert(0, p2)
        >>> s.chordify().show('text')
        {0.0} <music21.chord.Chord C3 E-3 G3>
        {2.0} <music21.chord.Chord C2 C3 E-3 G3>
        {3.0} <music21.chord.Chord C3 E-3 G3>
        {4.0} <music21.chord.Chord D3 F#3 A3>
        {7.0} <music21.chord.Chord A2 D3 F#3 A3>

        If addPartIdAsGroup is True, and there are redundant pitches,
        ensure that the merged pitch has both groups

        >>> s = stream.Score()
        >>> p0 = stream.Part(id='p0')
        >>> p0.insert(0, note.Note('C4'))
        >>> p1 = stream.Part(id='p1')
        >>> p1.insert(0, note.Note('C4'))
        >>> s.insert(0, p0)
        >>> s.insert(0, p1)
        >>> s1 = s.chordify(addPartIdAsGroup=True)
        >>> c = s1.recurse().notes[0]
        >>> c
        <music21.chord.Chord C4>
        >>> c.pitches[0].groups
        ['p0', 'p1']

        With copyPitches = False, then the original pitches are retained, which
        together with removeRedundantPitches=False can be a powerful tool for
        working back to the original score:

        >>> n00 = note.Note('C4')
        >>> n01 = note.Note('E4')
        >>> n10 = note.Note('E4', type='half')
        >>> p0 = stream.Part(id='p0')
        >>> p1 = stream.Part(id='p1')
        >>> p0.append([n00, n01])
        >>> p1.append(n10)
        >>> s = stream.Score()
        >>> s.insert(0, p0)
        >>> s.insert(0, p1)
        >>> ss = s.chordify(removeRedundantPitches=False, copyPitches=False, addPartIdAsGroup=True)
        >>> ss.show('text')
        {0.0} <music21.chord.Chord C4 E4>
        {1.0} <music21.chord.Chord E4 E4>

        >>> c1 = ss.recurse().notes[1]
        >>> for p in c1.pitches:
        ...     if 'p0' in p.groups:
        ...         p.step = 'G'  # make a complete triad
        >>> n01
        <music21.note.Note G>

        Changes in v.5:

        Runs a little faster for small scores and run a TON faster for big scores
        running in O(n) time not O(n^2)

        no longer supported: displayTiedAccidentals=False,

        Changes in v.6.3:

        Added copyPitches

        OMIT_FROM_DOCS

        Test that chordifying works on a single stream.

        >>> f2 = stream.Score()
        >>> f2.insert(0, metadata.Metadata())
        >>> f2.insert(0, note.Note('C4'))
        >>> f2.insert(0, note.Note('D#4'))
        >>> c = f2.chordify()
        >>> cn = c.notes
        >>> cn[0].pitches
        (<music21.pitch.Pitch C4>, <music21.pitch.Pitch D#4>)
        '''
        def chordifyOneMeasure(templateInner, streamToChordify):
            timespanTree = streamToChordify.asTimespans(classList=('GeneralNote',))
            allTimePoints = timespanTree.allTimePoints()
            if 0 not in allTimePoints:
                allTimePoints = (0,) + allTimePoints

            for offset, endTime in zip(allTimePoints, allTimePoints[1:]):
                if common.almostEquals(offset, endTime):
                    continue
                vert = timespanTree.getVerticalityAt(offset)
                quarterLength = endTime - offset
                if quarterLength < 0:
                    environLocal.warn(
                        'Something is wrong with the verticality '
                        + f'in stream {templateInner!r}: {vert!r} '
                        + f'its endTime {endTime} is less than its offset {offset}'
                    )

                chordOrRest = vert.makeElement(quarterLength,
                                               addTies=addTies,
                                               addPartIdAsGroup=addPartIdAsGroup,
                                               removeRedundantPitches=removeRedundantPitches,
                                               copyPitches=copyPitches,
                                               )

                templateInner.coreInsert(opFrac(offset), chordOrRest)
            templateInner.coreElementsChanged()
            consolidateRests(templateInner)

        def consolidateRests(templateInner):
            consecutiveRests = []
            for el in list(templateInner.getElementsByClass('GeneralNote')):
                if 'Rest' not in el.classes:
                    removeConsecutiveRests(templateInner, consecutiveRests)
                    consecutiveRests = []
                else:
                    consecutiveRests.append(el)
            removeConsecutiveRests(templateInner, consecutiveRests)

        def removeConsecutiveRests(templateInner, consecutiveRests):
            if len(consecutiveRests) < 2:
                return
            totalDuration = sum(r.duration.quarterLength for r in consecutiveRests)
            startOffset = templateInner.elementOffset(consecutiveRests[0])
            for r in consecutiveRests:
                templateInner.remove(r)
            rNew = note.Rest()
            rNew.duration.quarterLength = totalDuration
            templateInner.insert(startOffset, rNew)

        # --------------------------------------
        if toSoundingPitch:
            # environLocal.printDebug(['at sounding pitch', allParts[0].atSoundingPitch])
            if (self.hasPartLikeStreams()
                     and self.getElementsByClass('Stream')[0].atSoundingPitch is False):
                workObj = self.toSoundingPitch(inPlace=False)
            elif self.atSoundingPitch is False:
                workObj = self.toSoundingPitch(inPlace=False)
            else:
                workObj = self
        else:
            workObj = self

        if self.hasPartLikeStreams():
            templateStream = workObj.getElementsByClass('Stream')[0]
        else:
            templateStream = workObj

        template = templateStream.template(fillWithRests=False,
                                           removeClasses=('GeneralNote',),
                                           retainVoices=False)

        if template.hasMeasures():
            measureIterator = template.getElementsByClass('Measure')
            for i, templateMeasure in enumerate(measureIterator):
                measurePart = workObj.measure(i, collect=(), indicesNotNumbers=True)
                if measurePart is not None:
                    chordifyOneMeasure(templateMeasure, measurePart)
                else:
                    environLocal.warn(f'Malformed Part object, {workObj}, at measure index {i}')
        else:
            chordifyOneMeasure(template, workObj)

        # accidental displayStatus needs to change.
        for p in template.pitches:
            if p.accidental is not None and p.accidental.displayStatus != 'even-tied':
                p.accidental.displayStatus = None

        if (hasattr(workObj, 'metadata')
                and workObj.metadata is not None
                and workObj.hasPartLikeStreams() is True):
            template.insert(0, copy.deepcopy(workObj.metadata))

        return template

    def splitByClass(self, classObj, fx):
        # noinspection PyShadowingNames
        '''
        Given a stream, get all objects of type classObj and divide them into
        two new streams depending on the results of fx.
        Fx should be a lambda or other function on elements.
        All elements where fx returns True go in the first stream.
        All other elements are put in the second stream.

        If classObj is None then all elements are returned.  ClassObj
        can also be a list of classes.

        In this example, we will create 50 notes from midi note 30 (two
        octaves and a tritone below middle C) to midi note 80 (an octave
        and a minor sixth above middle C) and add them to a Stream.
        We then create a lambda function to split between those notes
        below middle C (midi note 60) and those above
        (google "lambda functions in Python" for more information on
        what these powerful tools are).


        >>> stream1 = stream.Stream()
        >>> for x in range(30, 81):
        ...     n = note.Note()
        ...     n.pitch.midi = x
        ...     stream1.append(n)
        >>> fx = lambda n: n.pitch.midi < 60
        >>> b, c = stream1.splitByClass(note.Note, fx)

        Stream b now contains all the notes below middle C,
        that is, 30 notes, beginning with F#1 and ending with B3
        while Stream c has the 21 notes from C4 to A-5:

        >>> len(b)
        30
        >>> (b[0].nameWithOctave, b[-1].nameWithOctave)
        ('F#1', 'B3')
        >>> len(c)
        21
        >>> (c[0].nameWithOctave, c[-1].nameWithOctave)
        ('C4', 'G#5')
        '''
        a = self.cloneEmpty(derivationMethod='splitByClass')
        b = self.cloneEmpty(derivationMethod='splitByClass')
        if classObj is not None:
            found = self.getElementsByClass(classObj).stream()
        else:
            found = self
        for e in found:
            if fx(e):
                a.coreInsert(found.elementOffset(e), e)  # provide an offset here
            else:
                b.coreInsert(found.elementOffset(e), e)
        for e in found._endElements:
            if fx(e):
                # a.storeAtEnd(e)
                a.coreStoreAtEnd(e)
            else:
                # b.storeAtEnd(e)
                b.coreStoreAtEnd(e)
        a.coreElementsChanged()
        b.coreElementsChanged()
        return a, b

    def offsetMap(self, srcObj=None):
        '''
        Returns a list where each element is a NamedTuple
        consisting of the 'offset' of each element in a stream, the
        'endTime' (that is, the offset plus the duration) and the
        'element' itself.  Also contains a 'voiceIndex' entry which
        contains the voice number of the element, or None if there
        are no voices.

        >>> n1 = note.Note(type='quarter')
        >>> c1 = clef.AltoClef()
        >>> n2 = note.Note(type='half')
        >>> s1 = stream.Stream()
        >>> s1.append([n1, c1, n2])
        >>> om = s1.offsetMap()
        >>> om[2].offset
        1.0
        >>> om[2].endTime
        3.0
        >>> om[2].element is n2
        True
        >>> om[2].voiceIndex


        Needed for makeMeasures and a few other places

        The Stream source of elements is self by default,
        unless a `srcObj` is provided.


        >>> s = stream.Stream()
        >>> s.repeatAppend(note.Note(), 8)
        >>> for om in s.offsetMap():
        ...     om
        OffsetMap(element=<music21.note.Note C>, offset=0.0, endTime=1.0, voiceIndex=None)
        OffsetMap(element=<music21.note.Note C>, offset=1.0, endTime=2.0, voiceIndex=None)
        OffsetMap(element=<music21.note.Note C>, offset=2.0, endTime=3.0, voiceIndex=None)
        OffsetMap(element=<music21.note.Note C>, offset=3.0, endTime=4.0, voiceIndex=None)
        OffsetMap(element=<music21.note.Note C>, offset=4.0, endTime=5.0, voiceIndex=None)
        OffsetMap(element=<music21.note.Note C>, offset=5.0, endTime=6.0, voiceIndex=None)
        OffsetMap(element=<music21.note.Note C>, offset=6.0, endTime=7.0, voiceIndex=None)
        OffsetMap(element=<music21.note.Note C>, offset=7.0, endTime=8.0, voiceIndex=None)
        '''
        if srcObj is None:
            srcObj = self
        # assume that flat/sorted options will be set before processing
        offsetMap = []  # list of start, start+dur, element
        if srcObj.hasVoices():
            groups = []
            for i, v in enumerate(srcObj.voices):
                groups.append((v.flat, i))
            elsNotOfVoice = srcObj.getElementsNotOfClass('Voice')
            if len(elsNotOfVoice) > 0:
                groups.insert(0, (elsNotOfVoice, None))
        else:  # create a single collection
            groups = [(srcObj, None)]
        # environLocal.printDebug(['offsetMap', groups])
        for group, voiceIndex in groups:
            for e in group._elements:
                # do not include barlines
                if isinstance(e, bar.Barline):
                    continue
                dur = e.duration.quarterLength
                offset = group.elementOffset(e)
                endTime = opFrac(offset + dur)
                # NOTE: used to make a copy.copy of elements here;
                # this is not necessary b/c making deepcopy of entire Stream
                thisOffsetMap = _OffsetMap(e, offset, endTime, voiceIndex)
                # environLocal.printDebug(['offsetMap: thisOffsetMap', thisOffsetMap])
                offsetMap.append(thisOffsetMap)
                # offsetMap.append((offset, offset + dur, e, voiceIndex))
                # offsetMap.append([offset, offset + dur, copy.copy(e)])
        return offsetMap

    def makeMeasures(
        self,
        meterStream=None,
        refStreamOrTimeRange=None,
        searchContext=False,
        innerBarline=None,
        finalBarline='final',
        bestClef=False,
        inPlace=False,
    ):
        '''
        Return a new stream (or if inPlace=True change in place) this
        Stream so that it has internal measures.

        For more details, see :py:func:`~music21.stream.makeNotation.makeMeasures`.
        '''
        return makeNotation.makeMeasures(
            self,
            meterStream=meterStream,
            refStreamOrTimeRange=refStreamOrTimeRange,
            searchContext=searchContext,
            innerBarline=innerBarline,
            finalBarline=finalBarline,
            bestClef=bestClef,
            inPlace=inPlace,
        )

    def makeRests(
        self,
        refStreamOrTimeRange=None,
        fillGaps=False,
        timeRangeFromBarDuration=False,
        inPlace=True,
        hideRests=False,
    ):
        '''
        Calls :py:func:`~music21.stream.makeNotation.makeRests`.
        '''
        return makeNotation.makeRests(
            self,
            refStreamOrTimeRange=refStreamOrTimeRange,
            fillGaps=fillGaps,
            timeRangeFromBarDuration=timeRangeFromBarDuration,
            inPlace=inPlace,
            hideRests=hideRests,
        )

    def makeTies(self,
                 meterStream=None,
                 inPlace=False,
                 displayTiedAccidentals=False,
                 ):
        '''
        Calls :py:func:`~music21.stream.makeNotation.makeTies`.

        Changed in v.4., inPlace=False by default.
        '''
        return makeNotation.makeTies(
            self,
            meterStream=meterStream,
            inPlace=inPlace,
            displayTiedAccidentals=displayTiedAccidentals,
        )

    def makeBeams(self, *, inPlace=False):
        '''
        Return a new Stream, or modify the Stream in place, with beams applied to all
        notes.

        See :py:func:`~music21.stream.makeNotation.makeBeams`.
        '''
        return makeNotation.makeBeams(
            self,
            inPlace=inPlace,
        )

    def makeAccidentals(
        self,
        *,
        pitchPast: Optional[List[pitch.Pitch]] = None,
        pitchPastMeasure: Optional[List[pitch.Pitch]] = None,
        useKeySignature: Union[bool, key.KeySignature] = True,
        alteredPitches: Optional[List[pitch.Pitch]] = None,
        searchKeySignatureByContext: bool = False,
        cautionaryPitchClass: bool = True,
        cautionaryAll: bool = False,
        inPlace: bool = True,
        overrideStatus: bool = False,
        cautionaryNotImmediateRepeat: bool = True,
        tiePitchSet: Optional[Set[str]] = None
    ):
        '''
        A method to set and provide accidentals given various conditions and contexts.

        `pitchPast` is a list of pitches preceding this pitch in this measure.

        `pitchPastMeasure` is a list of pitches preceding this pitch but in a previous measure.


        If `useKeySignature` is True, a :class:`~music21.key.KeySignature` will be searched
        for in this Stream or this Stream's defined contexts. An alternative KeySignature
        can be supplied with this object and used for temporary pitch processing.

        If `alteredPitches` is a list of modified pitches (Pitches with Accidentals) that
        can be directly supplied to Accidental processing. These are the same values obtained
        from a :class:`music21.key.KeySignature` object using the
        :attr:`~music21.key.KeySignature.alteredPitches` property.

        If `cautionaryPitchClass` is True, comparisons to past accidentals are made regardless
        of register. That is, if a past sharp is found two octaves above a present natural,
        a natural sign is still displayed.

        If `cautionaryAll` is True, all accidentals are shown.

        If `overrideStatus` is True, this method will ignore any current `displayStatus` stetting
        found on the Accidental. By default this does not happen. If `displayStatus` is set to
        None, the Accidental's `displayStatus` is set.

        If `cautionaryNotImmediateRepeat` is True, cautionary accidentals will be displayed for
        an altered pitch even if that pitch had already been displayed as altered.

        If `tiePitchSet` is not None it should be a set of `.nameWithOctave` strings
        to determine whether following accidentals should be shown because the last
        note of the same pitch had a start or continue tie.

        If `searchKeySignatureByContext` is True then keySignatures from the context of the
        stream will be used if none found.

        The :meth:`~music21.pitch.Pitch.updateAccidentalDisplay` method is used to determine if
        an accidental is necessary.

        This will assume that the complete Stream is the context of evaluation. For smaller context
        ranges, call this on Measure objects.

        If `inPlace` is True, this is done in-place; if `inPlace` is False,
        this returns a modified deep copy.

        TODO: inPlace default will become False in v.7.

        Changed in v.6: does not return anything if inPlace is True.
        All arguments are keyword only.
        '''
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        # need to reset these lists unless values explicitly provided
        if pitchPast is None:
            pitchPast = []
        if pitchPastMeasure is None:
            pitchPastMeasure = []
        # see if there is any key signatures to add to altered pitches
        if alteredPitches is None:
            alteredPitches = []
        addAlteredPitches: List[pitch.Pitch] = []
        if isinstance(useKeySignature, key.KeySignature):
            addAlteredPitches = useKeySignature.alteredPitches
        elif useKeySignature is True:  # get from defined contexts
            # will search local, then activeSite
            ksStream = self.getKeySignatures(
                searchContext=searchKeySignatureByContext)
            if ksStream:
                # assume we want the first found; in some cases it is possible
                # that this may not be true
                addAlteredPitches = ksStream[0].alteredPitches
        alteredPitches += addAlteredPitches
        # environLocal.printDebug(['processing makeAccidentals() with alteredPitches:',
        #   alteredPitches])

        # need to move through notes in order
        # recurse to capture notes in substreams: https://github.com/cuthbertLab/music21/issues/577
        noteIterator = returnObj.recurse().notesAndRests

        # environLocal.printDebug(['alteredPitches', alteredPitches])
        # environLocal.printDebug(['pitchPast', pitchPast])

        if tiePitchSet is None:
            tiePitchSet = set()

        for e in noteIterator:
            if isinstance(e, note.Note):
                if e.pitch.nameWithOctave in tiePitchSet:
                    lastNoteWasTied = True
                else:
                    lastNoteWasTied = False

                e.pitch.updateAccidentalDisplay(
                    pitchPast=pitchPast,
                    pitchPastMeasure=pitchPastMeasure,
                    alteredPitches=alteredPitches,
                    cautionaryPitchClass=cautionaryPitchClass,
                    cautionaryAll=cautionaryAll,
                    overrideStatus=overrideStatus,
                    cautionaryNotImmediateRepeat=cautionaryNotImmediateRepeat,
                    lastNoteWasTied=lastNoteWasTied)
                pitchPast.append(e.pitch)

                tiePitchSet.clear()
                if e.tie is not None and e.tie.type != 'stop':
                    tiePitchSet.add(e.pitch.nameWithOctave)

            elif isinstance(e, chord.Chord):
                # add all chord elements to past first
                # when reading a chord, this will apply an accidental
                # if pitches in the chord suggest an accidental
                seenPitchNames = set()

                for n in list(e):
                    p = n.pitch
                    if p.nameWithOctave in tiePitchSet:
                        lastNoteWasTied = True
                    else:
                        lastNoteWasTied = False

                    p.updateAccidentalDisplay(
                        pitchPast=pitchPast,
                        pitchPastMeasure=pitchPastMeasure,
                        alteredPitches=alteredPitches,
                        cautionaryPitchClass=cautionaryPitchClass,
                        cautionaryAll=cautionaryAll,
                        overrideStatus=overrideStatus,
                        cautionaryNotImmediateRepeat=cautionaryNotImmediateRepeat,
                        lastNoteWasTied=lastNoteWasTied)

                    if n.tie is not None and n.tie.type != 'stop':
                        seenPitchNames.add(p.nameWithOctave)

                tiePitchSet.clear()
                for pName in seenPitchNames:
                    tiePitchSet.add(pName)

                pitchPast += e.pitches
            else:
                tiePitchSet.clear()

        if not inPlace:
            return returnObj

    def haveAccidentalsBeenMade(self):
        # could be called: hasAccidentalDisplayStatusSet
        '''
        If Accidentals.displayStatus is None for all
        contained pitches, it as assumed that accidentals
        have not been set for display and/or makeAccidentals
        has not been run. If any Accidental has displayStatus
        other than None, this method returns True, regardless
        of if makeAccidentals has actually been run.
        '''
        return self.streamStatus.haveAccidentalsBeenMade()

    def makeNotation(self,
                     meterStream=None,
                     refStreamOrTimeRange=None,
                     inPlace=False,
                     bestClef=False,
                     **subroutineKeywords):
        '''
        This method calls a sequence of Stream methods on this Stream to prepare
        notation, including creating voices for overlapped regions, Measures
        if necessary, creating ties, beams, and accidentals.

        If `inPlace` is True, this is done in-place;
        if `inPlace` is False, this returns a modified deep copy.

        makeAccidentalsKeywords can be a dict specifying additional
        parameters to send to makeAccidentals


        >>> s = stream.Stream()
        >>> n = note.Note('g')
        >>> n.quarterLength = 1.5
        >>> s.repeatAppend(n, 10)
        >>> sMeasures = s.makeNotation()
        >>> len(sMeasures.getElementsByClass('Measure'))
        4
        >>> sMeasures.getElementsByClass('Measure')[-1].rightBarline.type
        'final'
        '''
        # determine what is the object to work on first
        if inPlace:
            returnStream = self
        else:
            returnStream = copy.deepcopy(self)

        # if 'finalBarline' in subroutineKeywords:
        #     lastBarlineType = subroutineKeywords['finalBarline']
        # else:
        #     lastBarlineType = 'final'
        returnStream.coreGatherMissingSpanners()  # get spanners needed but not here!

        # only use inPlace arg on first usage
        if not self.hasMeasures():
            # only try to make voices if no Measures are defined
            returnStream.makeVoices(inPlace=True, fillGaps=True)
            # if this is not inPlace, it will return a newStream; if
            # inPlace, this returns None
            # use inPlace=True, as already established above
            returnStream.makeMeasures(
                meterStream=meterStream,
                refStreamOrTimeRange=refStreamOrTimeRange,
                inPlace=True,
                bestClef=bestClef)

        measureStream = returnStream.getElementsByClass('Measure').stream()
        # environLocal.printDebug(['Stream.makeNotation(): post makeMeasures,
        #   length', len(returnStream)])

        # for now, calling makeAccidentals once per measures
        # pitches from last measure are passed
        # this needs to be called before makeTies
        # note that this functionality is also placed in Part
        if not measureStream.haveAccidentalsBeenMade():
            ksLast = None
            srkCopy = subroutineKeywords.copy()
            if 'meterStream' in srkCopy:
                del(srkCopy['meterStream'])
            if 'refStreamOrTimeRange' in srkCopy:
                del(srkCopy['refStreamOrTimeRange'])

            for i in range(len(measureStream)):
                m = measureStream[i]
                if m.keySignature is not None:
                    ksLast = m.keySignature

                if i > 0 and ksLast is None:
                    try:
                        previousNoteOrChord = measureStream[i - 1][-1]
                        tiePitchSet = makeNotation.getTiePitchSet(previousNoteOrChord)
                    except (IndexError, StreamException):
                        tiePitchSet = None
                    pitchPastMeasure = measureStream[i - 1].pitches
                else:
                    tiePitchSet = None
                    pitchPastMeasure = None

                m.makeAccidentals(
                    pitchPastMeasure=pitchPastMeasure,
                    useKeySignature=ksLast,
                    searchKeySignatureByContext=False,
                    tiePitchSet=tiePitchSet,
                    inPlace=True,
                    **srkCopy)
        # environLocal.printDebug(['makeNotation(): meterStream:', meterStream, meterStream[0]])
        try:
            measureStream.makeTies(meterStream, inPlace=True)
        except StreamException as e:
            strE = str(e)
            environLocal.warn('StreamException: makeTies: ' + strE)

        # measureStream.makeBeams(inPlace=True)
        if not measureStream.streamStatus.beams:
            try:
                measureStream.makeBeams(inPlace=True)
            except (StreamException, meter.MeterException):
                # this is a result of makeMeasures not getting everything
                # note to measure allocation right
                # environLocal.printDebug(['skipping makeBeams exception', StreamException])
                pass

        # note: this needs to be after makeBeams, as placing this before
        # makeBeams was causing the duration's tuplet to lose its type setting
        # check for tuplet brackets one measure at a time
        # this means that they will never extend beyond one measure
        for m in measureStream:
            if m.streamStatus.haveTupletBracketsBeenMade() is False:
                makeNotation.makeTupletBrackets(m, inPlace=True)

        if not measureStream:
            raise StreamException(
                f'no measures found in stream with {len(self)} elements')
        # environLocal.printDebug(['Stream.makeNotation(): created measures:',
        #   len(measureStream)])

        return returnStream

    def extendDuration(self, objName, *, inPlace=False):
        '''
        Given a Stream and an object class name, go through the Stream
        and find each instance of the desired object. The time between
        adjacent objects is then assigned to the duration of each object.
        The last duration of the last object is assigned to extend to the
        end of the Stream.

        If `inPlace` is True, this is done in-place; if `inPlace` is
        False, this returns a modified deep copy.

        >>> stream1 = stream.Stream()
        >>> n = note.Note(type='quarter')
        >>> n.duration.quarterLength
        1.0
        >>> stream1.repeatInsert(n, [0, 10, 20, 30, 40])

        >>> dyn = dynamics.Dynamic('ff')
        >>> stream1.insert(15, dyn)
        >>> stream1[-1].offset  # offset of last element
        40.0
        >>> stream1.duration.quarterLength  # total duration
        41.0
        >>> len(stream1)
        6

        >>> stream2 = stream1.flat.extendDuration(note.GeneralNote, inPlace=False)
        >>> len(stream2)
        6
        >>> stream2[0].duration.quarterLength
        10.0

        The Dynamic does not affect the second note:

        >>> stream2[1].offset
        10.0
        >>> stream2[1].duration.quarterLength
        10.0

        >>> stream2[-1].duration.quarterLength  # or extend to end of stream
        1.0
        >>> stream2.duration.quarterLength
        41.0
        >>> stream2[-1].offset
        40.0
        '''

        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        qLenTotal = returnObj.duration.quarterLength
        elements = list(returnObj.getElementsByClass(objName))

        # print(elements[-1], qLenTotal, elements[-1].duration)
        # print(_MOD, elements)
        for i in range(len(elements) - 1):
            # print(i, len(elements))
            span = returnObj.elementOffset(elements[i + 1]) - returnObj.elementOffset(elements[i])
            elements[i].duration.quarterLength = span

        # handle last element
        # print(elements[-1], qLenTotal, elements[-1].duration)
        if elements:
            elements[-1].duration.quarterLength = (qLenTotal
                                                   - returnObj.elementOffset(elements[-1]))
            # print(elements[-1], elements[-1].duration)
        if not inPlace:
            return returnObj

    def extendDurationAndGetBoundaries(self, objName, *, inPlace=False):
        '''
        DEPRECATED in v.6 -- to be removed in v.7

        Extend the Duration of elements specified by objName;
        then, collect a dictionary for every matched element of objName class,
        where the matched element is the value and the key is the (start, end) offset value.

        >>> from pprint import pprint as pp
        >>> s = stream.Stream()
        >>> s.insert(3, dynamics.Dynamic('mf'))
        >>> s.insert(7, dynamics.Dynamic('f'))
        >>> s.insert(12, dynamics.Dynamic('ff'))
        >>> pp(s.extendDurationAndGetBoundaries('Dynamic'))
        {(3.0, 7.0): <music21.dynamics.Dynamic mf>,
         (7.0, 12.0): <music21.dynamics.Dynamic f>,
         (12.0, 12.0): <music21.dynamics.Dynamic ff>}


        TODO: only allow inPlace=True or delete or something, can't return two different things
        '''
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self
        returnObj.extendDuration(objName, inPlace=True)
        # TODO: use iteration.
        elements = returnObj.getElementsByClass(objName)
        boundaries = {}
        if not elements:
            raise StreamException('no elements of this class defined in this Stream')

        for e in elements:
            start = returnObj.elementOffset(e)
            end = start + e.duration.quarterLength
            boundaries[(start, end)] = e
        return boundaries

    def stripTies(
        self,
        inPlace=False,
        matchByPitch=False,
        retainContainers=True
    ):
        # noinspection PyShadowingNames
        '''
        Find all notes that are tied; remove all tied notes,
        then make the first of the tied notes have a duration
        equal to that of all tied constituents. Lastly,
        remove the formerly-tied notes.

        This method can be used on Stream and Stream subclasses.
        When used on a stream containing Part-like substreams, as with many scores,
        :class:`~music21.stream.Part`, :class:`~music21.stream.Measure`, and other
        Stream subclasses are retained.

        `inPlace` controls whether the input stream is modified or whether a deep copy
        is made. `retainContainers=False` returns a flattened stream where Measures
        and other structures have been stripped.

        N.B.: `retainContainers=False` will have no effect on streams with part-like
        substreams, such as a :class:`~music21.stream.Score`.

        Changed in v.6 -- `retainContainers` defaults to True.
        Changed in v.6 -- `retainContainers=False` now only flattens the return
        stream, rather than also calling `.notesAndRests`.
        TODO: retainContainers TO BE DEPRECATED in v.7 (just call `.flat`)

        Presently, this only works if tied notes are sequential in the same voice; ultimately
        this will need to look at .to and .from attributes (if they exist)

        In some cases (under makeMeasures()) a continuation note will not have a
        Tie object with a stop attribute set. In that case, we need to look
        for sequential notes with matching pitches. The `matchByPitch` option can
        be used to use this technique.

        >>> a = stream.Stream()
        >>> n = note.Note()
        >>> n.quarterLength = 6
        >>> a.append(n)
        >>> m = a.makeMeasures()
        >>> m.makeTies(inPlace=True)
        >>> len(m.flat.notes)
        2

        >>> m = m.stripTies()
        >>> len(m.flat.notes)
        1
        '''
        # environLocal.printDebug(['calling stripTies'])
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
            returnObj.derivation.method = 'stripTies'
            returnObj.setDerivationMethod('stripTies', recurse=True)
        else:
            returnObj = self

        if returnObj.hasPartLikeStreams():
            # part-like does not necessarily mean that the next level down is a stream.Part
            # object or that this is a stream.Score object, so do not substitute
            # returnObj.parts for this...
            for p in returnObj.getElementsByClass('Stream'):
                # already copied if necessary; edit in place
                # when handling a score, retain containers should be true
                p.stripTies(inPlace=True, matchByPitch=matchByPitch,
                            retainContainers=True)
            return returnObj  # exit

        if returnObj.hasVoices():
            for v in returnObj.voices:
                # already copied if necessary; edit in place
                v.stripTies(inPlace=True, matchByPitch=matchByPitch,
                            retainContainers=retainContainers)
            return returnObj  # exit

        # need to just get .notesAndRests, as there may be other objects in the Measure
        # that come before the first Note, such as a SystemLayout object
        f = returnObj.flat
        notes = f.notesAndRests.stream()

        posConnected = []  # temporary storage for index of tied notes
        posDelete = []  # store deletions to be processed later

        def updateEndMatch(nInner):
            '''
            updateEndMatch based on nList, iLast, matchByPitch, etc.
            '''
            # ties tell us when they are ended
            # unless n is a chord, which only tells if SOME member has a tie
            # https://github.com/cuthbertLab/music21/issues/502
            if (hasattr(nInner, 'tie')
                    and 'Chord' not in nInner.classes
                    and nInner.tie is not None
                    and nInner.tie.type == 'stop'):
                return True
            # but capture case where all chord members have a stop tie
            elif (hasattr(nInner, 'tie')
                    and 'Chord' in nInner.classes
                    and None not in [inner_p.tie for inner_p in nInner.notes]
                    and {inner_p.tie.type for inner_p in nInner.notes} == {'stop'}):
                return True
            # if we cannot find a stop tie, see if last note was connected
            # and this and the last note are the same pitch; this assumes
            # that connected and same pitch value is tied; this is not
            # frequently the case
            elif not matchByPitch:
                return False

            # else...
            # find out if the last index is in position connected
            # if the pitches are the same for each note
            if (nLast is not None
                    and iLast in posConnected
                    and hasattr(nLast, 'pitch')
                    and hasattr(nInner, 'pitch')
                    and nLast.pitch == nInner.pitch):
                return True
            # looking for two chords of equal size
            elif (nLast is not None
                    and 'Note' not in nInner.classes
                    and iLast in posConnected
                    and hasattr(nLast, 'pitches')
                    and hasattr(nInner, 'pitches')):
                if len(nLast.pitches) != len(nInner.pitches):
                    return False

                allPitchesMatched = True
                for pitchIndex in range(len(nLast.pitches)):
                    if nLast.pitches[pitchIndex] != nInner.pitches[pitchIndex]:
                        allPitchesMatched = False
                        break
                return allPitchesMatched

            return False

        for i in range(len(notes)):
            endMatch = None  # can be True, False, or None
            n = notes[i]
            if i > 0:  # get i and n for the previous value
                iLast = i - 1
                nLast = notes[iLast]
            else:
                iLast = None
                nLast = None

            # see if we have a tie and it is started
            # a start typed tie may not be a true start tie
            if (hasattr(n, 'tie')
                    and n.tie is not None
                    and n.tie.type == 'start'):
                # find a true start, add to known connected positions
                if iLast is None or iLast not in posConnected:
                    posConnected = [i]  # reset list with start
                # find a continuation: the last note was a tie
                # start and this note is a tie start (this may happen)
                elif iLast in posConnected:
                    posConnected.append(i)
                # a connection has been started or continued, so no endMatch
                endMatch = False

            elif (hasattr(n, 'tie')
                    and n.tie is not None
                    and n.tie.type == 'continue'):
                # a continue always implies a connection
                posConnected.append(i)
                endMatch = False

            # establish end condition
            if endMatch is None:  # not yet set, not a start or continue
                endMatch = updateEndMatch(n)

            # process end condition
            if endMatch:
                posConnected.append(i)  # add this last position
                if len(posConnected) < 2:
                    # an open tie, not connected to anything
                    # should be an error; presently, just skipping
                    # raise StreamException('cannot consolidate ties when only one tie is present',
                    #    notes[posConnected[0]])
                    # environLocal.printDebug(
                    #   ['cannot consolidate ties when only one tie is present',
                    #     notes[posConnected[0]]])
                    posConnected = []
                    continue

                # get sum of durations for all notes
                # do not include first; will add to later; do not delete
                durSum = 0
                for q in posConnected[1:]:  # all but the first
                    durSum += notes[q].quarterLength
                    posDelete.append(q)  # store for deleting later
                # dur sum should always be greater than zero
                if durSum == 0:
                    raise StreamException('aggregated ties have a zero duration sum')
                # change the duration of the first note to be self + sum
                # of all others
                qLen = notes[posConnected[0]].quarterLength
                notes[posConnected[0]].quarterLength = qLen + durSum

                # set tie to None on first note
                notes[posConnected[0]].tie = None
                posConnected = []  # reset to empty

        # all results have been processed
        posDelete.reverse()  # start from highest and go down

        for i in posDelete:
            # environLocal.printDebug(['removing note', notes[i]])
            # get the obj ref
            nTarget = notes[i]
            # Recurse rather than depend on the containers being Measures
            # https://github.com/cuthbertLab/music21/issues/266
            returnObj.remove(nTarget, recurse=True)
        returnObj.coreElementsChanged()

        if retainContainers:
            return returnObj
        else:
            return returnObj.flat

    def extendTies(self, ignoreRests=False, pitchAttr='nameWithOctave'):
        '''
        Connect any adjacent pitch space values that are the
        same with a Tie. Adjacent pitches can be Chords, Notes, or Voices.

        If `ignoreRests` is True, rests that occur between events will not be
        considered in matching pitches.

        The `pitchAttr` determines the pitch attribute that is
        used for comparison. Any valid pitch attribute name can be used.
        '''
        def _getNextElements(srcStream, currentIndex, targetOffset):
            # need to find next event that start at the appropriate offset
            if currentIndex == len(srcStream) - 1:  # assume flat
                # environLocal.printDebug(['_getNextElements: nothing to process',
                # currentIndex, len(srcStream.notes) ])
                return []  # nothing left
            # iterate over all possible elements
            if ignoreRests:
                # need to find the offset of the first thing that is not rest
                for j in range(currentIndex + 1, len(srcStream._elements)):
                    el = srcStream._elements[j]
                    if 'NotRest' in el.classes:
                        # change target offset to this position
                        targetOffset = srcStream._elements[j].getOffsetBySite(
                            srcStream)
                        break
            match = srcStream.getElementsByOffset(targetOffset)
            # filter matched elements
            post = []
            for matchEl in match:
                if 'NotRest' in matchEl.classes:
                    post.append(matchEl)
            return post

        # take all flat elements; this will remove all voices; just use offset
        # position
        # do not need to worry about ._endElements
        srcFlat = self.flat.notes.stream()
        for i, e in enumerate(srcFlat):
            pSrc = []
            if 'Note' in e.classes:
                pSrc = [e]
            elif 'Chord' in e.classes:
                pSrc = list(e)  # get components
            else:
                continue
            # environLocal.printDebug(['examining', i, e])
            connections = _getNextElements(srcFlat, i,
                                           e.getOffsetBySite(srcFlat) + e.duration.quarterLength)
            # environLocal.printDebug(['possible connections', connections])

            for p, m in itertools.product(pSrc, connections):
                # for each p, see if there is match in the next position
                # for each element, look for a pitch to match
                mSrc = []
                if 'Note' in m.classes:
                    mSrc = [m]
                elif 'Chord' in m.classes:
                    mSrc = list(m)  # get components
                # final note comparison
                for q in mSrc:
                    if getattr(q.pitch, pitchAttr) == getattr(p.pitch, pitchAttr):
                        # create a tie from p to q
                        if p.tie is None:
                            p.tie = tie.Tie('start')
                        elif p.tie.type == 'stop':
                            p.tie.type = 'continue'
                        # if dst tie exists, assume it connects
                        q.tie = tie.Tie('stop')
                        break  # can only have one match from p to q

    # --------------------------------------------------------------------------

    def sort(self, force=False):
        '''
        Sort this Stream in place by offset, then priority, then
        standard class sort order (e.g., Clefs before KeySignatures before
        TimeSignatures).

        Note that Streams automatically sort themselves unless
        autoSort is set to False (as in the example below)

        If `force` is True, a sort will be attempted regardless of any other parameters.


        >>> n1 = note.Note('A')
        >>> n2 = note.Note('B')
        >>> s = stream.Stream()
        >>> s.autoSort = False
        >>> s.insert(100, n2)
        >>> s.insert(0, n1)  # now a has a lower offset by higher index
        >>> [n.name for n in s]
        ['B', 'A']
        >>> s[0].name
        'B'
        >>> s.sort()
        >>> s[0].name
        'A'
        >>> [n.name for n in s]
        ['A', 'B']
        '''
        # trust if this is sorted: do not sort again
        # experimental
        if (not self.isSorted and self._mutable) or force:
            self._elements.sort(key=lambda x: x.sortTuple(self))
            self._endElements.sort(key=lambda x: x.sortTuple(self))

            # as sorting changes order, elements have changed;
            # need to clear cache, but flat status is the same
            self.coreElementsChanged(
                updateIsFlat=False,
                clearIsSorted=False,
                keepIndex=False,  # this is False by default, but just to be sure for later
            )
            self.isSorted = True
            # environLocal.printDebug(['_elements', self._elements])

    @property
    def sorted(self):
        # noinspection PyShadowingNames
        '''
        Returns a new Stream where all the elements are sorted according to offset time, then
        priority, then classSortOrder (so that, for instance, a Clef at offset 0 appears before
        a Note at offset 0)

        if this Stream is not flat, then only the highest elements are sorted.  To sort all,
        run myStream.flat.sorted

        For instance, here is an unsorted Stream

        >>> s = stream.Stream()
        >>> s.autoSort = False  # if True, sorting is automatic
        >>> s.insert(1, note.Note('D'))
        >>> s.insert(0, note.Note('C'))
        >>> s.show('text')
        {1.0} <music21.note.Note D>
        {0.0} <music21.note.Note C>


        But a sorted version of the Stream puts the C first:

        >>> s.sorted.show('text')
        {0.0} <music21.note.Note C>
        {1.0} <music21.note.Note D>

        While the original stream remains unsorted:

        >>> s.show('text')
        {1.0} <music21.note.Note D>
        {0.0} <music21.note.Note C>


        OMIT_FROM_DOCS

        >>> s = stream.Stream()
        >>> s.autoSort = False
        >>> s.repeatInsert(note.Note('C#'), [0, 2, 4])
        >>> s.repeatInsert(note.Note('D-'), [1, 3, 5])
        >>> s.isSorted
        False
        >>> g = ''
        >>> for myElement in s:
        ...    g += '%s: %s; ' % (myElement.offset, myElement.name)
        >>> g
        '0.0: C#; 2.0: C#; 4.0: C#; 1.0: D-; 3.0: D-; 5.0: D-; '
        >>> y = s.sorted
        >>> y.isSorted
        True
        >>> g = ''
        >>> for myElement in y:
        ...    g += '%s: %s; ' % (myElement.offset, myElement.name)
        >>> g
        '0.0: C#; 1.0: D-; 2.0: C#; 3.0: D-; 4.0: C#; 5.0: D-; '
        >>> farRight = note.Note('E')
        >>> farRight.priority = 5
        >>> farRight.offset = 2.0
        >>> y.insert(farRight)
        >>> g = ''
        >>> for myElement in y:
        ...    g += '%s: %s; ' % (myElement.offset, myElement.name)
        >>> g
        '0.0: C#; 1.0: D-; 2.0: C#; 3.0: D-; 4.0: C#; 5.0: D-; 2.0: E; '
        >>> z = y.sorted
        >>> g = ''
        >>> for myElement in z:
        ...    g += '%s: %s; ' % (myElement.offset, myElement.name)
        >>> g
        '0.0: C#; 1.0: D-; 2.0: C#; 2.0: E; 3.0: D-; 4.0: C#; 5.0: D-; '
        >>> z[2].name, z[3].name
        ('C#', 'E')
        '''
        if 'sorted' not in self._cache or self._cache['sorted'] is None:
            shallowElements = copy.copy(self._elements)  # already a copy
            shallowEndElements = copy.copy(self._endElements)  # already a copy
            s = copy.copy(self)
            # assign directly to _elements, as we do not need to call
            # coreElementsChanged()
            s._elements = shallowElements
            s._endElements = shallowEndElements

            for e in shallowElements + shallowEndElements:
                s.setElementOffset(e, self.elementOffset(e), addElement=True)
                e.sites.add(s)
                # need to explicitly set activeSite
                s.coreSelfActiveSite(e)
            # now just sort this stream in place; this will update the
            # isSorted attribute and sort only if not already sorted
            s.sort()
            self._cache['sorted'] = s
        return self._cache['sorted']

    def _getFlatOrSemiFlat(self, retainContainers=False):
        '''
        A private method that implements the .flat or .semiFlat reduction types by using
        `retainContainers`=False to get .flat and retainContainers=True to get .semiFlat
        '''
        # environLocal.printDebug(['_getFlatOrSemiFlat(): self', self,
        #  'self.activeSite', self.activeSite])

        # this copy will have a shared locations object
        # note that copy.copy() in some cases seems to not cause secondary
        # problems that self.__class__() does
        if retainContainers:
            method = 'semiFlat'
        else:
            method = 'flat'

        sNew = copy.copy(self)
        if sNew.id != id(sNew):
            sOldId = sNew.id
            if common.isNum(sOldId) and sOldId > defaults.minIdNumberToConsiderMemoryLocation:
                sOldId = hex(sOldId)

            newId = str(sOldId) + '_' + method
            sNew.id = newId

        sNew._derivation = derivation.Derivation(sNew)
        sNew._derivation.origin = self
        sNew.derivation.method = method
        # storing .elements in here necessitates
        # create a new, independent cache instance in the flat representation
        sNew._cache = {}
        sNew._offsetDict = {}
        sNew._elements = []
        sNew._endElements = []
        sNew.coreElementsChanged()

        ri = iterator.RecursiveIterator(self,
                                        restoreActiveSites=False,
                                        includeSelf=False,
                                        ignoreSorting=True,
                                        )
        for e in ri:
            if e.isStream and not retainContainers:
                continue
            sNew.coreInsert(ri.currentHierarchyOffset(),
                             e,
                             setActiveSite=False)

        sNew.isFlat = True
        if self.autoSort is True:
            sNew.sort()  # sort it immediately so that cache is not invalidated
        else:
            sNew.coreElementsChanged()
        # here, we store the source stream from which this stream was derived
        return sNew

    @property
    def flat(self):
        '''
        A very important read-only property that returns a new Stream
        that has all sub-containers "flattened" within it,
        that is, it returns a new Stream where no elements nest within
        other elements.

        Here is a simple example of the usefulness of .flat.  We
        will create a Score with two Parts in it, each with two Notes:

        >>> sc = stream.Score()
        >>> p1 = stream.Part()
        >>> p1.id = 'part1'
        >>> n1 = note.Note('C4')
        >>> n2 = note.Note('D4')
        >>> p1.append(n1)
        >>> p1.append(n2)

        >>> p2 = stream.Part()
        >>> p2.id = 'part2'
        >>> n3 = note.Note('E4')
        >>> n4 = note.Note('F4')
        >>> p2.append(n3)
        >>> p2.append(n4)

        >>> sc.insert(0, p1)
        >>> sc.insert(0, p2)

        When we look at sc, we will see only the two parts:

        >>> sc.elements
        (<music21.stream.Part part1>, <music21.stream.Part part2>)

        We can get at the notes by using the indices of the
        stream to get the parts and then looking at the .elements
        there:

        >>> sc[0].elements
        (<music21.note.Note C>, <music21.note.Note D>)

        >>> sc.getElementById('part2').elements
        (<music21.note.Note E>, <music21.note.Note F>)

        ...but if we want to get all the notes, storing their
        offsets related to the beginning of the containing stream,
        one way
        is via calling .flat on sc and looking at the elements
        there:

        >>> sc.flat.elements
        (<music21.note.Note C>, <music21.note.Note E>,
         <music21.note.Note D>, <music21.note.Note F>)

        Flattening a stream is a great way to get at all the notes in
        a larger piece.  For instance if we load a four-part
        Bach chorale into music21 from the integrated corpus, it
        will appear at first that there are no notes in the piece:

        >>> bwv66 = corpus.parse('bach/bwv66.6')
        >>> len(bwv66.notes)
        0

        This is because all the notes in the piece lie within :class:`music21.stream.Measure`
        objects and those measures lie within :class:`music21.stream.Part`
        objects.  It'd be a pain to navigate all the way through all those
        objects just to count notes.  Fortunately we can get a Stream of
        all the notes in the piece with .flat.notes and then use the
        length of that Stream to count notes:

        >>> bwv66flat = bwv66.flat
        >>> len(bwv66flat.notes)
        165

        When, as is commonly the case, we want to find all of the notes,
        but do not care to have offsets related to the origin of the stream,
        then `.recurse()` is generally a more efficiant way of working:

        >>> len(bwv66.recurse().notes)
        165

        If you look back to our simple example of four notes above,
        you can see that the E (the first note in part2) comes before the D
        (the second note of part1).  This is because the flat stream
        is automatically sorted like all streams are by default.  The
        next example shows how to change this behavior.

        >>> s = stream.Stream()
        >>> s.autoSort = False
        >>> s.repeatInsert(note.Note('C#'), [0, 2, 4])
        >>> s.repeatInsert(note.Note('D-'), [1, 3, 5])
        >>> s.isSorted
        False

        >>> g = ''
        >>> for myElement in s:
        ...    g += '%s: %s; ' % (myElement.offset, myElement.name)
        ...

        >>> g
        '0.0: C#; 2.0: C#; 4.0: C#; 1.0: D-; 3.0: D-; 5.0: D-; '

        >>> y = s.sorted
        >>> y.isSorted
        True

        >>> g = ''
        >>> for myElement in y:
        ...    g += '%s: %s; ' % (myElement.offset, myElement.name)
        ...

        >>> g
        '0.0: C#; 1.0: D-; 2.0: C#; 3.0: D-; 4.0: C#; 5.0: D-; '

        >>> q = stream.Stream()
        >>> for i in range(5):
        ...     p = stream.Stream()
        ...     p.repeatInsert(base.Music21Object(), [0, 1, 2, 3, 4])
        ...     q.insert(i * 10, p)
        ...

        >>> len(q)
        5


        >>> qf = q.flat
        >>> len(qf)
        25
        >>> qf[24].offset
        44.0

        OMIT_FROM_DOCS

        >>> r = stream.Stream()
        >>> for j in range(5):
        ...   q = stream.Stream()
        ...   for i in range(5):
        ...      p = stream.Stream()
        ...      p.repeatInsert(base.Music21Object(), [0, 1, 2, 3, 4])
        ...      q.insert(i * 10, p)
        ...   r.insert(j * 100, q)

        >>> len(r)
        5

        >>> len(r.flat)
        125

        >>> r.flat[124].offset
        444.0
        '''
        if 'flat' not in self._cache or self._cache['flat'] is None:
            self._cache['flat'] = self._getFlatOrSemiFlat(
                retainContainers=False)
        return self._cache['flat']

        # non cached approach
        # return self._getFlatOrSemiFlat(retainContainers=False)

    @property
    def semiFlat(self):
        '''
        Returns a flat-like Stream representation, but where
        Stream sub-classed containers, such as Measure or Part,
        are retained in the output Stream, but positioned at their
        relative offset.


        >>> s = stream.Stream()

        >>> p1 = stream.Part()
        >>> p1.id = 'part1'
        >>> n1 = note.Note('C5')
        >>> p1.append(n1)

        >>> p2 = stream.Part()
        >>> p2.id = 'part2'
        >>> n2 = note.Note('D5')
        >>> p2.append(n2)

        >>> s.insert(0, p1)
        >>> s.insert(0, p2)
        >>> sf = s.semiFlat
        >>> sf.elements
        (<music21.stream.Part part1>,
         <music21.stream.Part part2>,
         <music21.note.Note C>,
         <music21.note.Note D>)
        >>> sf[0]
        <music21.stream.Part part1>
        >>> sf[2]
        <music21.note.Note C>
        >>> sf[0][0]
        <music21.note.Note C>
        '''
        if 'semiFlat' not in self._cache or self._cache['semiFlat'] is None:
            # environLocal.printDebug(['using cached semiFlat', self])
            self._cache['semiFlat'] = self._getFlatOrSemiFlat(
                retainContainers=True)
        return self._cache['semiFlat']

    def recurse(self,
                *,
                streamsOnly=False,
                restoreActiveSites=True,
                classFilter=(),
                skipSelf=True,
                includeSelf=None):
        '''
        `.recurse()` is a fundamental method of music21 for getting into
        elements contained in a Score, Part, or Measure, where elements such as
        notes are contained in sub-Stream elements.

        Returns an iterator that iterates over a list of Music21Objects
        contained in the Stream, starting with self's elements (unless
        skipSelf=False in which case, it starts with the element itself),
        and whenever finding a Stream subclass in self,
        that Stream subclass's elements.

        Here's an example. Let's create a simple score.

        >>> s = stream.Score(id='mainScore')
        >>> p0 = stream.Part(id='part0')
        >>> p1 = stream.Part(id='part1')

        >>> m01 = stream.Measure(number=1)
        >>> m01.append(note.Note('C', type='whole'))
        >>> m02 = stream.Measure(number=2)
        >>> m02.append(note.Note('D', type='whole'))
        >>> m11 = stream.Measure(number=1)
        >>> m11.append(note.Note('E', type='whole'))
        >>> m12 = stream.Measure(number=2)
        >>> m12.append(note.Note('F', type='whole'))

        >>> p0.append([m01, m02])
        >>> p1.append([m11, m12])

        >>> s.insert(0, p0)
        >>> s.insert(0, p1)
        >>> s.show('text')
        {0.0} <music21.stream.Part part0>
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.note.Note C>
            {4.0} <music21.stream.Measure 2 offset=4.0>
                {0.0} <music21.note.Note D>
        {0.0} <music21.stream.Part part1>
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.note.Note E>
            {4.0} <music21.stream.Measure 2 offset=4.0>
                {0.0} <music21.note.Note F>

        Now we could assign the `.recurse()` method to something,
        but that won't have much effect:

        >>> sRecurse = s.recurse()
        >>> sRecurse
        <music21.stream.iterator.RecursiveIterator for Score:mainScore @:0>

        So, that's not how we use `.recurse()`.  Instead use it in a `for` loop:

        >>> for el in s.recurse():
        ...     tup = (el, el.offset, el.activeSite)
        ...     print(tup)
        (<music21.stream.Part part0>, 0.0, <music21.stream.Score mainScore>)
        (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part0>)
        (<music21.note.Note C>, 0.0, <music21.stream.Measure 1 offset=0.0>)
        (<music21.stream.Measure 2 offset=4.0>, 4.0, <music21.stream.Part part0>)
        (<music21.note.Note D>, 0.0, <music21.stream.Measure 2 offset=4.0>)
        (<music21.stream.Part part1>, 0.0, <music21.stream.Score mainScore>)
        (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part1>)
        (<music21.note.Note E>, 0.0, <music21.stream.Measure 1 offset=0.0>)
        (<music21.stream.Measure 2 offset=4.0>, 4.0, <music21.stream.Part part1>)
        (<music21.note.Note F>, 0.0, <music21.stream.Measure 2 offset=4.0>)

        If we specify `includeSelf=True` then the original stream is also iterated:

        >>> for el in s.recurse(includeSelf=True):
        ...     tup = (el, el.offset, el.activeSite)
        ...     print(tup)
        (<music21.stream.Score mainScore>, 0.0, None)
        (<music21.stream.Part part0>, 0.0, <music21.stream.Score mainScore>)
        (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part0>)
        (<music21.note.Note C>, 0.0, <music21.stream.Measure 1 offset=0.0>)
        ...

        Notice that like calling `.show('text')`, the offsets are relative to their containers.

        Compare the difference between putting `classFilter='Note'` and `.flat.notes`:

        >>> for el in s.recurse(classFilter='Note'):
        ...     tup = (el, el.offset, el.activeSite)
        ...     print(tup)
        (<music21.note.Note C>, 0.0, <music21.stream.Measure 1 offset=0.0>)
        (<music21.note.Note D>, 0.0, <music21.stream.Measure 2 offset=4.0>)
        (<music21.note.Note E>, 0.0, <music21.stream.Measure 1 offset=0.0>)
        (<music21.note.Note F>, 0.0, <music21.stream.Measure 2 offset=4.0>)

        >>> for el in s.flat.notes:
        ...     tup = (el, el.offset, el.activeSite)
        ...     print(tup)
        (<music21.note.Note C>, 0.0, <music21.stream.Score mainScore_flat>)
        (<music21.note.Note E>, 0.0, <music21.stream.Score mainScore_flat>)
        (<music21.note.Note D>, 4.0, <music21.stream.Score mainScore_flat>)
        (<music21.note.Note F>, 4.0, <music21.stream.Score mainScore_flat>)

        If you don't need correct offsets or activeSites, set `restoreActiveSites` to `False`.
        Then the last offset/activeSite will be used.  It's a bit of a speedup, but leads to some
        bad code, so use it only in highly optimized situations.

        We'll also test using multiple classes here... the Stream given is the same as the
        s.flat.notes stream.

        >>> for el in s.recurse(classFilter=('Note', 'Rest'), restoreActiveSites=False):
        ...     tup = (el, el.offset, el.activeSite)
        ...     print(tup)
        (<music21.note.Note C>, 0.0, <music21.stream.Score mainScore_flat>)
        (<music21.note.Note D>, 4.0, <music21.stream.Score mainScore_flat>)
        (<music21.note.Note E>, 0.0, <music21.stream.Score mainScore_flat>)
        (<music21.note.Note F>, 4.0, <music21.stream.Score mainScore_flat>)

        So, this is pretty unreliable so don't use it unless the tiny speedup is worth it.

        The other two attributes are pretty self-explanatory: `streamsOnly` will put only Streams
        in, while `includeSelf` will add the initial stream from recursion.  If the inclusion or
        exclusion of `self` is important to you, put it in explicitly.

        >>> for el in s.recurse(includeSelf=False, streamsOnly=True):
        ...     tup = (el, el.offset, el.activeSite)
        ...     print(tup)
        (<music21.stream.Part part0>, 0.0, <music21.stream.Score mainScore>)
        (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part0>)
        (<music21.stream.Measure 2 offset=4.0>, 4.0, <music21.stream.Part part0>)
        (<music21.stream.Part part1>, 0.0, <music21.stream.Score mainScore>)
        (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part1>)
        (<music21.stream.Measure 2 offset=4.0>, 4.0, <music21.stream.Part part1>)

        .. warning::

            Remember that like all iterators, it is dangerous to alter
            the components of the Stream being iterated over during iteration.
            if you need to edit while recursing, list(s.recurse()) is safer.

        Changed in v5.5 -- `skipSelf` is True as promised.  All attributes are keyword only.
        `includeSelf` is added and now preferred over `skipSelf`.  `skipSelf` will be removed
        in or after 2022 in v. 7 or v. 8.
        '''
        if includeSelf is None:
            includeSelf = not skipSelf
        ri = iterator.RecursiveIterator(self,
                                        streamsOnly=streamsOnly,
                                        restoreActiveSites=restoreActiveSites,
                                        includeSelf=includeSelf
                                        )
        if classFilter != ():
            ri = ri.addFilter(filters.ClassFilter(classFilter))
        return ri

    def containerInHierarchy(self, el, *, setActiveSite=True) -> Optional['music21.stream.Stream']:
        '''
        Returns the container in a hierarchy that this element belongs to.

        For instance, assume a Note (n) is in a Measure (m1) which is in a Part, in a Score (s1),
        and the Note is also in another hierarchy (say, a chordified version of a Score, s2).
        if s1.containerInHierarchy(n) is called, it will return m1,
        the Measure that contains the note.

        Unless `setActiveSite` is False, n's activeStie will be set to m1, and
        its `.offset` will be the offset in `m1`.

        >>> s1 = stream.Score(id='s1')
        >>> p1 = stream.Part()
        >>> m1 = stream.Measure(id='m1')
        >>> n = note.Note('D')
        >>> m1.append(n)
        >>> p1.append(m1)
        >>> s1.insert(0, p1)
        >>> s2 = stream.Stream(id='s2')
        >>> s2.append(n)
        >>> n.activeSite.id
        's2'
        >>> s1.containerInHierarchy(n).id
        'm1'
        >>> n.activeSite.id
        'm1'
        >>> n.activeSite = s2
        >>> s1.containerInHierarchy(n, setActiveSite=False).id
        'm1'
        >>> n.activeSite.id
        's2'

        If the element cannot be found in the hierarchy then None is returned.

        >>> s3 = stream.Stream()
        >>> s3.containerInHierarchy(n) is None
        True
        '''
        elSites = el.sites
        for s in self.recurse(includeSelf=True, streamsOnly=True, restoreActiveSites=False):
            if s in elSites:
                if setActiveSite:
                    s.coreSelfActiveSite(el)
                return s
        return None

    def makeImmutable(self):
        '''
        Clean this Stream: for self and all elements, purge all dead locations
        and remove all non-contained sites. Further, restore all active sites.
        '''
        if self._mutable is not False:
            self.sort()  # must sort before making immutable
        for e in self.recurse(streamsOnly=True, includeSelf=False):
            # e.purgeLocations(rescanIsDead=True)
            # NOTE: calling this method was having the side effect of removing
            # sites from locations when a Note was both in a Stream and in
            # an Interval
            if e.isStream:
                e.sort()  # sort before making immutable
                e._mutable = False
        self._mutable = False

    def makeMutable(self, recurse=True):
        self._mutable = True
        if recurse:
            for e in self.recurse(streamsOnly=True):
                # do not recurse, as will get all Stream
                e.makeMutable(recurse=False)
        self.coreElementsChanged()

    # --------------------------------------------------------------------------
    # duration and offset methods and properties

    @property
    def highestOffset(self):
        '''
        Get start time of element with the highest offset in the Stream.
        Note the difference between this property and highestTime
        which gets the end time of the highestOffset

        >>> stream1 = stream.Stream()
        >>> for offset in [0, 4, 8]:
        ...     n = note.Note('G#', type='whole')
        ...     stream1.insert(offset, n)
        >>> stream1.highestOffset
        8.0
        >>> stream1.highestTime
        12.0
        '''
        # TODO: Perfect timespans candidate
        if 'HighestOffset' in self._cache and self._cache['HighestOffset'] is not None:
            pass  # return cache unaltered
        elif not self._elements:
            self._cache['HighestOffset'] = 0.0
        elif self.isSorted is True:
            eLast = self._elements[-1]
            self._cache['HighestOffset'] = self.elementOffset(eLast)
        else:  # iterate through all elements
            highestOffsetSoFar = None
            for e in self._elements:
                candidateOffset = self.elementOffset(e)
                if highestOffsetSoFar is None or candidateOffset > highestOffsetSoFar:
                    highestOffsetSoFar = candidateOffset

            if highestOffsetSoFar is not None:
                self._cache['HighestOffset'] = float(highestOffsetSoFar)
            else:
                self._cache['HighestOffset'] = None
        return self._cache['HighestOffset']

    def _setHighestTime(self, value):
        '''For internal use only.
        '''
        self._cache['HighestTime'] = value

    @property
    def highestTime(self):
        '''
        Returns the maximum of all Element offsets plus their Duration
        in quarter lengths. This value usually represents the last
        "release" in the Stream.

        Stream.duration is usually equal to the highestTime
        expressed as a Duration object, but it can be set separately
        for advanced operations.

        Example: Insert a dotted half note at position 0 and see where
        it cuts off:

        >>> p1 = stream.Stream()
        >>> p1.highestTime
        0.0

        >>> n = note.Note('A-')
        >>> n.quarterLength = 3
        >>> p1.insert(0, n)
        >>> p1.highestTime
        3.0

        Now insert in the same stream, the dotted half note
        at positions 1, 2, 3, 4 and see when the final note cuts off:

        >>> p1.repeatInsert(n, [1, 2, 3, 4])
        >>> p1.highestTime
        7.0

        Another example.

        >>> n = note.Note('C#')
        >>> n.quarterLength = 3

        >>> q = stream.Stream()
        >>> for i in [20, 0, 10, 30, 40]:
        ...    p = stream.Stream()
        ...    p.repeatInsert(n, [0, 1, 2, 3, 4])
        ...    q.insert(i, p)  # insert out of order
        >>> len(q.flat)
        25
        >>> q.highestTime  # this works b/c the component Stream has an duration
        47.0
        >>> r = q.flat

        Changed in v6.5 -- highestTime can return a Fraction.


        OMIT_FROM_DOCS

        Make sure that the cache really is empty
        >>> 'HighestTime' in r._cache
        False
        >>> r.highestTime  # 44 + 3
        47.0

        Can highestTime be a fraction?

        >>> n = note.Note('D')
        >>> n.duration.quarterLength = 1/3
        >>> n.duration.quarterLength
        Fraction(1, 3)

        >>> s = stream.Stream()
        >>> s.append(note.Note('C'))
        >>> s.append(n)
        >>> s.highestTime
        Fraction(4, 3)
        '''
        # TODO(msc) -- why is cache 'HighestTime' and not 'highestTime'?
        # environLocal.printDebug(['_getHighestTime', 'isSorted', self.isSorted, self])

        # remove cache -- durations might change...
        if 'HighestTime' in self._cache and self._cache['HighestTime'] is not None:
            pass  # return cache unaltered
        elif not self._elements:
            # _endElements does not matter here, since ql > 0 on endElements not allowed.
            self._cache['HighestTime'] = 0.0
            return 0.0
        else:
            highestTimeSoFar = 0.0
            # TODO: optimize for a faster way of doing this.
            #     but cannot simply look at the last element even if isSorted
            #     because what if the penultimate
            #     element, with a
            #     lower offset has a longer duration than the last?
            #     Take the case where a whole note appears a 0.0, but a
            #     textExpression (ql=0) at 0.25 --
            #     isSorted would be true, but highestTime should be 4.0 not 0.25
            for e in self._elements:
                candidateOffset = (self.elementOffset(e)
                                   + e.duration.quarterLength)
                if candidateOffset > highestTimeSoFar:
                    highestTimeSoFar = candidateOffset
            self._cache['HighestTime'] = opFrac(highestTimeSoFar)
        return self._cache['HighestTime']

    @property
    def lowestOffset(self):
        '''
        Get the start time of the Element with the lowest offset in the Stream.

        >>> stream1 = stream.Stream()
        >>> for x in range(3, 5):
        ...     n = note.Note('G#')
        ...     stream1.insert(x, n)
        ...
        >>> stream1.lowestOffset
        3.0


        If the Stream is empty, then the lowest offset is 0.0:


        >>> stream2 = stream.Stream()
        >>> stream2.lowestOffset
        0.0



        >>> p = stream.Stream()
        >>> p.repeatInsert(note.Note('D5'), [0, 1, 2, 3, 4])
        >>> q = stream.Stream()
        >>> q.repeatInsert(p, list(range(0, 50, 10)))
        >>> len(q.flat)
        25
        >>> q.lowestOffset
        0.0
        >>> r = stream.Stream()
        >>> r.repeatInsert(q, list(range(97, 500, 100)))
        >>> len(r.flat)
        125
        >>> r.lowestOffset
        97.0
        '''
        if 'lowestOffset' in self._cache and self._cache['LowestOffset'] is not None:
            pass  # return cache unaltered
        elif not self._elements:
            self._cache['LowestOffset'] = 0.0
        elif self.isSorted is True:
            eFirst = self._elements[0]
            self._cache['LowestOffset'] = self.elementOffset(eFirst)
        else:  # iterate through all elements
            minOffsetSoFar = None
            for e in self._elements:
                candidateOffset = self.elementOffset(e)
                if minOffsetSoFar is None or candidateOffset < minOffsetSoFar:
                    minOffsetSoFar = candidateOffset
            self._cache['LowestOffset'] = minOffsetSoFar

            # environLocal.printDebug(['_getLowestOffset: iterated elements', min])

        return self._cache['LowestOffset']

    def _getDuration(self):
        '''
        Gets the duration of the whole stream (generally the highestTime, but can be set
        independently).
        '''
        if self._unlinkedDuration is not None:
            return self._unlinkedDuration
        elif 'Duration' in self._cache and self._cache['Duration'] is not None:
            # environLocal.printDebug(['returning cached duration'])
            return self._cache['Duration']
        else:
            # environLocal.printDebug(['creating new duration based on highest time'])
            self._cache['Duration'] = duration.Duration()
            self._cache['Duration'].quarterLength = self.highestTime
            return self._cache['Duration']

    def _setDuration(self, durationObj):
        '''
        Set the total duration of the Stream independently of the highestTime
        of the stream.  Useful to define the scope of the stream as independent
        of its constituted elements.

        If set to None, then the default behavior of computing automatically from
        highestTime is reestablished.
        '''
        if isinstance(durationObj, duration.Duration):
            self._unlinkedDuration = durationObj
        elif durationObj is None:
            self._unlinkedDuration = None
        else:  # need to permit Duration object assignment here
            raise StreamException(f'this must be a Duration object, not {durationObj}')

    duration = property(_getDuration, _setDuration, doc='''
        Returns the total duration of the Stream, from the beginning of the
        stream until the end of the final element.
        May be set independently by supplying a Duration object.

        >>> a = stream.Stream()
        >>> q = note.Note(type='quarter')
        >>> a.repeatInsert(q, [0, 1, 2, 3])
        >>> a.highestOffset
        3.0
        >>> a.highestTime
        4.0
        >>> a.duration
        <music21.duration.Duration 4.0>
        >>> a.duration.quarterLength
        4.0


        Advanced usage: override the duration from what is set:

        >>> newDuration = duration.Duration('half')
        >>> newDuration.quarterLength
        2.0

        >>> a.duration = newDuration
        >>> a.duration.quarterLength
        2.0

        Restore normal behavior by setting duration to None:

        >>> a.duration = None
        >>> a.duration
        <music21.duration.Duration 4.0>

        Note that the highestTime for the stream is the same
        whether duration is overridden or not:

        >>> a.highestTime
        4.0
        ''')

    def _setSeconds(self, value):
        pass

    def _getSeconds(self):
        getTempoFromContext = False
        # need to find all tempo indications and the number of quarter lengths
        # under each
        tiStream = self.getElementsByClass('TempoIndication')
        offsetMetronomeMarkPairs = []

        if not tiStream:
            getTempoFromContext = True
        else:
            for ti in tiStream:
                o = self.elementOffset(ti)
                # get the desired metronome mark from any of ti classes
                mm = ti.getSoundingMetronomeMark()
                offsetMetronomeMarkPairs.append([o, mm])

        for i, (o, mm) in enumerate(offsetMetronomeMarkPairs):
            if i == 0 and o > 0.0:
                getTempoFromContext = True
            break  # just need first

        if getTempoFromContext:
            ti = self.getContextByClass('TempoIndication')
            if ti is None:
                if self.highestTime != 0.0:
                    return float('nan')
                else:
                    return 0.0
            # insert at zero offset position, even though coming from
            # outside this stream
            mm = ti.getSoundingMetronomeMark()
            offsetMetronomeMarkPairs = [[0.0, mm]] + offsetMetronomeMarkPairs
        sec = 0.0
        for i, (o, mm) in enumerate(offsetMetronomeMarkPairs):
            # handle only one mm right away
            if len(offsetMetronomeMarkPairs) == 1:
                sec += mm.durationToSeconds(self.highestTime)
                break
            oStart, mmStart = o, mm
            # if not the last ti, get the next by index to get the offset
            if i < len(offsetMetronomeMarkPairs) - 1:
                # cases of two or more remain
                oEnd, unused_mmEnd = offsetMetronomeMarkPairs[i + 1]
            else:  # at the last
                oEnd = self.highestTime
            sec += mmStart.durationToSeconds(oEnd - oStart)

        return sec

    seconds = property(_getSeconds, _setSeconds, doc='''
        Get or set the duration of this Stream in seconds, assuming that
        this object contains a :class:`~music21.tempo.MetronomeMark` or
        :class:`~music21.tempo.MetricModulation`.

        >>> s = corpus.parse('bwv66.6')  # piece without a tempo
        >>> sFlat = s.flat
        >>> t = tempo.MetronomeMark('adagio')
        >>> sFlat.insert(0, t)
        >>> sFlat.seconds
        38.57142857...
        >>> tFast = tempo.MetronomeMark('allegro')
        >>> sFlat.replace(t, tFast)
        >>> sFlat.seconds
        16.363...

        Setting seconds on streams is not supported.  Ideally it would instead
        scale all elements to fit, but this is a long way off.

        If a stream does not have a tempo-indication in it then the property
        returns 0.0 if an empty Stream (or self.highestTime is 0.0) or 'nan'
        if there are non-zero duration objects in the stream:

        >>> s = stream.Stream()
        >>> s.seconds
        0.0
        >>> s.insert(0, clef.TrebleClef())
        >>> s.seconds
        0.0
        >>> s.append(note.Note(type='half'))
        >>> s.seconds
        nan
        >>> import math
        >>> math.isnan(s.seconds)
        True

        Changed in v6.3 -- return nan rather than raising an exception.  Do not
        attempt to change seconds on a stream, as it did not do what you would expect.
        ''')

    def metronomeMarkBoundaries(self, srcObj=None):
        '''
        Return a list of offset start, offset end,
        MetronomeMark triples for all TempoIndication objects found
        in this Stream or a Stream provided by srcObj.

        If no MetronomeMarks are found, or an initial region does not
        have a MetronomeMark, a mark of quarter equal to 120 is provided as default.

        Note that if other TempoIndication objects are defined,
        they will be converted to MetronomeMarks and returned here


        >>> s = stream.Stream()
        >>> s.repeatAppend(note.Note(), 8)
        >>> s.insert([6, tempo.MetronomeMark(number=240)])
        >>> s.metronomeMarkBoundaries()
        [(0.0, 6.0, <music21.tempo.MetronomeMark animato Quarter=120>),
         (6.0, 8.0, <music21.tempo.MetronomeMark Quarter=240>)]
        '''
        if srcObj is None:
            srcObj = self
        # get all tempo indications from the flat Stream;
        # using flat here may not always be desirable
        # may want to do a recursive upward search as well
        srcFlat = srcObj.flat
        tiStream = srcFlat.getElementsByClass('TempoIndication')
        mmBoundaries = []  # a  list of (start, end, mm)

        # not sure if this should be taken from the flat representation
        highestTime = srcFlat.highestTime
        lowestOffset = srcFlat.lowestOffset

        # if no tempo
        if not tiStream:
            mmDefault = tempo.MetronomeMark(number=120)  # a default
            mmBoundaries.append((lowestOffset, highestTime, mmDefault))
        # if just one tempo
        elif len(tiStream) == 1:
            # get offset from flat source
            o = tiStream[0].getOffsetBySite(srcFlat)
            mm = tiStream[0].getSoundingMetronomeMark()
            if o > lowestOffset:
                mmDefault = tempo.MetronomeMark(number=120)  # a default
                mmBoundaries.append((lowestOffset, o, mmDefault))
                mmBoundaries.append((o, highestTime, mm))
            else:
                mmBoundaries.append((lowestOffset, highestTime, mm))
        # one or more tempi
        else:
            offsetPairs = []
            for ti in tiStream:
                o = ti.getOffsetBySite(srcFlat)
                offsetPairs.append([o, ti.getSoundingMetronomeMark()])
            # fill boundaries
            # if lowest region not defined, supply as default
            if offsetPairs[0][0] > lowestOffset:
                mmDefault = tempo.MetronomeMark(number=120)  # a default
                mmBoundaries.append((lowestOffset, offsetPairs[0][0],
                                     mmDefault))
                mmBoundaries.append((offsetPairs[0][0], offsetPairs[1][0],
                                     offsetPairs[0][1]))
            else:  # just add the first range
                mmBoundaries.append((offsetPairs[0][0], offsetPairs[1][0],
                                     offsetPairs[0][1]))
            # add any remaining ranges, starting from the second; if last,
            # use the highest time as the boundary
            for i, (o, mm) in enumerate(offsetPairs):
                if i == 0:
                    continue  # already added first
                elif i == len(offsetPairs) - 1:  # last index
                    mmBoundaries.append((offsetPairs[i][0], highestTime,
                                         offsetPairs[i][1]))
                else:  # add with next boundary
                    mmBoundaries.append((offsetPairs[i][0], offsetPairs[i + 1][0],
                                         offsetPairs[i][1]))

        # environLocal.printDebug(['self.metronomeMarkBoundaries()',
        # 'got mmBoundaries:', mmBoundaries])
        return mmBoundaries

    def _accumulatedSeconds(self, mmBoundaries, oStart, oEnd):
        '''
        Given MetronomeMark boundaries, for any pair of offsets,
        determine the realized duration in seconds.
        '''
        # assume tt mmBoundaries are in order
        totalSeconds = 0.0
        activeStart = oStart
        activeEnd = None
        for s, e, mm in mmBoundaries:
            if s <= activeStart < e:
                # find time in this region
                if oEnd < e:  # if end within this region
                    activeEnd = oEnd
                else:  # if end after this region
                    activeEnd = e
                # environLocal.printDebug(['activeStart', activeStart,
                # 'activeEnd', activeEnd, 's, e, mm', s, e, mm])
                totalSeconds += mm.durationToSeconds(activeEnd - activeStart)
            else:
                continue
            if activeEnd == oEnd:
                break
            else:  # continue on
                activeStart = activeEnd
        return totalSeconds

    def _getSecondsMap(self, srcObj=None):
        '''
        Return a list of dictionaries for all elements in this Stream,
        where each dictionary defines the real-time characteristics of
        the stored events. This will attempt to find
        all :class:`~music21.tempo.TempoIndication` subclasses and use these
        values to realize tempi. If no initial tempo is found,
        a tempo of 120 BPM will be provided.
        '''
        if srcObj is None:
            srcObj = self
        mmBoundaries = self.metronomeMarkBoundaries(srcObj=srcObj)

        # not sure if this should be taken from the flat representation
        lowestOffset = srcObj.lowestOffset

        secondsMap = []  # list of start, start+dur, element
        if srcObj.hasVoices():
            groups = []
            for i, v in enumerate(srcObj.voices):
                groups.append((v.flat, i))
        else:  # create a single collection
            groups = [(srcObj, None)]

        # get accumulated time over many possible tempo changes for
        # start/end offset
        for group, voiceIndex in groups:
            for e in group:
                if isinstance(e, bar.Barline):
                    continue
                dur = e.duration.quarterLength
                offset = round(e.getOffsetBySite(group), 8)
                # calculate all time regions given this offset

                # all stored values are seconds
                # noinspection PyDictCreation
                secondsDict = {}
                secondsDict['offsetSeconds'] = srcObj._accumulatedSeconds(
                    mmBoundaries, lowestOffset, offset)
                secondsDict['durationSeconds'] = srcObj._accumulatedSeconds(
                    mmBoundaries, offset, offset + dur)
                secondsDict['endTimeSeconds'] = (secondsDict['offsetSeconds']
                                                 + secondsDict['durationSeconds'])
                secondsDict['element'] = e
                secondsDict['voiceIndex'] = voiceIndex
                secondsMap.append(secondsDict)
        return secondsMap

    # do not make a property decorator since _getSecondsMap takes arguments
    secondsMap = property(_getSecondsMap, doc='''
        Returns a list where each element is a dictionary
        consisting of the 'offsetSeconds' in seconds of each element in a Stream,
        the 'duration' in seconds, the 'endTimeSeconds' in seconds (that is, the offset
        plus the duration), and the 'element' itself. Also contains a 'voiceIndex' entry which
        contains the voice number of the element, or None if there
        are no voices.


        >>> mm1 = tempo.MetronomeMark(number=120)
        >>> n1 = note.Note(type='quarter')
        >>> c1 = clef.AltoClef()
        >>> n2 = note.Note(type='half')
        >>> s1 = stream.Stream()
        >>> s1.append([mm1, n1, c1, n2])
        >>> om = s1.secondsMap
        >>> om[3]['offsetSeconds']
        0.5
        >>> om[3]['endTimeSeconds']
        1.5
        >>> om[3]['element'] is n2
        True
        >>> om[3]['voiceIndex']
    ''')

    # --------------------------------------------------------------------------
    # Metadata access

    def _getMetadata(self):
        '''


        >>> a = stream.Stream()
        >>> a.metadata = metadata.Metadata()
        '''
        mdList = self.getElementsByClass('Metadata')
        # only return metadata that has an offset = 0.0
        mdList = mdList.getElementsByOffset(0)
        if not mdList:
            return None
        else:
            return mdList[0]

    def _setMetadata(self, metadataObj):
        '''

        >>> a = stream.Stream()
        >>> a.metadata = metadata.Metadata()
        '''
        oldMetadata = self._getMetadata()
        if oldMetadata is not None:
            # environLocal.printDebug(['removing old metadata', oldMetadata])
            junk = self.pop(self.index(oldMetadata))

        if metadataObj is not None and isinstance(metadataObj, metadata.Metadata):
            self.insert(0, metadataObj)

    metadata = property(_getMetadata, _setMetadata,
                        doc='''
        Get or set the :class:`~music21.metadata.Metadata` object
        found at the beginning (offset 0) of this Stream.

        >>> s = stream.Stream()
        >>> s.metadata = metadata.Metadata()
        >>> s.metadata.composer = 'frank'
        >>> s.metadata.composer
        'frank'
        ''')

    # --------------------------------------------------------------------------
    # these methods override the behavior inherited from base.py

    def _getMeasureOffset(self):
        # this normally returns the offset of this object in its container
        # for now, simply return the offset for the activeSite
        return self.getOffsetBySite(self.activeSite)

    @property
    def beat(self):
        '''
        beat returns None for a Stream.
        '''
        # this normally returns the beat within a measure; here, it could
        # be beats from the beginning?
        return None

    @property
    def beatStr(self):
        '''
        unlike other Music21Objects, streams always have beatStr (beat string) of None

        May change to '' soon.
        '''
        return None

    @property
    def beatDuration(self):
        '''
        unlike other Music21Objects, streams always have beatDuration of None
        '''
        # this returns the duration of the active beat
        return None

    @property
    def beatStrength(self):
        '''
        unlike other Music21Objects, streams always have beatStrength of None
        '''
        # this returns the accent weight of the active beat
        return None

    def beatAndMeasureFromOffset(self, searchOffset, fixZeros=True):
        '''
        Returns a two-element tuple of the beat and the Measure object (or the first one
        if there are several at the same offset; unlikely but possible) for a given
        offset from the start of this Stream (that contains measures).

        Recursively searches for measures.  Note that this method assumes that all parts
        have measures of consistent length.  If that's not the case, this
        method can be called on the relevant part.

        This algorithm should work even for weird time signatures such as 2+3+2/8.


        >>> bach = corpus.parse('bach/bwv1.6')
        >>> bach.parts[0].measure(2).getContextByClass('TimeSignature')
        <music21.meter.TimeSignature 4/4>
        >>> returnTuples = []
        >>> for offset in [0.0, 1.0, 2.0, 5.0, 5.5]:
        ...     returnTuples.append(bach.beatAndMeasureFromOffset(offset))
        >>> returnTuples
        [(4.0, <music21.stream.Measure 0 offset=0.0>),
         (1.0, <music21.stream.Measure 1 offset=1.0>),
         (2.0, <music21.stream.Measure 1 offset=1.0>),
         (1.0, <music21.stream.Measure 2 offset=5.0>),
         (1.5, <music21.stream.Measure 2 offset=5.0>)]

        To get just the measureNumber and beat, use a transformation like this:
        >>> [(beat, measureObj.number) for beat, measureObj in returnTuples]
        [(4.0, 0), (1.0, 1), (2.0, 1), (1.0, 2), (1.5, 2)]

        Adapted from contributed code by Dmitri Tymoczko.  With thanks to DT.
        '''
        myStream = self
        if not myStream.hasMeasures():
            if myStream.hasPartLikeStreams():
                foundPart = False
                for subStream in myStream:
                    if 'Stream' not in subStream.classes:
                        continue
                    if subStream.hasMeasures():
                        foundPart = True
                        myStream = subStream
                        break
                if not foundPart:
                    raise StreamException('beatAndMeasureFromOffset: could not find any parts!')
                    # was return False
            else:
                if not myStream.hasMeasures():
                    raise StreamException('beatAndMeasureFromOffset: could not find any measures!')
                    # return False
        # Now we get the measure containing our offset.
        # In most cases this second part of the code does the job.
        myMeas = myStream.getElementAtOrBefore(searchOffset, classList=['Measure'])
        if myMeas is None:
            raise StreamException('beatAndMeasureFromOffset: no measure at that offset.')
        ts1 = myMeas.timeSignature
        if ts1 is None:
            ts1 = myMeas.getContextByClass('TimeSignature')

        if ts1 is None:
            raise StreamException(
                'beatAndMeasureFromOffset: could not find a time signature for that place.')
        try:
            myBeat = ts1.getBeatProportion(searchOffset - myMeas.offset)
        except:
            raise StreamException(
                'beatAndMeasureFromOffset: offset is beyond the end of the piece')
        foundMeasureNumber = myMeas.number
        # deal with second half of partial measures...

        # Now we deal with the problem case, where we have the second half of a partial measure.
        # These are
        # treated as unnumbered measures (or measures with suffix 'X') by notation programs,
        # even though they are
        # logically part of the previous measure.
        # The variable padBeats will represent extra beats we add to the front
        # of our partial measure
        numSuffix = myMeas.numberSuffix
        if numSuffix == '':
            numSuffix = None

        if numSuffix is not None or (fixZeros and foundMeasureNumber == 0):
            prevMeas = myStream.getElementBeforeOffset(myMeas.offset, classList=['Measure'])
            if prevMeas:
                ts2 = prevMeas.getContextByClass('TimeSignature')
                if not ts2:
                    raise StreamException(
                        'beatAndMeasureFromOffset: partial measure found, '
                        + 'but could not find a time signature for the preceding measure')
                # foundMeasureNumber = prevMeas.number

                # need this for chorales 197 and 280, where we
                # have a full-length measure followed by a pickup in
                # a new time signature
                if prevMeas.highestTime == ts2.barDuration.quarterLength:
                    padBeats = ts2.beatCount
                else:
                    padBeats = ts2.getBeatProportion(prevMeas.highestTime) - 1
                return (myBeat + padBeats, prevMeas)
            else:
                # partial measure at start of piece
                padBeats = ts1.getBeatProportion(
                    ts1.barDuration.quarterLength - myMeas.duration.quarterLength) - 1
                return (myBeat + padBeats, myMeas)
        else:
            return (myBeat, myMeas)

    # --------------------------------------------------------------------------
    # transformations

    def transpose(
        self,
        value,
        inPlace=False,
        recurse=True,
        classFilterList=None
    ):
        # noinspection PyShadowingNames
        '''
        Transpose all specified classes in the
        Stream by the
        user-provided value. If the value is an integer, the
        transposition is treated in half steps. If the value is
        a string, any Interval string specification can be
        provided.

        returns a new Stream by default, but if the
        optional "inPlace" key is set to True then
        it modifies pitches in place.

        TODO: for generic interval set accidental by key signature.

        >>> aInterval = interval.Interval('d5')

        >>> aStream = corpus.parse('bach/bwv324.xml')
        >>> part = aStream.parts[0]
        >>> [str(p) for p in aStream.parts[0].pitches[:10]]
        ['B4', 'D5', 'B4', 'B4', 'B4', 'B4', 'C5', 'B4', 'A4', 'A4']

        >>> bStream = aStream.parts[0].flat.transpose('d5')
        >>> [str(p) for p in bStream.pitches[:10]]
        ['F5', 'A-5', 'F5', 'F5', 'F5', 'F5', 'G-5', 'F5', 'E-5', 'E-5']

        Test that aStream hasn't been changed:

        >>> [str(p) for p in aStream.parts[0].pitches[:10]]
        ['B4', 'D5', 'B4', 'B4', 'B4', 'B4', 'C5', 'B4', 'A4', 'A4']

        >>> cStream = bStream.flat.transpose('a4')
        >>> [str(p) for p in cStream.pitches[:10]]
        ['B5', 'D6', 'B5', 'B5', 'B5', 'B5', 'C6', 'B5', 'A5', 'A5']

        >>> cStream.flat.transpose(aInterval, inPlace=True)
        >>> [str(p) for p in cStream.pitches[:10]]
        ['F6', 'A-6', 'F6', 'F6', 'F6', 'F6', 'G-6', 'F6', 'E-6', 'E-6']
        '''
        # only change the copy
        if not inPlace:
            post = copy.deepcopy(self)
            post.derivation.method = 'transpose'
            post.setDerivationMethod('transpose', recurse=True)
        else:
            post = self
        # for p in post.pitches:  # includes chords
        #     # do inplace transpositions on the deepcopy
        #     p.transpose(value, inPlace=True)
        #
        # for e in post.getElementsByClass(classFilterList=classFilterList):
        #     e.transpose(value, inPlace=True)

        # this will get all elements at this level and downward.
        if recurse is True:
            sIterator = post.recurse()
        else:
            sIterator = post.__iter__()

        if classFilterList:
            sIterator = sIterator.addFilter(filters.ClassFilter(classFilterList))

        for e in sIterator:
            if e.isStream:
                continue
            if hasattr(e, 'transpose'):
                if (hasattr(value, 'classes')
                        and 'GenericInterval' in value.classes):
                    # do not transpose KeySignatures w/ Generic Intervals
                    if 'KeySignature' not in e.classes and hasattr(e, 'pitches'):
                        k = e.getContextByClass('KeySignature')
                        for p in e.pitches:
                            value.transposePitchKeyAware(p, k, inPlace=True)
                else:
                    e.transpose(value, inPlace=True)
        if not inPlace:
            return post
        else:
            return None

    def scaleOffsets(self, amountToScale, *, anchorZero='lowest',
                     anchorZeroRecurse=None, inPlace=False):
        '''
        Scale all offsets by a multiplication factor given
        in `amountToScale`. Durations are not altered.

        To augment or diminish a Stream,
        see the :meth:`~music21.stream.Stream.augmentOrDiminish` method.

        The `anchorZero` parameter determines if and/or where
        the zero offset is established for the set of
        offsets in this Stream before processing.
        Offsets are shifted to make either the lower
        or upper values the new zero; then offsets are scaled;
        then the shifts are removed. Accepted values are None
        (no offset shifting), "lowest", or "highest".

        The `anchorZeroRecurse` parameter determines the
        anchorZero for all embedded Streams, and Streams
        embedded within those Streams. If the lowest offset
        in an embedded Stream is non-zero, setting this value
        to None will a the space between the start of that
        Stream and the first element to be scaled. If the
        lowest offset in an embedded Stream is non-zero,
        setting this value to 'lowest' will not alter the
        space between the start of that Stream and the first
        element to be scaled.

        To shift all the elements in a Stream, see the
        :meth:`~music21.stream.Stream.shiftElements` method.

        Changed in v.5 -- inPlace is default False, and anchorZero, anchorZeroRecurse
        and inPlace are keyword only arguments.
        '''
        # if we have offsets at 0, 2, 4
        # we scale by 2, getting offsets at 0, 4, 8

        # compare to offsets at 10, 12, 14
        # we scale by 2; if we do not anchor at lower, we get 20, 24, 28
        # if we anchor, we get 10, 14, 18

        if not amountToScale > 0:
            raise StreamException('amountToScale must be greater than zero')

        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
            returnObj.derivation.method = 'scaleOffsets'
            returnObj.setDerivationMethod('scaleOffsets', recurse=True)
        else:
            returnObj = self

        # first, get the offset shift requested
        if anchorZero in ['lowest']:
            offsetShift = Fraction(returnObj.lowestOffset)
        elif anchorZero in ['highest']:
            offsetShift = Fraction(returnObj.highestOffset)
        elif anchorZero in [None]:
            offsetShift = Fraction(0, 1)
        else:
            raise StreamException(f'an anchorZero value of {anchorZero} is not accepted')

        for e in returnObj._elements:

            # subtract the offset shift (and lowestOffset of 80 becomes 0)
            # then apply the amountToScale
            o = (returnObj.elementOffset(e) - offsetShift) * amountToScale
            # after scaling, return the shift taken away
            o += offsetShift

            # environLocal.printDebug(['changing offset', o, scalar, offsetShift])

            returnObj.setElementOffset(e, o)
            # need to look for embedded Streams, and call this method
            # on them, with inPlace , as already copied if
            # inPlace is != True
            # if hasattr(e, 'elements'):  # recurse time:
            if e.isStream:
                e.scaleOffsets(amountToScale,
                               anchorZero=anchorZeroRecurse,
                               anchorZeroRecurse=anchorZeroRecurse,
                               inPlace=True)

        returnObj.coreElementsChanged()
        if not inPlace:
            return returnObj

    def scaleDurations(self, amountToScale, *, inPlace=False):
        '''
        Scale all durations by a provided scalar. Offsets are not modified.

        To augment or diminish a Stream, see the
        :meth:`~music21.stream.Stream.augmentOrDiminish` method.

        We do not retain durations in any circumstance; if inPlace=True, two deepcopies are done
        which can be quite slow.
        '''
        if not amountToScale > 0:
            raise StreamException('amountToScale must be greater than zero')

        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
            returnObj.derivation.method = 'scaleDurations'
            returnObj.setDerivationMethod('scaleDurations', recurse=True)
        else:
            returnObj = self

        for e in returnObj.recurse().getElementsNotOfClass('Stream'):
            e.duration = e.duration.augmentOrDiminish(amountToScale)

        returnObj.coreElementsChanged()
        if inPlace is not True:
            return returnObj

    def augmentOrDiminish(self, amountToScale, *, inPlace=False):
        '''
        Given a number greater than zero,
        multiplies the current quarterLength of the
        duration of each element by this number
        as well as their offset and returns a new Stream.
        Or if `inPlace` is
        set to True, modifies the durations of each
        element within the stream.


        A number of 0.5 will halve the durations and relative
        offset positions; a number of 2 will double the
        durations and relative offset positions.



        Note that the default for inPlace is the opposite
        of what it is for augmentOrDiminish on a Duration.
        This is done purposely to reflect the most common
        usage.



        >>> s = stream.Stream()
        >>> n = note.Note()
        >>> s.repeatAppend(n, 10)
        >>> s.highestOffset, s.highestTime
        (9.0, 10.0)
        >>> s1 = s.augmentOrDiminish(2)
        >>> s1.highestOffset, s1.highestTime
        (18.0, 20.0)
        >>> s1 = s.augmentOrDiminish(0.5)
        >>> s1.highestOffset, s1.highestTime
        (4.5, 5.0)
        '''
        if not amountToScale > 0:
            raise StreamException('amountToScale must be greater than zero')
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
            returnObj.derivation.method = 'augmentOrDiminish'
            returnObj.setDerivationMethod('augmentOrDiminish', recurse=True)
        else:
            returnObj = self

        # inPlace is True as a copy has already been made if nec
        returnObj.scaleOffsets(amountToScale=amountToScale, anchorZero='lowest',
                               anchorZeroRecurse=None, inPlace=True)
        returnObj.scaleDurations(amountToScale=amountToScale, inPlace=True)
        returnObj.coreElementsChanged()

        # do not need to call elements changed, as called in sub methods
        return returnObj

    def quantize(
        self,
        quarterLengthDivisors=None,
        processOffsets=True,
        processDurations=True,
        inPlace=False,
        recurse=True
    ):
        # noinspection PyShadowingNames
        '''
        Quantize time values in this Stream by snapping offsets
        and/or durations to the nearest multiple of a quarter length value
        given as one or more divisors of 1 quarter length. The quantized
        value found closest to a divisor multiple will be used.

        The `quarterLengthDivisors` provides a flexible way to provide quantization
        settings. For example, (2,) will snap all events to eighth note grid.
        (4, 3) will snap events to sixteenth notes and eighth note triplets,
        whichever is closer. (4, 6) will snap events to sixteenth notes and
        sixteenth note triplets.  If quarterLengthDivisors is not specified then
        defaults.quantizationQuarterLengthDivisors is used.  The default is (4, 3).

        `processOffsets` determines whether the Offsets are quantized.

        `processDurations` determines whether the Durations are quantized.

        Both are set to True by default.  Setting both to False does nothing to the Stream.

        if `inPlace` is True then the quantization is done on the Stream itself.  If False
        (default) then a new quantized Stream of the same class is returned.

        If `recurse` is True then all substreams are also quantized.  If False (TODO: MAKE default)
        then only the highest level of the Stream is quantized.



        >>> n = note.Note()
        >>> n.quarterLength = 0.49
        >>> s = stream.Stream()
        >>> s.repeatInsert(n, [0.1, 0.49, 0.9])
        >>> nShort = note.Note()
        >>> nShort.quarterLength = 0.26
        >>> s.repeatInsert(nShort, [1.49, 1.76])

        >>> s.quantize([4], processOffsets=True, processDurations=True, inPlace=True)
        >>> [e.offset for e in s]
        [0.0, 0.5, 1.0, 1.5, 1.75]
        >>> [e.duration.quarterLength for e in s]
        [0.5, 0.5, 0.5, 0.25, 0.25]


        The error in quantization is set in the editorial attribute for the note in
        two places `.offsetQuantizationError` and `.quarterLengthQuantizationError`

        >>> [e.editorial.offsetQuantizationError for e in s.notes]
        [0.1, -0.01, -0.1, -0.01, 0.01]
        >>> [e.editorial.quarterLengthQuantizationError for e in s.notes]
        [-0.01, -0.01, -0.01, 0.01, 0.01]


        with default quarterLengthDivisors...

        >>> s = stream.Stream()
        >>> s.repeatInsert(n, [0.1, 0.49, 0.9])
        >>> nShort = note.Note()
        >>> nShort.quarterLength = 0.26
        >>> s.repeatInsert(nShort, [1.49, 1.76])
        >>> t = s.quantize(processOffsets=True, processDurations=True, inPlace=False)
        >>> [e.offset for e in t]
        [0.0, 0.5, 1.0, 1.5, 1.75]
        >>> [e.duration.quarterLength for e in t]
        [0.5, 0.5, 0.5, 0.25, 0.25]

        OMIT_FROM_DOCS


        Test changing defaults, running, and changing back...


        >>> dd = defaults.quantizationQuarterLengthDivisors
        >>> defaults.quantizationQuarterLengthDivisors = (3,)

        >>> u = s.quantize(processOffsets=True, processDurations=True, inPlace=False)
        >>> [e.offset for e in u]
        [0.0, Fraction(1, 3), 1.0, Fraction(4, 3), Fraction(5, 3)]
        >>> [e.duration.quarterLength for e in u]
        [Fraction(1, 3), Fraction(1, 3), Fraction(1, 3), Fraction(1, 3), Fraction(1, 3)]

        >>> defaults.quantizationQuarterLengthDivisors = dd
        >>> v = s.quantize(processOffsets=True, processDurations=True, inPlace=False)
        >>> [e.offset for e in v]
        [0.0, 0.5, 1.0, 1.5, 1.75]
        >>> [e.duration.quarterLength for e in v]
        [0.5, 0.5, 0.5, 0.25, 0.25]

        TODO: test recurse and inPlace etc.
        TODO: recurse should be off by default -- standard
        '''
        if quarterLengthDivisors is None:
            quarterLengthDivisors = defaults.quantizationQuarterLengthDivisors

        # this presently is not trying to avoid overlaps that
        # result from quantization; this may be necessary

        def bestMatch(target, divisors):
            found = []
            for div in divisors:
                match, error, signedErrorInner = common.nearestMultiple(target, (1 / div))
                found.append((error, match, signedErrorInner))  # reverse match, error for sorting
            # get first, and leave out the error
            bestMatchTuple = sorted(found)[0]
            return bestMatchTuple

        # if we have a min of 0.25 (sixteenth)
        # quarterLengthMin = quarterLengthDivisors[0]

        # TODO: Use new filters...
        if inPlace is False:
            returnStream = copy.deepcopy(self)
            returnStream.derivation.method = 'quantize'
            returnStream.setDerivationMethod('quantize', recurse=True)

        else:
            returnStream = self

        useStreams = [returnStream]
        if recurse is True:
            for obj in returnStream.recurse():
                if 'Stream' in obj.classes:
                    useStreams.append(obj)

        for useStream in useStreams:
            for e in useStream._elements:
                if processOffsets:
                    o = useStream.elementOffset(e)
                    sign = 1
                    if o < 0:
                        sign = -1
                        o = -1 * o
                    unused_error, oNew, signedError = bestMatch(float(o), quarterLengthDivisors)
                    useStream.setElementOffset(e, oNew * sign)
                    if (hasattr(e, 'editorial')
                            and signedError != 0):
                        e.editorial.offsetQuantizationError = signedError * sign
                if processDurations:
                    ql = e.duration.quarterLength
                    if ql < 0:  # buggy MIDI file?
                        ql = 0
                    unused_error, qlNew, signedError = bestMatch(
                        float(ql), quarterLengthDivisors)
                    # Enforce nonzero duration for non-grace notes
                    if qlNew == 0 and 'GeneralNote' in e.classes and not e.duration.isGrace:
                        qlNew = 1 / max(quarterLengthDivisors)
                        signedError = ql - qlNew
                    e.duration.quarterLength = qlNew
                    if (hasattr(e, 'editorial')
                            and signedError != 0):
                        e.editorial.quarterLengthQuantizationError = signedError

        if inPlace is False:
            return returnStream

    def expandRepeats(self, copySpanners=True):
        '''Expand this Stream with repeats. Nested repeats
        given with :class:`~music21.bar.Repeat` objects, or
        repeats and sections designated with
        :class:`~music21.repeat.RepeatExpression` objects, are all expanded.

        This method always returns a new Stream, with
        deepcopies of all contained elements at all levels.

        Uses the :class:`~music21.repeat.Expander` object in the `repeat` module.

        TODO: DOC TEST
        '''
        if not self.hasMeasures():
            raise StreamException(
                'cannot process repeats on Stream that does not contain measures'
            )

        ex = repeat.Expander(self)
        post = ex.process()

        # copy all non-repeats
        # do not copy repeat brackets
        for e in self.getElementsNotOfClass('Measure'):
            if 'RepeatBracket' not in e.classes:
                eNew = copy.deepcopy(e)  # assume that this is needed
                post.insert(self.elementOffset(e), eNew)

        # all elements at this level and in measures have been copied; now we
        # need to reconnect spanners
        if copySpanners:
            # environLocal.printDebug(['Stream.expandRepeats', 'copying spanners'])
            # spannerBundle = spanner.SpannerBundle(post.flat.spanners)
            spannerBundle = post.spannerBundle
            # iterate over complete semi flat (need containers); find
            # all new/old pairs
            for e in post.semiFlat:
                # update based on last id, new object
                if e.sites.hasSpannerSite():
                    origin = e.derivation.origin
                    if (origin is not None
                            and e.derivation.method == '__deepcopy__'):
                        spannerBundle.replaceSpannedElement(id(origin), e)

        return post

    # --------------------------------------------------------------------------
    # slicing and recasting a note as many notes

    def sliceByQuarterLengths(self, quarterLengthList, *, target=None,
                              addTies=True, inPlace=False):
        '''
        Slice all :class:`~music21.duration.Duration` objects on all Notes and Rests
        of this Stream.
        Duration are sliced according to values provided in `quarterLengthList` list.
        If the sum of these values is less than the Duration, the values are accumulated
        in a loop to try to fill the Duration. If a match cannot be found, an
        Exception is raised.

        If `target` is None, the entire Stream is processed. Otherwise, only the element
        specified is manipulated.

        '''
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        if returnObj.hasMeasures():
            # call on component measures
            for m in returnObj.getElementsByClass('Measure'):
                m.sliceByQuarterLengths(quarterLengthList,
                                        target=target, addTies=addTies, inPlace=True)
            returnObj.coreElementsChanged()
            return returnObj  # exit

        if returnObj.hasPartLikeStreams():
            for p in returnObj.getElementsByClass('Part'):
                p.sliceByQuarterLengths(quarterLengthList,
                                        target=target, addTies=addTies, inPlace=True)
            returnObj.coreElementsChanged()
            return returnObj  # exit

        if not common.isListLike(quarterLengthList):
            quarterLengthList = [quarterLengthList]

        if target is not None:
            # get the element out of return obj
            # need to use self.index to get index value
            eToProcess = [returnObj[self.index(target)]]
        else:  # get elements list from Stream
            eToProcess = returnObj.notesAndRests

        for e in eToProcess:
            # if qlList values are greater than the found duration, skip
            if opFrac(sum(quarterLengthList)) > e.quarterLength:
                continue
            elif not opFrac(sum(quarterLengthList)) == e.quarterLength:
                # try to map a list that is of sufficient duration
                qlProcess = []
                i = 0
                while True:
                    qlProcess.append(
                        quarterLengthList[i % len(quarterLengthList)])
                    i += 1
                    sumQL = opFrac(sum(qlProcess))
                    if sumQL >= e.quarterLength:
                        break
            else:
                qlProcess = quarterLengthList

            # environLocal.printDebug(['got qlProcess', qlProcess,
            # 'for element', e, e.quarterLength])

            if not opFrac(sum(qlProcess)) == e.quarterLength:
                raise StreamException(
                    'cannot map quarterLength list into element Duration: %s, %s' % (
                        sum(qlProcess), e.quarterLength))

            post = e.splitByQuarterLengths(qlProcess, addTies=addTies)
            # remove e from the source
            oInsert = e.getOffsetBySite(returnObj)
            returnObj.remove(e)
            for eNew in post:
                returnObj.coreInsert(oInsert, eNew)
                oInsert = opFrac(oInsert + eNew.quarterLength)

        returnObj.coreElementsChanged()

        if not inPlace:
            return returnObj

    def sliceByGreatestDivisor(self, *, addTies=True, inPlace=False):
        '''
        Slice all :class:`~music21.duration.Duration` objects on all Notes and Rests of this Stream.
        Duration are sliced according to the approximate GCD found in all durations.
        '''
        # when operating on a Stream, this should take all durations found
        # and use the approximateGCD to get a min duration; then, call sliceByQuarterLengths

        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        if returnObj.hasMeasures():
            # call on component measures
            for m in returnObj.getElementsByClass('Measure'):
                m.sliceByGreatestDivisor(addTies=addTies, inPlace=True)
            returnObj.coreElementsChanged()
            return returnObj  # exit

        uniqueQuarterLengths = []
        for e in returnObj.notesAndRests:
            if e.quarterLength not in uniqueQuarterLengths:
                uniqueQuarterLengths.append(e.quarterLength)

        # environLocal.printDebug(['unique quarter lengths', uniqueQuarterLengths])

        # will raise an exception if no gcd can be found
        divisor = common.approximateGCD(uniqueQuarterLengths)

        # process in place b/c a copy, if necessary, has already been made
        returnObj.sliceByQuarterLengths(quarterLengthList=[divisor],
                                        target=None, addTies=addTies, inPlace=True)

        returnObj.coreElementsChanged()
        if not inPlace:
            return returnObj

    def sliceAtOffsets(
        self,
        offsetList,
        target=None,
        addTies=True,
        inPlace=False,
        displayTiedAccidentals=False
    ):
        # noinspection PyShadowingNames
        '''
        Given a list of quarter lengths, slice and optionally tie all
        Music21Objects crossing these points.

        >>> s = stream.Stream()
        >>> n = note.Note()
        >>> n.duration.type = 'whole'
        >>> s.append(n)
        >>> post = s.sliceAtOffsets([1, 2, 3], inPlace=True)
        >>> [(e.offset, e.quarterLength) for e in s]
        [(0.0, 1.0), (1.0, 1.0), (2.0, 1.0), (3.0, 1.0)]
        '''
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        if returnObj.hasMeasures():
            # call on component measures
            for m in returnObj.getElementsByClass('Measure'):
                # offset values are not relative to measure; need to
                # shift by each measure's offset
                offsetListLocal = [o - m.getOffsetBySite(returnObj) for o in offsetList]
                m.sliceAtOffsets(offsetList=offsetListLocal,
                                 addTies=addTies,
                                 inPlace=True,
                                 displayTiedAccidentals=displayTiedAccidentals)
            return returnObj  # exit

        if returnObj.hasPartLikeStreams():
            # part-like requires getting Streams, not Parts
            for p in returnObj.getElementsByClass('Stream'):
                offsetListLocal = [o - p.getOffsetBySite(returnObj) for o in offsetList]
                p.sliceAtOffsets(offsetList=offsetListLocal,
                                 addTies=addTies,
                                 inPlace=True,
                                 displayTiedAccidentals=displayTiedAccidentals)
            return returnObj  # exit

        # list of start, start+dur, element, all in abs offset time
        offsetMap = self.offsetMap(returnObj)

        offsetList = [opFrac(o) for o in offsetList]

        for ob in offsetMap:
            # if target is defined, only modify that object
            e, oStart, oEnd, unused_voiceCount = ob
            if target is not None and id(e) != id(target):
                continue

            cutPoints = []
            oStart = opFrac(oStart)
            oEnd = opFrac(oEnd)

            for o in offsetList:
                if oStart < o < oEnd:
                    cutPoints.append(o)
            # environLocal.printDebug(['cutPoints', cutPoints, 'oStart', oStart, 'oEnd', oEnd])
            if cutPoints:
                # remove old
                # eProc = returnObj.remove(e)
                eNext = e
                oStartNext = oStart
                for o in cutPoints:
                    oCut = o - oStartNext
                    unused_eComplete, eNext = eNext.splitAtQuarterLength(
                        oCut,
                        retainOrigin=True,
                        addTies=addTies,
                        displayTiedAccidentals=displayTiedAccidentals
                    )
                    # only need to insert eNext, as eComplete was modified
                    # in place due to retainOrigin option
                    # insert at o, not oCut (duration into element)
                    returnObj.coreInsert(o, eNext)
                    oStartNext = o
        returnObj.coreElementsChanged()
        if inPlace is False:
            return returnObj

    def sliceByBeat(self,
                    target=None,
                    addTies=True,
                    inPlace=False,
                    displayTiedAccidentals=False):
        '''
        Slice all elements in the Stream that have a Duration at
        the offsets determined to be the beat from the local TimeSignature.

        TODO: return None if inPlace is True
        '''

        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        if returnObj.hasMeasures():
            # call on component measures
            for m in returnObj.getElementsByClass('Measure'):
                m.sliceByBeat(target=target,
                              addTies=addTies,
                              inPlace=True,
                              displayTiedAccidentals=displayTiedAccidentals)
            return returnObj  # exit

        if returnObj.hasPartLikeStreams():
            for p in returnObj.getElementsByClass('Part'):
                p.sliceByBeat(target=target,
                              addTies=addTies,
                              inPlace=True,
                              displayTiedAccidentals=displayTiedAccidentals)
            return returnObj  # exit

        # this will return a default
        # using this method to work on Stream, not just Measures
        tsStream = returnObj.getTimeSignatures(returnDefault=True)

        if not tsStream:
            raise StreamException('no time signature was found')

        if len(tsStream) > 1:
            raise StreamException('not yet implemented: slice by changing time signatures')

        offsetList = tsStream[0].getBeatOffsets()
        returnObj.sliceAtOffsets(offsetList,
                                 target=target,
                                 addTies=addTies,
                                 inPlace=True,
                                 displayTiedAccidentals=displayTiedAccidentals)

        return returnObj

    # --------------------------------------------------------------------------
    # get boolean information from the Stream

    def hasMeasures(self):
        '''
        Return a boolean value showing if this Stream contains Measures.


        >>> s = stream.Stream()
        >>> s.repeatAppend(note.Note(), 8)
        >>> s.hasMeasures()
        False
        >>> s.makeMeasures(inPlace=True)
        >>> len(s.getElementsByClass('Measure'))
        2
        >>> s.hasMeasures()
        True
        '''
        if 'hasMeasures' not in self._cache or self._cache['hasMeasures'] is None:
            post = False
            # do not need to look in endElements
            for obj in self._elements:
                # if obj is a Part, we have multi-parts
                if 'Measure' in obj.classes:
                    post = True
                    break  # only need one
            self._cache['hasMeasures'] = post
        return self._cache['hasMeasures']

    def hasVoices(self):
        '''
        Return a boolean value showing if this Stream contains Voices
        '''
        if 'hasVoices' not in self._cache or self._cache['hasVoices'] is None:
            post = False
            # do not need to look in endElements
            for obj in self._elements:
                # if obj is a Part, we have multi-parts
                if 'Voice' in obj.classes:
                    post = True
                    break  # only need one
            self._cache['hasVoices'] = post
        return self._cache['hasVoices']

    def hasPartLikeStreams(self):
        '''
        Return a boolean value showing if this Stream contains any Parts,
        or Part-like sub-Streams.

        Part-like sub-streams are Streams that contain Measures or Notes.
        And where no sub-stream begins at an offset besides zero.

        >>> s = stream.Score()
        >>> s.hasPartLikeStreams()
        False
        >>> p1 = stream.Part()
        >>> p1.repeatAppend(note.Note(), 8)
        >>> s.insert(0, p1)
        >>> s.hasPartLikeStreams()
        True

        A stream that has a measure in it is not a part-like stream.

        >>> s = stream.Score()
        >>> m1 = stream.Measure()
        >>> m1.repeatAppend(note.Note(), 4)
        >>> s.append(m1)
        >>> s.hasPartLikeStreams()
        False


        A stream with a single generic Stream substream at the beginning has part-like Streams...

        >>> s = stream.Score()
        >>> m1 = stream.Stream()
        >>> m1.repeatAppend(note.Note(), 4)
        >>> s.append(m1)
        >>> s.hasPartLikeStreams()
        True


        Adding another though makes it not part-like.

        >>> m2 = stream.Stream()
        >>> m2.repeatAppend(note.Note(), 4)
        >>> s.append(m2)
        >>> s.hasPartLikeStreams()
        False

        Flat objects do not have part-like Streams:

        >>> sf = s.flat
        >>> sf.hasPartLikeStreams()
        False
        '''
        if 'hasPartLikeStreams' not in self._cache or self._cache['hasPartLikeStreams'] is None:
            multiPart = False
            if not self.isFlat:  # if flat, does not have parts!
                # do not need to look in endElements
                for obj in self.getElementsByClass('Stream'):
                    # if obj is a Part, we have multi-parts
                    if 'Part' in obj.classes:
                        multiPart = True
                        break

                    elif 'Measure' in obj.classes or 'Voice' in obj.classes:
                        multiPart = False
                        break

                    elif obj.offset != 0.0:
                        multiPart = False
                        break

                    # if components are streams of Notes or Measures,
                    # than assume this is like a Part
                    elif (obj.getElementsByClass('Measure')
                          or obj.notesAndRests):
                        multiPart = True
            self._cache['hasPartLikeStreams'] = multiPart
        return self._cache['hasPartLikeStreams']

    def isTwelveTone(self):
        '''
        Return true if this Stream only employs twelve-tone equal-tempered pitch values.


        >>> s = stream.Stream()
        >>> s.append(note.Note('G#4'))
        >>> s.isTwelveTone()
        True
        >>> s.append(note.Note('G~4'))
        >>> s.isTwelveTone()
        False
        '''
        for p in self.pitches:
            if not p.isTwelveTone():
                return False
        return True

    def isWellFormedNotation(self):
        # noinspection PyShadowingNames
        '''
        Return True if, given the context of this Stream or Stream subclass,
        contains what appears to be well-formed notation. This often means
        the formation of Measures, or a Score that contains Part with Measures.


        >>> s = corpus.parse('bwv66.6')
        >>> s.isWellFormedNotation()
        True
        >>> s.parts[0].isWellFormedNotation()
        True
        >>> s.parts[0].getElementsByClass('Measure')[0].isWellFormedNotation()
        True

        >>> s = stream.Score()
        >>> m = stream.Measure()
        >>> s.append(m)
        >>> s.isWellFormedNotation()
        False
        '''
        # if a measure, we assume we are well-formed
        if 'Measure' in self.classes:
            return True
        elif 'Part' in self.classes:
            if self.hasMeasures():
                return True
        elif self.hasPartLikeStreams():
            # higher constraint than has part-like streams: has sub-streams
            # with Measures
            match = 0
            count = 0
            for s in self.getElementsByClass('Stream'):
                count += 1
                if s.hasMeasures():
                    match += 1
            if match == count:
                return True
        # all other conditions are not well-formed notation
        return False

    # --------------------------------------------------------------------------
    @property
    def notesAndRests(self):
        '''
        The notesAndRests property of a Stream returns a `StreamIterator`
        that consists only of the :class:`~music21.note.GeneralNote` objects found in
        the stream.  The new Stream will contain
        mostly notes and rests (including
        :class:`~music21.note.Note`,
        :class:`~music21.chord.Chord`,
        :class:`~music21.note.Rest`) but also their subclasses, such as
        `Harmony` objects (`ChordSymbols`, `FiguredBass`), `SpacerRests` etc.


        >>> s1 = stream.Stream()
        >>> k1 = key.KeySignature(0)  # key of C
        >>> n1 = note.Note('B')
        >>> r1 = note.Rest()
        >>> c1 = chord.Chord(['A', 'B-'])
        >>> s1.append([k1, n1, r1, c1])
        >>> s1.show('text')
        {0.0} <music21.key.KeySignature of no sharps or flats>
        {0.0} <music21.note.Note B>
        {1.0} <music21.note.Rest rest>
        {2.0} <music21.chord.Chord A B->

        `.notesAndRests` removes the `KeySignature` object but keeps the `Rest`.

        >>> notes1 = s1.notesAndRests.stream()
        >>> notes1.show('text')
        {0.0} <music21.note.Note B>
        {1.0} <music21.note.Rest rest>
        {2.0} <music21.chord.Chord A B->

        The same caveats about `Stream` classes and `.flat` in `.notes` apply here.
        '''
        if 'notesAndRests' not in self._cache or self._cache['notesAndRests'] is None:
            # environLocal.printDebug(['updating noteAndRests cache:', str(self), id(self)])
            noteIterator = self.getElementsByClass('GeneralNote')
            noteIterator.overrideDerivation = 'notesAndRests'
            self._cache['notesAndRests'] = noteIterator
        return self._cache['notesAndRests']

        # return self.getElementsByClass([note.GeneralNote, chord.Chord])
        # using string class names is import for some test contexts where
        # absolute class name matching fails
        # return self.getElementsByClass(['GeneralNote', 'Chord'])

    @property
    def notes(self):
        '''
        The notes property of a Stream returns an iterator
        that consists only of the notes (that is,
        :class:`~music21.note.Note`,
        :class:`~music21.chord.Chord`, etc.) found
        in the stream. This excludes :class:`~music21.note.Rest` objects.


        >>> p1 = stream.Part()
        >>> k1 = key.KeySignature(0)  # key of C
        >>> n1 = note.Note('B')
        >>> r1 = note.Rest()
        >>> c1 = chord.Chord(['A', 'B-'])
        >>> p1.append([k1, n1, r1, c1])
        >>> p1.show('text')
        {0.0} <music21.key.KeySignature of no sharps or flats>
        {0.0} <music21.note.Note B>
        {1.0} <music21.note.Rest rest>
        {2.0} <music21.chord.Chord A B->

        >>> noteStream = p1.notes.stream()
        >>> noteStream.show('text')
        {0.0} <music21.note.Note B>
        {2.0} <music21.chord.Chord A B->

        Notice that `.notes` returns a :class:`~music21.stream.iterator.StreamIterator` object

        >>> p1.notes
        <music21.stream.iterator.StreamIterator for Part:0x105b56128 @:0>

        Let's add a measure to `p1`:

        >>> m1 = stream.Measure()
        >>> n2 = note.Note('D')
        >>> m1.insert(0, n2)
        >>> p1.append(m1)

        Now note that `n2` is *not* found in `p1.notes`

        >>> p1.notes.stream().show('text')
        {0.0} <music21.note.Note B>
        {2.0} <music21.chord.Chord A B->

        We need to call `p1.flat.notes` to find it:

        >>> p1.flat.notes.stream().show('text')
        {0.0} <music21.note.Note B>
        {2.0} <music21.chord.Chord A B->
        {3.0} <music21.note.Note D>

        (Technical note: All elements of class NotRest are being found
        right now.  This will eventually change to also filter out
        Unpitched objects, so that all elements returned by
        `.notes` have a `.pitches` attribute.
        '''
        if 'notes' not in self._cache or self._cache['notes'] is None:
            noteIterator = self.getElementsByClass('NotRest')
            noteIterator.overrideDerivation = 'notes'
            self._cache['notes'] = noteIterator
        return self._cache['notes']

    @property
    def pitches(self):
        '''
        Returns all :class:`~music21.pitch.Pitch` objects found in any
        element in the Stream as a Python List. Elements such as
        Streams, and Chords will have their Pitch objects accumulated as
        well. For that reason, a flat representation is not required.

        Pitch objects are returned in a List, not a Stream.  This usage
        differs from the .notes property, but makes sense since Pitch
        objects usually have by default a Duration of zero. This is an important difference
        between them and :class:`music21.note.Note` objects.

        key.Key objects are subclasses of Scales, which DO have a .pitches list, but
        they are specifically exempt from looking for pitches, since that is unlikely
        what someone wants here.

        N.B., TODO: This may turn to an Iterator soon.

        >>> from music21 import corpus
        >>> a = corpus.parse('bach/bwv324.xml')
        >>> partOnePitches = a.parts[0].pitches
        >>> len(partOnePitches)
        25
        >>> partOnePitches[0]
        <music21.pitch.Pitch B4>
        >>> [str(p) for p in partOnePitches[0:10]]
        ['B4', 'D5', 'B4', 'B4', 'B4', 'B4', 'C5', 'B4', 'A4', 'A4']


        Note that the pitches returned above are
        objects, not text:

        >>> partOnePitches[0].octave
        4

        Since all elements with a `.pitches` list are returned and streams themselves have
        `.pitches` properties (the docs you are reading now), pitches from embedded streams
        are also returned.  Flattening a stream is not necessary.  Whether this is a
        feature or a bug is in the eye of the beholder.

        >>> len(a.pitches)
        104

        Chords get their pitches found as well:

        >>> c = chord.Chord(['C4', 'E4', 'G4'])
        >>> n = note.Note('F#4')
        >>> m = stream.Measure()
        >>> m.append(n)
        >>> m.append(c)
        >>> m.pitches
        [<music21.pitch.Pitch F#4>, <music21.pitch.Pitch C4>,
         <music21.pitch.Pitch E4>, <music21.pitch.Pitch G4>]
        '''
        post = []
        for e in self.elements:
            if 'music21.key.Key' in e.classSet:  # use fully qualified name
                continue
            if hasattr(e, 'pitch'):
                post.append(e.pitch)
            # both Chords and Stream have a pitches properties; this just
            # causes a recursive pitch gathering
            elif hasattr(e, 'pitches'):
                post.extend(list(e.pitches))
        return post

    # --------------------------------------------------------------------------
    # interval routines

    def findConsecutiveNotes(self,
                             skipRests=False,
                             skipChords=False,
                             skipUnisons=False,
                             skipOctaves=False,
                             skipGaps=False,
                             getOverlaps=False,
                             noNone=False, **keywords):
        r'''
        Returns a list of consecutive *pitched* Notes in a Stream.

        A single "None" is placed in the list
        at any point there is a discontinuity (such as if there is a
        rest between two pitches), unless the `noNone` parameter is True.

        How to determine consecutive pitches is a little tricky and there are many options:

            The `skipUnisons` parameter uses the midi-note value (.ps) to determine unisons,
            so enharmonic transitions (F# -> Gb) are
            also skipped if `skipUnisons` is true.  We believe that this is the most common usage.
            However, because
            of this, you cannot completely be sure that the
            x.findConsecutiveNotes() - x.findConsecutiveNotes(skipUnisons=True)
            will give you the number of P1s in the piece, because there could be
            d2's in there as well.

        See test.TestStream.testFindConsecutiveNotes() for usage details.

        This example is adapted from the tutorials/Examples page.

        >>> s = converter.parse("tinynotation: 4/4 f8 d'8~ d'8 d'8~ d'4 b'-8 a'-8 a-8")
        >>> m = s.measure(1)
        >>> m.findConsecutiveNotes(skipUnisons=True, skipOctaves=True,
        ...                        skipRests=True, noNone=True)
        [<music21.note.Note F>, <music21.note.Note D>,
         <music21.note.Note B->, <music21.note.Note A->]

        >>> m.findConsecutiveNotes(skipUnisons=False,
        ...                        skipRests=True, noNone=True)
        [<music21.note.Note F>, <music21.note.Note D>,
         <music21.note.Note D>, <music21.note.Note D>, <music21.note.Note D>,
         <music21.note.Note B->, <music21.note.Note A->]

        OMIT_FROM_DOCS

        N.B. for chords, currently, only the first pitch is tested for unison.
        this is a bug TODO: FIX

        (\*\*kwargs is there so that other methods that pass along dicts to
        findConsecutiveNotes don't have to remove
        their own args; this method is used in melodicIntervals.)
        '''
        if self.isSorted is False and self.autoSort:
            self.sort()
        returnList = []
        lastStart = 0.0
        lastEnd = 0.0
        lastWasNone = False
        lastPitch = None
        if skipOctaves is True:
            skipUnisons = True  # implied

        # need to look for voices in self and deal with each one at a time
        if self.hasVoices():
            vGroups = []
            for v in self.voices:
                vGroups.append(v.flat)
            vGroups.append(self)
        else:
            vGroups = (self.flat,)

        for v in vGroups:
            for e in v:
                # Filter out all but notes and rests
                if 'GeneralNote' not in e.classes:
                    continue
                if (lastWasNone is False
                        and skipGaps is False
                        and e.offset > lastEnd):
                    if not noNone:
                        returnList.append(None)
                        lastWasNone = True
                if hasattr(e, 'pitch'):
                    # if (skipUnisons is False or isinstance(lastPitch, list)
                    if not(skipUnisons is False
                            or isinstance(lastPitch, tuple)
                            or lastPitch is None
                            or (hasattr(lastPitch, 'pitchClass')
                                and e.pitch.pitchClass != lastPitch.pitchClass)
                            or (skipOctaves is False
                                and hasattr(lastPitch, 'pitchClass')
                                and e.pitch.ps != lastPitch.ps)):
                        continue
                    if getOverlaps is False and e.offset < lastEnd:
                        continue

                    returnList.append(e)
                    if e.offset < lastEnd:  # is an overlap...
                        continue

                    lastStart = e.offset
                    if hasattr(e, 'duration'):
                        lastEnd = opFrac(lastStart + e.duration.quarterLength)
                    else:
                        lastEnd = lastStart
                    lastWasNone = False
                    lastPitch = e.pitch

                # if we have a chord
                elif hasattr(e, 'pitches') and len(e.pitches) > 1:
                    if skipChords is True:
                        if lastWasNone is False and not noNone:
                            returnList.append(None)
                            lastWasNone = True
                            lastPitch = None
                    # if we have a chord
                    elif (not (skipUnisons is True
                                and isinstance(lastPitch, (list, tuple))
                                and e.pitches[0].ps == lastPitch[0].ps
                               ) and (getOverlaps is True or e.offset >= lastEnd)):
                        returnList.append(e)
                        if e.offset < lastEnd:  # is an overlap...
                            continue

                        lastStart = e.offset
                        if hasattr(e, 'duration'):
                            lastEnd = opFrac(lastStart + e.duration.quarterLength)
                        else:
                            lastEnd = lastStart
                        # this is the case where the last pitch is a
                        # a list
                        lastPitch = e.pitches
                        lastWasNone = False

                elif (skipRests is False
                      and isinstance(e, note.Rest)
                      and lastWasNone is False):
                    if noNone is False:
                        returnList.append(None)
                        lastWasNone = True
                        lastPitch = None
                elif skipRests is True and isinstance(e, note.Rest):
                    lastEnd = opFrac(e.offset + e.duration.quarterLength)

        if lastWasNone is True:
            returnList.pop()  # removes the last-added element
        return returnList

    def melodicIntervals(self, *skipArgs, **skipKeywords):
        '''
        Returns a Stream of :class:`~music21.interval.Interval` objects
        between Notes (and by default, Chords) that follow each other in a stream.
        the offset of the Interval is the offset of the beginning of the interval
        (if two notes are adjacent, then this offset is equal to the offset of
        the second note, but if skipRests is set to True or there is a gap
        in the Stream, then these two numbers
        will be different).

        See :meth:`~music21.stream.Stream.findConsecutiveNotes` in this class for
        a discussion of what is meant by default for "consecutive notes", and
        which keywords such as skipChords, skipRests, skipUnisons, etc. can be
        used to change that behavior.

        The interval between a Note and a Chord (or between two chords) is the
        interval to the first pitch of the Chord (pitches[0]) which is usually the lowest.
        For more complex interval calculations,
        run :meth:`~music21.stream.Stream.findConsecutiveNotes` and then calculate
        your own intervals directly.

        Returns an empty Stream if there are not at least two elements found by
        findConsecutiveNotes.



        >>> s1 = converter.parse("tinynotation: 3/4 c4 d' r b b'", makeNotation=False)
        >>> #_DOCS_SHOW s1.show()

        .. image:: images/streamMelodicIntervals1.*
            :width: 246

        >>> intervalStream1 = s1.melodicIntervals()
        >>> intervalStream1.show('text')
        {1.0} <music21.interval.Interval M9>
        {4.0} <music21.interval.Interval P8>

        >>> M9 = intervalStream1[0]
        >>> M9.noteStart.nameWithOctave, M9.noteEnd.nameWithOctave
        ('C4', 'D5')

        Using the skip attributes from :meth:`~music21.stream.Stream.findConsecutiveNotes`,
        we can alter which intervals are reported:

        >>> intervalStream2 = s1.melodicIntervals(skipRests=True, skipOctaves=True)
        >>> intervalStream2.show('text')
        {1.0} <music21.interval.Interval M9>
        {2.0} <music21.interval.Interval m-3>

        >>> m3 = intervalStream2[1]
        >>> m3.directedNiceName
        'Descending Minor Third'
        '''
        returnList = self.findConsecutiveNotes(**skipKeywords)
        if len(returnList) < 2:
            return self.cloneEmpty(derivationMethod='melodicIntervals')

        returnStream = self.cloneEmpty(derivationMethod='melodicIntervals')
        for i in range(len(returnList) - 1):
            firstNote = returnList[i]
            secondNote = returnList[i + 1]
            firstPitch = None
            secondPitch = None
            if firstNote is not None and secondNote is not None:
                startIsChord = False
                endIsChord = False
                if hasattr(firstNote, 'pitch') and firstNote.pitch is not None:
                    firstPitch = firstNote.pitch
                elif hasattr(firstNote, 'pitches') and firstNote.pitches:
                    firstPitch = firstNote.pitches[0]
                    startIsChord = True
                if hasattr(secondNote, 'pitch') and secondNote.pitch is not None:
                    secondPitch = secondNote.pitch
                elif hasattr(secondNote, 'pitches') and secondNote.pitches:
                    secondPitch = secondNote.pitches[0]
                    endIsChord = True
                if firstPitch is not None and secondPitch is not None:
                    returnInterval = interval.notesToInterval(firstPitch,
                                                              secondPitch)
                    if startIsChord is False:
                        returnInterval.noteStart = firstNote
                    if endIsChord is False:
                        returnInterval.noteEnd = secondNote
                    returnInterval.offset = opFrac(firstNote.offset
                                                   + firstNote.duration.quarterLength)
                    returnInterval.duration = duration.Duration(opFrac(
                        secondNote.offset - returnInterval.offset))
                    returnStream.insert(returnInterval)

        return returnStream

    # --------------------------------------------------------------------------
    def _getDurSpan(self, flatStream):
        '''
        Given a flat stream, create a list of the start and end
        times (as a tuple pair) of all elements in the Stream.


        >>> a = stream.Stream()
        >>> a.repeatInsert(note.Note(type='half'), [0, 1, 2, 3, 4])
        >>> a._getDurSpan(a.flat)
        [(0.0, 2.0), (1.0, 3.0), (2.0, 4.0), (3.0, 5.0), (4.0, 6.0)]
        '''
        post = []
        for e in flatStream:
            dur = e.duration.quarterLength
            durSpan = (e.offset, opFrac(e.offset + dur))
            post.append(durSpan)
        # assume this is already sorted
        # index found here will be the same as elementsSorted
        return post

    def _durSpanOverlap(self, a, b, includeEndBoundary=False):
        '''
        Compare two durSpans and find overlaps; optionally,
        include coincident boundaries. a and b are sorted to permit any ordering.

        If an element ends at 3.0 and another starts at 3.0, this may or may not
        be considered an overlap. The includeCoincidentEnds parameter determines
        this behaviour, where ending and starting 3.0 being a type of overlap
        is set by the includeEndBoundary being True.


        >>> sc = stream.Stream()
        >>> sc._durSpanOverlap((0, 5), (4, 12), False)
        True
        >>> sc._durSpanOverlap((0, 10), (11, 12), False)
        False
        >>> sc._durSpanOverlap((11, 12), (0, 10), False)
        False
        >>> sc._durSpanOverlap((0, 3), (3, 6), False)
        False
        >>> sc._durSpanOverlap((0, 3), (3, 6), True)
        True
        '''
        durSpans = [a, b]
        # sorting will ensure that leading numbers are ordered from low to high
        durSpans.sort()
        found = False

        if includeEndBoundary:
            # if the start of b is before the end of a
            # if durSpans[1][0] <= durSpans[0][1]:
            if durSpans[1][0] <= durSpans[0][1]:
                found = True
        else:  # do not include coincident boundaries
            # if durSpans[1][0] < durSpans[0][1]:
            if durSpans[1][0] < durSpans[0][1]:
                found = True
        return found

    def _findLayering(self):
        '''
        Find any elements in an elementsSorted list that have
        durations that cause overlaps.

        Returns a lists that has elements with overlaps,
        all index values that match are included in that list.

        See testOverlaps, in unit tests, for examples.

        Used in getOverlaps inside makeVoices.
        '''
        flatStream = self.flat
        if flatStream.isSorted is False:
            flatStream = flatStream.sorted
        # these may not be sorted
        durSpanSorted = self._getDurSpan(flatStream)
        # According to the above comment, the spans may not be sorted
        # so we sort them to be sure, but keep track of their original indices
        durSpanSortedIndex = list(enumerate(durSpanSorted))
        durSpanSortedIndex.sort()

        # create a list with an entry for each element
        # in each entry, provide indices of all other elements that overlap
        overlapMap = [[] for dummy in range(len(durSpanSorted))]

        for i in range(len(durSpanSortedIndex)):
            src = durSpanSortedIndex[i]
            for j in range(i + 1, len(durSpanSortedIndex)):
                dst = durSpanSortedIndex[j]
                if self._durSpanOverlap(src[1], dst[1]):
                    overlapMap[src[0]].append(dst[0])
                    overlapMap[dst[0]].append(src[0])
                else:
                    break

        # Preserve exact same behaviour as earlier code.
        # It is unclear if anything depends on the individual lists being sorted.
        for ls in overlapMap:
            ls.sort()
        return overlapMap

    def _consolidateLayering(self, layeringMap):
        '''
        Given a stream of flat elements and a map of equal length with lists of
        index values that meet a given condition (overlap or simultaneities),
        organize into a dictionary by the relevant or first offset
        '''
        flatStream = self.flat
        if flatStream.isSorted is False:
            flatStream = flatStream.sorted

        if len(layeringMap) != len(flatStream):
            raise StreamException('layeringMap must be the same length as flatStream')

        post = {}
        for i in range(len(layeringMap)):
            # print('examining i:', i)
            indices = layeringMap[i]
            if not indices:
                continue

            srcElementObj = flatStream[i]
            srcOffset = srcElementObj.offset
            dstOffset = None
            # print('found indices', indices)
            # check indices
            for j in indices:  # indices of other elements that overlap
                elementObj = flatStream[j]
                # check if this object has been stored anywhere yet
                # if so, use the offset of where it was stored to
                # to store the src element below
                store = True
                for k in post:
                    # this comparison needs to be based on object id, not
                    # matching equality
                    if id(elementObj) in [id(e) for e in post[k]]:
                        # if elementObj in post[key]:
                        store = False
                        dstOffset = k
                        break
                if dstOffset is None:
                    dstOffset = srcOffset
                if store:
                    # print('storing offset', dstOffset)
                    if dstOffset not in post:
                        post[dstOffset] = []  # create dictionary entry
                    post[dstOffset].append(elementObj)

            # check if this object has been stored anywhere yet
            store = True
            for k in post:
                if id(srcElementObj) in [id(e) for e in post[k]]:
                    # if srcElementObj in post[key]:
                    store = False
                    break
            # dst offset may have been set when looking at indices
            if store:
                if dstOffset is None:
                    dstOffset = srcOffset
                if dstOffset not in post:
                    post[dstOffset] = []  # create dictionary entry
                # print('storing offset', dstOffset)
                post[dstOffset].append(srcElementObj)
        # print(post)
        return post

    def findGaps(self):
        # noinspection PyShadowingNames
        '''
        Returns either (1) a Stream containing empty Music21Objects
        whose offsets and durations
        are the length of gaps in the Stream
        or (2) None if there are no gaps.

        N.B. there may be gaps in the flattened representation of the stream
        but not in the unflattened.  Hence why "isSequence" calls self.flat.isGapless

        >>> s = stream.Stream()
        >>> s.insert(1.0, note.Note('E', type='half'))
        >>> s.insert(5.0, note.Note('F', type='whole'))
        >>> gapStream = s.findGaps()
        >>> gapStream.show('text', addEndTimes=True)
        {0.0 - 1.0} <music21.base.Music21Object object at 0x10e5f8be0>
        {3.0 - 5.0} <music21.base.Music21Object object at 0x10e5f8e80>

        Returns None if not gaps:

        >>> s2 = stream.Stream()
        >>> s2.append(note.Note('G'))
        >>> s2.findGaps() is None
        True

        Note: in v7 -- gapStream will be filled with rests instead of Music21Objects
        '''
        if 'findGaps' in self._cache and self._cache['findGaps'] is not None:
            return self._cache['findGaps']

        gapStream = self.cloneEmpty(derivationMethod='findGaps')

        highestCurrentEndTime = 0.0
        for e in self:
            eOffset = self.elementOffset(e, stringReturns=True)
            if eOffset == 'highestTime':
                break
            if eOffset > highestCurrentEndTime:
                # TODO(msc): in v7 return a note.Rest() instead -- takes 3x as long, but
                #    we are casting it into a Rest in the only call to findGaps anyhow.
                gapElement = base.Music21Object()
                gapQuarterLength = opFrac(eOffset - highestCurrentEndTime)
                gapElement.duration.quarterLength = gapQuarterLength
                gapStream.insert(highestCurrentEndTime, gapElement, ignoreSort=True)
            eDur = e.duration.quarterLength
            highestCurrentEndTime = opFrac(max(highestCurrentEndTime, eOffset + eDur))

        # TODO: Is this even necessary, we do insert the elements in sorted order
        #     and the stream is empty at the start
        gapStream.sort()

        if not gapStream:
            return None
        else:
            self._cache['findGaps'] = gapStream
            return gapStream

    @property
    def isGapless(self):
        '''
        Returns True if there are no gaps between the lowest offset and the highest time.
        Otherwise returns False


        >>> s = stream.Stream()
        >>> s.append(note.Note('C'))
        >>> s.append(note.Note('D'))
        >>> s.isGapless
        True
        >>> s.insert(10.0, note.Note('E'))
        >>> s.isGapless
        False

        OMIT_FROM_DOCS

        Test cache:

        >>> s.isGapless
        False
        '''
        if 'isGapless' in self._cache and self._cache['isGapless'] is not None:
            return self._cache['isGapless']
        else:
            if self.findGaps() is None:
                self._cache['Gapless'] = True
                return True
            else:
                self._cache['Gapless'] = False
                return False

    def getOverlaps(self):
        '''
        Find any elements that overlap. Overlapping might include elements
        that have zero-length duration simultaneous.

        This method returns a dictionary, where keys
        are the start time of the first overlap and
        value are a list of all objects included in
        that overlap group.

        This example demonstrates that end-joining overlaps do not count.

        >>> a = stream.Stream()
        >>> for x in range(4):
        ...     n = note.Note('G#')
        ...     n.duration = duration.Duration('quarter')
        ...     n.offset = x * 1
        ...     a.insert(n)
        ...
        >>> d = a.getOverlaps()
        >>> len(d)
        0

        Notes starting at the same time overlap:

        >>> a = stream.Stream()
        >>> for x in [0, 0, 0, 0, 13, 13, 13]:
        ...     n = note.Note('G#')
        ...     n.duration = duration.Duration('half')
        ...     n.offset = x
        ...     a.insert(n)
        ...
        >>> d = a.getOverlaps()
        >>> len(d[0])
        4
        >>> len(d[13])
        3
        >>> a = stream.Stream()
        >>> for x in [0, 0, 0, 0, 3, 3, 3]:
        ...     n = note.Note('G#')
        ...     n.duration = duration.Duration('whole')
        ...     n.offset = x
        ...     a.insert(n)
        ...

        Default is to not include coincident boundaries

        >>> d = a.getOverlaps()
        >>> len(d[0])
        7

        '''
        overlapMap = self._findLayering()
        # environLocal.printDebug(['overlapMap', overlapMap])

        return self._consolidateLayering(overlapMap)

    def isSequence(self):
        '''A stream is a sequence if it has no overlaps.


        >>> a = stream.Stream()
        >>> for x in [0, 0, 0, 0, 3, 3, 3]:
        ...     n = note.Note('G#')
        ...     n.duration = duration.Duration('whole')
        ...     n.offset = x * 1
        ...     a.insert(n)
        ...
        >>> a.isSequence()
        False

        OMIT_FROM_DOCS
        TODO: check that co-incident boundaries are properly handled

        >>> a = stream.Stream()
        >>> for x in [0, 4, 8.0]:
        ...     n = note.Note('G#')
        ...     n.duration = duration.Duration('whole')
        ...     a.append(n)
        ...
        >>> a.isSequence()
        True
        '''
        overlapMap = self._findLayering()
        post = True
        for indexList in overlapMap:
            if indexList:
                post = False
                break
        return post

    # --------------------------------------------------------------------------
    # routines for dealing with relationships to other streams.
    # Formerly in twoStreams.py

    def simultaneousAttacks(self, stream2):
        '''
        returns an ordered list of offsets where elements are started (attacked)
        at the same time in both self and stream2.

        In this example, we create one stream of Qtr, Half, Qtr, and one of Half, Qtr, Qtr.
        There are simultaneous attacks at offset 0.0 (the beginning) and at offset 3.0,
        but not at 1.0 or 2.0:


        >>> st1 = stream.Stream()
        >>> st2 = stream.Stream()
        >>> st1.append([note.Note(type='quarter'),
        ...             note.Note(type='half'),
        ...             note.Note(type='quarter')])
        >>> st2.append([note.Note(type='half'),
        ...             note.Note(type='quarter'),
        ...             note.Note(type='quarter')])
        >>> print(st1.simultaneousAttacks(st2))
        [0.0, 3.0]
        '''
        stream1Offsets = iterator.OffsetIterator(self)
        stream2Offsets = iterator.OffsetIterator(stream2)

        returnKey = {}

        for thisList in stream1Offsets:
            thisOffset = self.elementOffset(thisList[0])
            returnKey[thisOffset] = 1

        for thatList in stream2Offsets:
            thatOffset = thatList[0].getOffsetBySite(stream2)
            if thatOffset in returnKey:
                returnKey[thatOffset] += 1

        returnList = []
        for foundOffset in sorted(returnKey):
            if returnKey[foundOffset] >= 2:
                returnList.append(foundOffset)
        return returnList

        # this method was supposed to be faster, but actually 2000 times slower on op133

        # sOuter = Stream()
        # for e in self:
        #     sOuter.coreInsert(e.offset, e)
        # for e in stream2:
        #     sOuter.coreInsert(e.offset, e)
        # sOuter.coreElementsChanged(updateIsFlat=False)
        # sOuterTree = sOuter.asTree(flatten=False, groupOffsets=True)
        # return sorted(sOuterTree.simultaneityDict().keys())

    def attachIntervalsBetweenStreams(self, cmpStream):
        # noinspection PyShadowingNames
        '''
        For each element in self, creates an interval.Interval object in the element's
        editorial that is the interval between it and the element in cmpStream that
        is sounding at the moment the element in srcStream is attacked.

        Remember that if you are comparing two streams with measures, etc.,
        you'll need to flatten each stream as follows:

        >>> #_DOCS_SHOW stream1.flat.attachIntervalsBetweenStreams(stream2.flat)

        Example usage:


        >>> s1 = converter.parse('tinynotation: 7/4 C4 d8 e f# g A2 d2', makeNotation=False)
        >>> s2 = converter.parse('tinynotation: 7/4 g4 e8 d c4   a2 r2', makeNotation=False)
        >>> s1.attachIntervalsBetweenStreams(s2)
        >>> for n in s1.notes:
        ...     if n.editorial.harmonicInterval is None:
        ...         print('None')  # if other voice had a rest...
        ...     else:
        ...         print(n.editorial.harmonicInterval.directedName)
        P12
        M2
        M-2
        A-4
        P-5
        P8
        None
        '''
        for n in self.notes:
            # get simultaneous elements form other stream
            simultEls = cmpStream.getElementsByOffset(self.elementOffset(n),
                                                      mustBeginInSpan=False,
                                                      mustFinishInSpan=False)
            if simultEls:
                for simultNote in simultEls.notes:
                    interval1 = None
                    try:
                        interval1 = interval.notesToInterval(n, simultNote)
                        n.editorial.harmonicInterval = interval1
                    except exceptions21.Music21Exception:
                        pass
                    if interval1 is not None:
                        break  # inner loop

    def attachMelodicIntervals(self):
        '''
        For each element in self, creates an interval.Interval object in the element's
        editorial that is the interval between it and the previous element in the stream. Thus,
        the first element will have a value of None.

        DEPRECATED sometime soon.  A replacement to come presently.

        >>> s1 = converter.parse('tinyNotation: 7/4 C4 d8 e f# g A2 d2', makeNotation=False)
        >>> s1.attachMelodicIntervals()
        >>> for n in s1.notes:
        ...     if n.editorial.melodicInterval is None:
        ...         print('None')
        ...     else:
        ...         print(n.editorial.melodicInterval.directedName)
        None
        M9
        M2
        M2
        m2
        m-7
        P4

        >>> s = stream.Stream()
        >>> s.append(note.Note('C'))
        >>> s.append(note.Note('D'))
        >>> s.append(note.Rest(quarterLength=4.0))
        >>> s.append(note.Note('D'))
        >>> s.attachMelodicIntervals()
        >>> for n in s.notes:
        ...     if n.editorial.melodicInterval is None:
        ...         print('None')  # if other voice had a rest...
        ...     else:
        ...         print(n.editorial.melodicInterval.directedName)
        None
        M2
        P1
        '''

        notes = self.notes.stream()
        currentObject = notes[0]
        previousObject = None
        while currentObject is not None:
            if (previousObject is not None
                    and 'Note' in currentObject.classes
                    and 'Note' in previousObject.classes):
                currentObject.editorial.melodicInterval = interval.notesToInterval(
                    previousObject, currentObject)
            previousObject = currentObject
            currentObject = currentObject.next()

    def playingWhenAttacked(self, el, elStream=None):
        '''
        Given an element (from another Stream) returns the single element
        in this Stream that is sounding while the given element starts.

        If there are multiple elements sounding at the moment it is
        attacked, the method returns the first element of the same class
        as this element, if any. If no element
        is of the same class, then the first element encountered is
        returned. For more complex usages, use allPlayingWhileSounding.

        Returns None if no elements fit the bill.

        The optional elStream is the stream in which el is found.
        If provided, el's offset
        in that Stream is used.  Otherwise, the current offset in
        el is used.  It is just
        in case you are paranoid that el.offset might not be what
        you want, because of some fancy manipulation of
        el.activeSite

        >>> n1 = note.Note('G#')
        >>> n2 = note.Note('D#')
        >>> s1 = stream.Stream()
        >>> s1.insert(20.0, n1)
        >>> s1.insert(21.0, n2)

        >>> n3 = note.Note('C#')
        >>> s2 = stream.Stream()
        >>> s2.insert(20.0, n3)
        >>> s1.playingWhenAttacked(n3)
        <music21.note.Note G#>

        >>> n3.setOffsetBySite(s2, 20.5)
        >>> s1.playingWhenAttacked(n3)
        <music21.note.Note G#>

        >>> n3.setOffsetBySite(s2, 21.0)
        >>> n3.offset
        21.0
        >>> s1.playingWhenAttacked(n3)
        <music21.note.Note D#>

        If there is more than one item at the same time in the other stream
        then the first item matching the same class is returned, even if
        another element has a closer offset.

        >>> n3.setOffsetBySite(s2, 20.5)
        >>> s1.insert(20.5, clef.BassClef())
        >>> s1.playingWhenAttacked(n3)
        <music21.note.Note G#>
        >>> fc = clef.FClef()  # superclass of BassClef
        >>> s2.insert(20.5, fc)
        >>> s1.playingWhenAttacked(fc)
        <music21.clef.BassClef>

        But since clefs have zero duration, moving the FClef ever so slightly
        will find the note instead

        >>> fc.setOffsetBySite(s2, 20.6)
        >>> s1.playingWhenAttacked(fc)
        <music21.note.Note G#>


        Optionally, specify the site to get the offset from:

        >>> n3.setOffsetBySite(s2, 21.0)
        >>> n3.setOffsetBySite(None, 100)
        >>> n3.activeSite = None
        >>> s1.playingWhenAttacked(n3) is None
        True
        >>> s1.playingWhenAttacked(n3, s2).name
        'D#'
        '''
        if elStream is not None:  # bit of safety
            elOffset = el.getOffsetBySite(elStream)
        else:
            elOffset = el.offset

        otherElements = self.getElementsByOffset(elOffset, mustBeginInSpan=False)
        if not otherElements:
            return None
        elif len(otherElements) == 1:
            return otherElements[0]
        else:
            for thisEl in otherElements:
                if isinstance(thisEl, el.__class__):
                    return thisEl
            return otherElements[0]

    def allPlayingWhileSounding(self, el, elStream=None):
        '''
        Returns a new Stream of elements in this stream that sound
        at the same time as `el`, an element presumably in another Stream.

        The offset of this new Stream is set to el's offset, while the
        offset of elements within the Stream are adjusted relative to
        their position with respect to the start of el.  Thus, a note
        that is sounding already when el begins would have a negative
        offset.  The duration of otherStream is forced
        to be the length of el -- thus a note sustained after el ends
        may have a release time beyond that of the duration of the Stream.

        As above, elStream is an optional Stream to look up el's offset in.  Use
        this to work on an element in another part.

        The method always returns a Stream, but it might be an empty Stream.

        OMIT_FROM_DOCS
        TODO: write: requireClass:
        Takes as an optional parameter "requireClass".  If this parameter
        is boolean True then only elements
        of the same class as el are added to the new Stream.  If requireClass
        is list, it is used like
        classList in elsewhere in stream to provide a list of classes that the
        el must be a part of.

        '''
        if elStream is not None:  # bit of safety
            elOffset = el.getOffsetBySite(elStream)
        else:
            elOffset = el.offset
        elEnd = elOffset + el.quarterLength

        if elEnd != elOffset:  # i.e. not zero length
            otherElements = self.getElementsByOffset(
                elOffset,
                elEnd,
                mustBeginInSpan=False,
                includeEndBoundary=False,
                includeElementsThatEndAtStart=False).stream()
        else:
            otherElements = self.getElementsByOffset(elOffset,
                                                     mustBeginInSpan=False).stream()

        otherElements.offset = elOffset
        otherElements.quarterLength = el.quarterLength
        for thisEl in otherElements:
            thisEl.offset = thisEl.offset - elOffset

        return otherElements

    # --------------------------------------------------------------------------
    # voice processing routines

    def makeVoices(self, *, inPlace=False, fillGaps=True):
        '''
        If this Stream has overlapping Notes or Chords, this method will isolate
        all overlaps in unique Voices, and place those Voices in the Stream.

        >>> s = stream.Stream()
        >>> s.insert(0, note.Note('C4', quarterLength=4))
        >>> s.repeatInsert(note.Note('b-4', quarterLength=0.5), [x * 0.5 for x in list(range(8))])
        >>> s.makeVoices(inPlace=True)
        >>> len(s.voices)
        2
        >>> [n.pitch for n in s.voices[0].notes]
        [<music21.pitch.Pitch C4>]
        >>> [str(n.pitch) for n in s.voices[1].notes]
        ['B-4', 'B-4', 'B-4', 'B-4', 'B-4', 'B-4', 'B-4', 'B-4']
        '''
        # this method may not always
        # produce the optimal voice assignment based on context (register
        # chord formation, etc
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self
        # must be sorted
        if not returnObj.isSorted:
            returnObj.sort()
        olDict = returnObj.notes.stream().getOverlaps()
        # environLocal.printDebug(['makeVoices(): olDict', olDict])
        # find the max necessary voices by finding the max number
        # of elements in each group; these may not all be necessary
        maxVoiceCount = 1
        for group in olDict.values():
            if len(group) > maxVoiceCount:
                maxVoiceCount = len(group)
        if maxVoiceCount == 1:  # nothing to do here
            if not inPlace:
                return returnObj
            return None

        # store all voices in a list
        voices = []
        for dummy in range(maxVoiceCount):
            voices.append(Voice())  # add voice classes

        # iterate through all elements; if not in an overlap, place in
        # voice 1, otherwise, distribute
        for e in returnObj.notes:
            o = e.getOffsetBySite(returnObj)
            # cannot match here by offset, as olDict keys are representative
            # of the first overlapped offset, not all contained offsets
            # if o not in olDict:  # place in a first voices
            #    voices[0].insert(o, e)
            # find a voice to place in
            # as elements are sorted, can use the highest time
            # else:
            for v in voices:
                if v.highestTime <= o:
                    v.insert(o, e)
                    break
            # remove from source
            returnObj.remove(e)
        # remove any unused voices (possible if overlap group has sus)
        for v in voices:
            if v:  # skip empty voices
                if fillGaps:
                    returnObj.makeRests(fillGaps=True, inPlace=True)
                returnObj.insert(0, v)
        # remove rests in returnObj
        returnObj.removeByClass('Rest')
        # elements changed will already have been called
        if not inPlace:
            return returnObj

    def _maxVoiceCount(self, *, countById=False):
        '''
        Returns the maximum number of voices in a part.  Used by voicesToParts.
        Minimum returned is 1.  If `countById` is True, returns a tuple of
        (maxVoiceCount, voiceIdList)

        >>> import copy

        >>> p0 = stream.Part()
        >>> p0._maxVoiceCount()
        1
        >>> v0 = stream.Voice(id='v0')
        >>> p0.insert(0, v0)
        >>> p0._maxVoiceCount()
        1
        >>> v1 = stream.Voice(id='v1')
        >>> p0.insert(0, v1)
        >>> p0._maxVoiceCount()
        2

        >>> p1 = stream.Part()
        >>> m1 = stream.Measure(number=1)
        >>> p1.insert(0, m1)
        >>> p1._maxVoiceCount()
        1
        >>> m1.insert(0, v0)
        >>> p1._maxVoiceCount()
        1
        >>> m1.insert(0, v1)
        >>> p1._maxVoiceCount()
        2
        >>> m2 = stream.Measure(number=2)
        >>> p1.append(m2)
        >>> p1._maxVoiceCount()
        2
        >>> v01 = copy.copy(v0)
        >>> v11 = stream.Voice(id='v11')
        >>> m2.insert(0, v01)
        >>> m2.insert(0, v11)
        >>> p1._maxVoiceCount()
        2
        >>> v2 = stream.Voice(id='v2')
        >>> m2.insert(0, v2)
        >>> p1._maxVoiceCount()
        3

        If `countById` is True then different voice ids create different parts.

        >>> mvc, vIds = p1._maxVoiceCount(countById=True)
        >>> mvc
        4
        >>> vIds
        ['v0', 'v1', 'v11', 'v2']

        >>> v01.id = 'v01'
        >>> mvc, vIds = p1._maxVoiceCount(countById=True)
        >>> mvc
        5
        >>> vIds
        ['v0', 'v1', 'v01', 'v11', 'v2']
        '''
        voiceCount = 1
        voiceIds = []

        if self.hasMeasures():
            for m in self.iter.getElementsByClass('Measure'):
                mVoices = m.voices
                mVCount = len(mVoices)
                if not countById:
                    voiceCount = max(mVCount, voiceCount)
                else:
                    for v in mVoices:
                        if v.id not in voiceIds:
                            voiceIds.append(v.id)

        elif self.hasVoices():
            voices = self.voices
            if not countById:
                voiceCount = len(voices)
            else:
                voiceIds = [v.id for v in voices]
        else:  # if no measure or voices, get one part
            voiceCount = 1

        voiceCount = max(voiceCount, len(voiceIds))

        if not countById:
            return voiceCount
        else:
            return voiceCount, voiceIds

    def voicesToParts(self, *, separateById=False):
        # noinspection PyShadowingNames
        '''
        If this Stream defines one or more voices,
        extract each into a Part, returning a Score.

        If this Stream has no voices, return the Stream as a Part within a Score.

        >>> c = corpus.parse('demos/two-voices')
        >>> c.show('t')
        {0.0} <music21.text.TextBox 'Music21 Fr...'>
        {0.0} <music21.text.TextBox 'Music21'>
        {0.0} <music21.metadata.Metadata object at 0x109ce1630>
        {0.0} <music21.stream.Part Piano>
            {0.0} <music21.instrument.Instrument 'P1: Piano: '>
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.layout.PageLayout>
                {0.0} <music21.layout.SystemLayout>
                ...
                {0.0} <music21.clef.BassClef>
                {0.0} <music21.key.Key of D major>
                {0.0} <music21.meter.TimeSignature 4/4>
                {0.0} <music21.stream.Voice 3>
                    {0.0} <music21.note.Note E>
                    ...
                    {3.0} <music21.note.Rest rest>
                {0.0} <music21.stream.Voice 4>
                    {0.0} <music21.note.Note F#>
                    ...
                    {3.5} <music21.note.Note B>
            {4.0} <music21.stream.Measure 2 offset=4.0>
                {0.0} <music21.stream.Voice 3>
                    {0.0} <music21.note.Note E>
                    ...
                    {3.0} <music21.note.Rest rest>
                {0.0} <music21.stream.Voice 4>
                    {0.0} <music21.note.Note E>
                    ...
                    {3.5} <music21.note.Note A>
            {8.0} <music21.stream.Measure 3 offset=8.0>
                {0.0} <music21.note.Rest rest>
                {4.0} <music21.bar.Barline type=final>
        {0.0} <music21.layout.ScoreLayout>

        >>> ce = c.voicesToParts()
        >>> ce.show('t')
        {0.0} <music21.stream.Part Piano-v0>
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.clef.TrebleClef>
                {0.0} <music21.key.Key of D major>
                {0.0} <music21.meter.TimeSignature 4/4>
                {0.0} <music21.note.Note E>
                ...
                {3.0} <music21.note.Rest rest>
            {4.0} <music21.stream.Measure 2 offset=4.0>
                {0.0} <music21.note.Note E>
                ...
                {3.0} <music21.note.Rest rest>
            {8.0} <music21.stream.Measure 3 offset=8.0>
                {0.0} <music21.note.Rest rest>
                {4.0} <music21.bar.Barline type=final>
        {0.0} <music21.stream.Part Piano-v1>
            {0.0} <music21.stream.Measure 1 offset=0.0>
                {0.0} <music21.clef.BassClef>
                {0.0} <music21.key.Key of D major>
                ...
                {3.5} <music21.note.Note B>
            {4.0} <music21.stream.Measure 2 offset=4.0>
                {0.0} <music21.note.Note E>
                ...
                {3.5} <music21.note.Note A>
            {8.0} <music21.stream.Measure 3 offset=8.0>
                {0.0} <music21.bar.Barline type=final>
        <BLANKLINE>

        If `separateById` is True then all voices with the same id
        will be connected to the same Part, regardless of order
        they appear in the measure.

        Compare the previous output:

        >>> p0pitches = ce.parts[0].pitches
        >>> p1pitches = ce.parts[1].pitches
        >>> ' '.join([p.nameWithOctave for p in p0pitches])
        'E4 D#4 D#4 E4 F#4 E4 B3 B3 E4 E4'
        >>> ' '.join([p.nameWithOctave for p in p1pitches])
        'F#2 F#3 E3 E2 D#2 D#3 B2 B3 E2 E3 D3 D2 C#2 C#3 A2 A3'

        Swap voice ids in first measure:

        >>> m0 = c.parts[0].getElementsByClass('Measure')[0]
        >>> m0.voices[0].id, m0.voices[1].id
        ('3', '4')
        >>> m0.voices[0].id = '4'
        >>> m0.voices[1].id = '3'

        Now run voicesToParts with `separateById=True`

        >>> ce = c.voicesToParts(separateById=True)
        >>> p0pitches = ce.parts[0].pitches
        >>> p1pitches = ce.parts[1].pitches
        >>> ' '.join([p.nameWithOctave for p in p0pitches])
        'E4 D#4 D#4 E4 F#4 E2 E3 D3 D2 C#2 C#3 A2 A3'
        >>> ' '.join([p.nameWithOctave for p in p1pitches])
        'F#2 F#3 E3 E2 D#2 D#3 B2 B3 E4 B3 B3 E4 E4'

        Note that the second and subsequent measure's pitches were changed
        not the first, because separateById aligns the voices according to
        order first encountered, not by sorting the Ids.
        '''
        s = Score()
        # s.metadata = self.metadata

        # if this is a Score, call this recursively on each Part, then
        # add all parts to one Score
        if self.hasPartLikeStreams():
            # part-like does not necessarily mean .parts
            for p in self.getElementsByClass('Stream'):
                sSub = p.voicesToParts(separateById=separateById)
                for pSub in sSub:
                    s.insert(0, pSub)
            return s

        # need to find maximum voice count
        mvcReturn = self._maxVoiceCount(countById=separateById)
        if not separateById:
            partCount = mvcReturn
            voiceIds = []
        else:
            partCount, voiceIds = mvcReturn

        # environLocal.printDebug(['voicesToParts(): got partCount', partCount])

        # create parts, naming ids by voice id?
        partDict = {}

        for i in range(partCount):
            p = Part()
            s.insert(0, p)
            if not separateById:
                p.id = str(self.id) + '-v' + str(i)
                partDict[i] = p
            else:
                voiceId = voiceIds[i]
                p.id = str(self.id) + '-' + voiceId
                partDict[voiceId] = p

        def doOneMeasureWithVoices(mInner):
            '''
            This is the main routine for dealing with the most common
            and most difficult voice set.
            '''
            mActive = Measure()
            mActive.mergeAttributes(mInner)  # get groups, optional id
            # merge everything except Voices; this will get
            # clefs
            mActive.mergeElements(
                mInner,
                classFilterList=(
                    'Barline', 'TimeSignature', 'Clef', 'KeySignature',
                )
            )

            # vIndexInner = 0 should not be necessary, but pylint warns on loop variables
            # that could possibly be undefined used out of the loop.
            vIndexInner = 0

            seenIdsThisMeasure = set()
            for vIndexInner, vInner in enumerate(mInner.voices):
                # TODO(msc): fix bugs if same voice id appears twice in same measure

                # make an independent copy
                mNewInner = copy.deepcopy(mActive)
                # merge all elements from the voice
                mNewInner.mergeElements(vInner)
                # insert in the appropriate part
                vId = vInner.id
                if not separateById:
                    pInner = partDict[vIndexInner]
                else:
                    seenIdsThisMeasure.add(vId)
                    pInner = partDict[vId]
                pInner.insert(self.elementOffset(mInner), mNewInner)

            # vIndexInner is now the number of voices - 1.  Fill empty voices
            if not separateById:
                for emptyIndex in range(vIndexInner + 1, partCount):
                    pInner = partDict[emptyIndex]
                    pInner.insert(self.elementOffset(mInner), copy.deepcopy(mActive))
            else:
                for voiceIdInner in partDict:
                    if voiceIdInner in seenIdsThisMeasure:
                        continue
                    pInner = partDict[voiceIdInner]
                    pInner.insert(self.elementOffset(mInner), copy.deepcopy(mActive))

        if self.hasMeasures():
            for m in self.iter.getElementsByClass('Measure'):
                if m.hasVoices():
                    doOneMeasureWithVoices(m)
                # if a measure does not have voices, simply populate
                # with elements and append
                else:
                    mNew = Measure()
                    mNew.mergeAttributes(m)  # get groups, optional id
                    # get all elements
                    mNew.mergeElements(m)
                    # always place in top-part
                    s.parts[0].insert(self.elementOffset(m), mNew)
                    for i in range(1, partCount):
                        mEmpty = Measure()
                        mEmpty.mergeAttributes(m)
                        # Propagate bar, meter, key elements to lower parts
                        mEmpty.mergeElements(m, classFilterList=('Barline',
                                            'TimeSignature', 'KeySignature'))
                        s.parts[i].insert(self.elementOffset(m), mEmpty)
        # if part has no measures but has voices, contents of each voice go into the part
        elif self.hasVoices():
            for vIndex, v in enumerate(self.voices):
                s.parts[vIndex].mergeElements(v)
        # if just a Stream of elements, add to a part
        else:
            s.parts[0].mergeElements(self)

        # there is no way to assure proper clef information, so using
        # best clef here is desirable.
        for p in s.parts:
            # only add clef if measures are defined; otherwise, assume
            # best clef will be assigned later
            if p.hasMeasures():
                # place in first measure
                p.getElementsByClass('Measure')[0].clef = clef.bestClef(p, recurse=True)
        return s

    def explode(self):
        '''
        Create a multi-part extraction from a single polyphonic Part.

        Currently just runs :meth:`~music21.stream.Stream.voicesToParts`
        but that will change as part explosion develops, and this
        method will use our best available quick method for part
        extraction.
        '''
        return self.voicesToParts()

    def flattenUnnecessaryVoices(self, *, force=False, inPlace=False):
        '''
        If this Stream defines one or more internal voices, do the following:

        * If there is more than one voice, and a voice has no elements,
          remove that voice.
        * If there is only one voice left that has elements, place those
          elements in the parent Stream.
        * If `force` is True, even if there is more than one Voice left,
          all voices will be flattened.

        Changed in v. 5 -- inPlace is default False and a keyword only arg.
        '''
        if not self.voices:
            return None  # do not make copy; return immediately

        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        # collect voices for removal and for flattening
        remove = []
        flatten = []
        for v in returnObj.voices:
            if not v:  # might add other criteria
                remove.append(v)
            else:
                flatten.append(v)

        for v in remove:
            returnObj.remove(v)

        if len(flatten) == 1 or force:  # always flatten 1
            for v in flatten:  # usually one unless force
                # get offset of voice in returnObj
                shiftOffset = v.getOffsetBySite(returnObj)
                for e in v.elements:
                    # insert shift + offset w/ voice
                    returnObj.coreInsert(shiftOffset + e.getOffsetBySite(v), e)
                returnObj.remove(v)

        returnObj.coreElementsChanged()
        if not inPlace:
            return returnObj
        else:
            return None

    # --------------------------------------------------------------------------
    # Lyric control
    # might be overwritten in base.splitAtDurations, but covered with a check
    # pylint: disable=method-hidden
    def lyrics(self, ignoreBarlines=True, recurse=False, skipTies=False):
        # noinspection PyShadowingNames
        '''
        Returns a dict of lists of lyric objects (with the keys being
        the lyric numbers) found in self. Each list will have an element for each
        note in the stream (which may be a note.Lyric() or None).
        By default, this method automatically
        recurses through measures, but not other container streams.


        >>> s = converter.parse('tinynotation: 4/4 a4 b c d   e f g a', makeNotation=False)
        >>> someLyrics = ['this', 'is', 'a', 'list', 'of', 'eight', 'lyric', 'words']
        >>> for n, lyric in zip(s.notes, someLyrics):
        ...     n.lyric = lyric


        >>> s.lyrics()
        {1: [<music21.note.Lyric number=1 syllabic=single text='this'>, ...,
             <music21.note.Lyric number=1 syllabic=single text='words'>]}

        >>> s.notes[3].lyric = None
        >>> s.lyrics()[1]
        [<music21.note.Lyric number=1 syllabic=single text='this'>, ..., None, ...,
         <music21.note.Lyric number=1 syllabic=single text='words'>]

        If ignoreBarlines is True, it will behave as if the elements in measures are all
        in a flattened stream (note that this is not stream.flat as it does not copy the elements)
        together without measure containers. This means that even if recurse is
        False, lyrics() will still essentially recurse through measures.

        >>> s.makeMeasures(inPlace=True)
        >>> s.lyrics()[1]
        [<music21.note.Lyric number=1 syllabic=single text='this'>, ..., None, ...,
         <music21.note.Lyric number=1 syllabic=single text='words'>]

        >>> list(s.lyrics(ignoreBarlines=False).keys())
        []

        If recurse is True, this method will recurse through all container streams and
        build a nested list structure mirroring the hierarchy of the stream.
        Note that if ignoreBarlines is True, measure structure will not be reflected
        in the hierarchy, although if ignoreBarlines is False, it will.

        Note that streams which do not contain any instance of a lyric number will not
        appear anywhere in the final list (not as a [] or otherwise).

        >>> p = stream.Part(s)
        >>> scr = stream.Score()
        >>> scr.append(p)

        >>> scr.lyrics(ignoreBarlines=False, recurse=True)[1]
        [[[<music21.note.Lyric number=1 syllabic=single text='this'>, <...'is'>, <...'a'>, None],
          [<...'of'>, <...'eight'>, <...'lyric'>, <...'words'>]]]

        Notice that the measures are nested in the part which is nested in the score.

        >>> scr.lyrics(ignoreBarlines=True, recurse=True)[1]
        [[<music21.note.Lyric number=1 syllabic=single text='this'>, <...'is'>, <...'a'>, None,
          <...'of'>, <...'eight'>, <...'lyric'>, <...'words'>]]

        Notice that this time, the measure structure is ignored.

        >>> list(scr.lyrics(ignoreBarlines=True, recurse=False).keys())
        []

        '''
        returnLists = {}
        numNotes = 0

        # --------------------

        # noinspection PyShadowingNames
        def appendLyricsFromNote(n, returnLists, numNonesToAppend):
            if not n.lyrics:
                for k in returnLists:
                    returnLists[k].append(None)
                return

            addLyricNums = []
            for ly in n.lyrics:
                if ly.number not in returnLists:
                    returnLists[ly.number] = [None for dummy in range(numNonesToAppend)]
                returnLists[ly.number].append(ly)
                addLyricNums.append(ly.number)
            for k in returnLists:
                if k not in addLyricNums:
                    returnLists[k].append(None)

        # -----------------------
        # TODO: use new recurse
        for e in self:
            eClasses = e.classes
            if ignoreBarlines is True and 'Measure' in eClasses:
                m = e
                for n in m.notes:
                    if skipTies is True:
                        if n.tie is None or n.tie.type == 'start':
                            appendLyricsFromNote(n, returnLists, numNotes)
                            numNotes += 1
                        else:
                            pass  # do nothing if end tie and skipTies is True
                    else:
                        appendLyricsFromNote(n, returnLists, numNotes)
                        numNotes += 1

            elif recurse is True and 'Stream' in eClasses:
                s = e
                sublists = s.lyrics(ignoreBarlines=ignoreBarlines, recurse=True, skipTies=skipTies)
                for k in sublists:
                    if k not in returnLists:
                        returnLists[k] = []
                    returnLists[k].append(sublists[k])
            elif 'NotRest' in eClasses:  # elif 'Stream' not in eClasses and hasattr(e, 'lyrics'):
                # noinspection PyTypeChecker
                n: 'music21.note.NotRest' = e
                if skipTies is True:
                    if n.tie is None or n.tie.type == 'start':
                        appendLyricsFromNote(n, returnLists, numNotes)
                        numNotes += 1
                    else:
                        pass  # do nothing if end tie and skipTies is True
                else:
                    appendLyricsFromNote(n, returnLists, numNotes)
                    numNotes += 1
            else:
                # e is a stream
                # (could be a measure if ignoreBarlines is False) and recurse is False
                pass  # do nothing

        return returnLists

    # --------------------------------------------------------------------------
    # Variant control
    @property
    def variants(self):
        '''
        Return a Stream containing all :class:`~music21.variant.Variant` objects in this Stream.

        >>> s = stream.Stream()
        >>> s.repeatAppend(note.Note('C4'), 8)
        >>> v1 = variant.Variant([note.Note('D#4'), note.Note('F#4')])
        >>> s.insert(3, v1)

        >>> varStream = s.variants
        >>> len(varStream)
        1
        >>> varStream[0] is v1
        True
        >>> len(s.variants[0])
        2

        Note that the D# and F# aren't found in the original Stream's pitches

        >>> [str(p) for p in s.pitches]
        ['C4', 'C4', 'C4', 'C4', 'C4', 'C4', 'C4', 'C4']
        '''
        if 'variants' not in self._cache or self._cache['variants'] is None:
            self._cache['variants'] = self.getElementsByClass('Variant').stream(
                returnStreamSubClass=False)
        return self._cache['variants']

    # ---- Variant Activation Methods

    def activateVariants(self, group=None, *, matchBySpan=True, inPlace=False):
        '''
        For any :class:`~music21.variant.Variant` objects defined in this Stream
        (or selected by matching the `group` parameter),
        replace elements defined in the Variant with those in the calling Stream.
        Elements replaced will be gathered into a new Variant
        given the group 'default'. If a variant is activated with
        .replacementDuration different from its length, the appropriate elements
        in the stream will have their offsets shifted, and measure numbering
        will be fixed. If matchBySpan is True, variants with lengthType
        'replacement' will replace all of the elements in the
        replacement region of comparable class. If matchBySpan is False,
        elements will be swapped in when a match is found between an element
        in the variant and an element in the replacement region of the string.

        >>> sStr   = 'd4 e4 f4 g4   a2 b-4 a4    g4 a8 g8 f4 e4    d2 a2              '
        >>> v1Str  = '              a2. b-8 a8 '
        >>> v2Str1 = '                                             d4 f4 a2 '
        >>> v2Str2 = '                                                      d4 f4 AA2 '

        >>> sStr += "d4 e4 f4 g4    a2 b-4 a4    g4 a8 b-8 c'4 c4    f1"

        >>> s = converter.parse('tinynotation: 4/4 ' + sStr, makeNotation=False)
        >>> s.makeMeasures(inPlace=True)  # maybe not necessary?
        >>> v1stream = converter.parse('tinynotation: 4/4 ' + v1Str, makeNotation=False)
        >>> v2stream1 = converter.parse('tinynotation: 4/4 ' + v2Str1, makeNotation=False)
        >>> v2stream2 = converter.parse('tinynotation: 4/4 ' + v2Str2, makeNotation=False)


        >>> v1 = variant.Variant()
        >>> v1measure = stream.Measure()
        >>> v1.insert(0.0, v1measure)
        >>> for e in v1stream.notesAndRests:
        ...    v1measure.insert(e.offset, e)

        >>> v2 = variant.Variant()
        >>> v2measure1 = stream.Measure()
        >>> v2measure2 = stream.Measure()
        >>> v2.insert(0.0, v2measure1)
        >>> v2.insert(4.0, v2measure2)
        >>> for e in v2stream1.notesAndRests:
        ...    v2measure1.insert(e.offset, e)
        >>> for e in v2stream2.notesAndRests:
        ...    v2measure2.insert(e.offset, e)

        >>> v3 = variant.Variant()
        >>> v2.replacementDuration = 4.0
        >>> v3.replacementDuration = 4.0
        >>> v1.groups = ['docVariants']
        >>> v2.groups = ['docVariants']
        >>> v3.groups = ['docVariants']

        >>> s.insert(4.0, v1)    # replacement variant
        >>> s.insert(12.0, v2)  # insertion variant (2 bars replace 1 bar)
        >>> s.insert(20.0, v3)  # deletion variant (0 bars replace 1 bar)
        >>> s.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            {0.0} <music21.meter.TimeSignature 4/4>
            {0.0} <music21.note.Note D>
            {1.0} <music21.note.Note E>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note G>
        {4.0} <music21.variant.Variant object of length 4.0>
        {4.0} <music21.stream.Measure 2 offset=4.0>
            {0.0} <music21.note.Note A>
            {2.0} <music21.note.Note B->
            {3.0} <music21.note.Note A>
        {8.0} <music21.stream.Measure 3 offset=8.0>
            {0.0} <music21.note.Note G>
            {1.0} <music21.note.Note A>
            {1.5} <music21.note.Note G>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note E>
        {12.0} <music21.variant.Variant object of length 8.0>
        {12.0} <music21.stream.Measure 4 offset=12.0>
            {0.0} <music21.note.Note D>
            {2.0} <music21.note.Note A>
        {16.0} <music21.stream.Measure 5 offset=16.0>
            {0.0} <music21.note.Note D>
            {1.0} <music21.note.Note E>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note G>
        {20.0} <music21.variant.Variant object of length 0.0>
        {20.0} <music21.stream.Measure 6 offset=20.0>
            {0.0} <music21.note.Note A>
            {2.0} <music21.note.Note B->
            {3.0} <music21.note.Note A>
        {24.0} <music21.stream.Measure 7 offset=24.0>
            {0.0} <music21.note.Note G>
            {1.0} <music21.note.Note A>
            {1.5} <music21.note.Note B->
            {2.0} <music21.note.Note C>
            {3.0} <music21.note.Note C>
        {28.0} <music21.stream.Measure 8 offset=28.0>
            {0.0} <music21.note.Note F>
            {4.0} <music21.bar.Barline type=final>

        >>> docVariant = s.activateVariants('docVariants')

        >>> #_DOCS_SHOW s.show()

        .. image:: images/stream_activateVariants1.*
            :width: 600

        >>> #_DOCS_SHOW docVariant.show()

        .. image:: images/stream_activateVariants2.*
            :width: 600

        >>> docVariant.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            {0.0} <music21.meter.TimeSignature 4/4>
            {0.0} <music21.note.Note D>
            {1.0} <music21.note.Note E>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note G>
        {4.0} <music21.variant.Variant object of length 4.0>
        {4.0} <music21.stream.Measure 2 offset=4.0>
            {0.0} <music21.note.Note A>
            {3.0} <music21.note.Note B->
            {3.5} <music21.note.Note A>
        {8.0} <music21.stream.Measure 3 offset=8.0>
            {0.0} <music21.note.Note G>
            {1.0} <music21.note.Note A>
            {1.5} <music21.note.Note G>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note E>
        {12.0} <music21.variant.Variant object of length 4.0>
        {12.0} <music21.stream.Measure 4 offset=12.0>
            {0.0} <music21.note.Note D>
            {1.0} <music21.note.Note F>
            {2.0} <music21.note.Note A>
        {16.0} <music21.stream.Measure 5 offset=16.0>
            {0.0} <music21.note.Note D>
            {1.0} <music21.note.Note F>
            {2.0} <music21.note.Note A>
        {20.0} <music21.stream.Measure 6 offset=20.0>
            {0.0} <music21.note.Note D>
            {1.0} <music21.note.Note E>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note G>
        {24.0} <music21.variant.Variant object of length 4.0>
        {24.0} <music21.stream.Measure 7 offset=24.0>
            {0.0} <music21.note.Note G>
            {1.0} <music21.note.Note A>
            {1.5} <music21.note.Note B->
            {2.0} <music21.note.Note C>
            {3.0} <music21.note.Note C>
        {28.0} <music21.stream.Measure 8 offset=28.0>
            {0.0} <music21.note.Note F>
            {4.0} <music21.bar.Barline type=final>

        After a variant group has been activated, the regions it replaced are
        stored as variants with the group 'default'.
        It should be noted that this means .activateVariants should rarely if
        ever be used on a stream which is returned
        by activateVariants because the group information is lost.

        >>> defaultVariant = docVariant.activateVariants('default')
        >>> #_DOCS_SHOW defaultVariant.show()

        .. image:: images/stream_activateVariants3.*
            :width: 600

        >>> defaultVariant.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            {0.0} <music21.meter.TimeSignature 4/4>
            {0.0} <music21.note.Note D>
            {1.0} <music21.note.Note E>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note G>
        {4.0} <music21.variant.Variant object of length 4.0>
        {4.0} <music21.stream.Measure 2 offset=4.0>
            {0.0} <music21.note.Note A>
            {2.0} <music21.note.Note B->
            {3.0} <music21.note.Note A>
        {8.0} <music21.stream.Measure 3 offset=8.0>
            {0.0} <music21.note.Note G>
            {1.0} <music21.note.Note A>
            {1.5} <music21.note.Note G>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note E>
        {12.0} <music21.variant.Variant object of length 8.0>
        {12.0} <music21.stream.Measure 4 offset=12.0>
            {0.0} <music21.note.Note D>
            {2.0} <music21.note.Note A>
        {16.0} <music21.stream.Measure 5 offset=16.0>
            {0.0} <music21.note.Note D>
            {1.0} <music21.note.Note E>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note G>
        {20.0} <music21.variant.Variant object of length 0.0>
        {20.0} <music21.stream.Measure 6 offset=20.0>
            {0.0} <music21.note.Note A>
            {2.0} <music21.note.Note B->
            {3.0} <music21.note.Note A>
        {24.0} <music21.stream.Measure 7 offset=24.0>
            {0.0} <music21.note.Note G>
            {1.0} <music21.note.Note A>
            {1.5} <music21.note.Note B->
            {2.0} <music21.note.Note C>
            {3.0} <music21.note.Note C>
        {28.0} <music21.stream.Measure 8 offset=28.0>
            {0.0} <music21.note.Note F>
            {4.0} <music21.bar.Barline type=final>
        '''
        if not inPlace:  # make a copy if inPlace is False
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        # Define Lists to cache variants
        elongationVariants = []
        deletionVariants = []

        # Loop through all variants, deal with replacement variants and
        # save insertion and deletion for later.
        for v in returnObj.variants:
            if group is not None:
                if group not in v.groups:
                    continue  # skip those that are not part of this group

            lengthType = v.lengthType

            # save insertions to perform last
            if lengthType == 'elongation':
                elongationVariants.append(v)
            # save deletions to perform after replacements
            elif lengthType == 'deletion':
                deletionVariants.append(v)
            # Deal with cases in which variant is the same length as what it replaces first.
            elif lengthType == 'replacement':
                returnObj._insertReplacementVariant(v, matchBySpan)

        # Now deal with deletions before insertion variants.
        # For keeping track of which measure numbers have been removed
        deletedMeasures = []
        # For keeping track of where new measures without measure numbers have been inserted,
        # will be a list of tuples (measureNumberPrior, [List, of, inserted, measures])
        insertedMeasures = []
        # For keeping track of the sections that are deleted
        # (so the offset gap can be closed later)
        deletedRegionsForRemoval = []
        for v in deletionVariants:
            (deletedRegion, vDeletedMeasures, vInsertedMeasuresTuple
             ) = returnObj._insertDeletionVariant(v, matchBySpan)  # deletes and inserts
            deletedRegionsForRemoval.append(deletedRegion)  # Saves the deleted region
            deletedMeasures.extend(vDeletedMeasures)  # Saves the deleted measure numbers
            # saves the inserted numberless measures (this will be empty unless there are
            # more bars in the variant than in the replacement region, which is unlikely
            # for a deletion variant.
            insertedMeasures.append(vInsertedMeasuresTuple)

        # Squeeze out the gaps that were saved.
        returnObj._removeOrExpandGaps(deletedRegionsForRemoval, isRemove=True, inPlace=True)

        # Before we can deal with insertions, we have to expand the stream to make space.
        insertionRegionsForExpansion = []  # For saving the insertion regions
        # go through all elongation variants to find the insertion regions.
        for v in elongationVariants:
            lengthDifference = v.replacementDuration - v.containedHighestTime
            insertionStart = v.getOffsetBySite(returnObj) + v.replacementDuration
            # Saves the information for each gap to be expanded
            insertionRegionsForExpansion.append((insertionStart, -1 * lengthDifference, [v]))

        # Expands the appropriate gaps in the stream.
        returnObj._removeOrExpandGaps(insertionRegionsForExpansion, isRemove=False, inPlace=True)
        # Now deal with elongation variants properly
        for v in elongationVariants:
            (vInsertedMeasuresTuple, vDeletedMeasures
             ) = returnObj._insertInsertionVariant(v, matchBySpan)  # deletes and inserts
            insertedMeasures.append(vInsertedMeasuresTuple)
            # Saves the numberless inserted measures
            # Saves deleted measures if any (it is unlikely that there will be unless
            # there are fewer measures in the variant than the replacement region,
            # which is unlikely for an elongation variant)
            deletedMeasures.extend(vDeletedMeasures)

        # Now fix measure numbers given the saved information
        returnObj._fixMeasureNumbers(deletedMeasures, insertedMeasures)

        # have to clear cached variants, as they are no longer the same
        returnObj.coreElementsChanged()
        if not inPlace:
            return returnObj
        else:
            return None

    def _insertReplacementVariant(self, v, matchBySpan=True):
        # noinspection PyShadowingNames
        '''
        Helper function for activateVariants. Activates variants which are the same size there the
        region they replace.


        >>> v = variant.Variant()
        >>> variantDataM1 = [('b', 'eighth'), ('c', 'eighth'),
        ...                  ('a', 'quarter'), ('a', 'quarter'),('b', 'quarter')]
        >>> variantDataM2 = [('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')]
        >>> variantData = [variantDataM1, variantDataM2]
        >>> for d in variantData:
        ...    m = stream.Measure()
        ...    for pitchName,durType in d:
        ...        n = note.Note(pitchName)
        ...        n.duration.type = durType
        ...        m.append(n)
        ...    v.append(m)
        >>> v.groups = ['paris']
        >>> v.replacementDuration = 8.0

        >>> s = stream.Stream()
        >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')]
        >>> streamDataM2 = [('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'),
        ...                 ('a', 'quarter'), ('b', 'quarter')]
        >>> streamDataM3 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')]
        >>> streamDataM4 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')]
        >>> streamData = [streamDataM1, streamDataM2, streamDataM3, streamDataM4]
        >>> for d in streamData:
        ...    m = stream.Measure()
        ...    for pitchName,durType in d:
        ...        n = note.Note(pitchName)
        ...        n.duration.type = durType
        ...        m.append(n)
        ...    s.append(m)
        >>> s.insert(4.0, v)

        >>> deletedMeasures, insertedMeasuresTuple = s._insertReplacementVariant(v)
        >>> deletedMeasures
        []
        >>> insertedMeasuresTuple
        (0, [])
        >>> s.show('text')
        {0.0} <music21.stream.Measure 0 offset=0.0>
            {0.0} <music21.note.Note A>
            {1.0} <music21.note.Note B>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note G>
        {4.0} <music21.variant.Variant object of length 8.0>
        {4.0} <music21.stream.Measure 0 offset=4.0>
            {0.0} <music21.note.Note B>
            {0.5} <music21.note.Note C>
            {1.0} <music21.note.Note A>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note B>
        {8.0} <music21.stream.Measure 0 offset=8.0>
            {0.0} <music21.note.Note C>
            {1.0} <music21.note.Note D>
            {2.0} <music21.note.Note E>
            {3.0} <music21.note.Note E>
        {12.0} <music21.stream.Measure 0 offset=12.0>
            {0.0} <music21.note.Note C>
            {1.0} <music21.note.Note B>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note A>
        '''
        from music21 import variant

        removed = variant.Variant()  # replacement variant
        removed.groups = ['default']  # for now, default
        vStart = self.elementOffset(v)
        # this method matches and removes on an individual basis
        if not matchBySpan:
            targetsMatched = 0
            for e in v.elements:  # get components in the Variant
                # get target offset relative to Stream
                oInStream = vStart + e.getOffsetBySite(v.containedSite)
                # get all elements at this offset, force a class match
                targets = self.iter.getElementsByOffset(oInStream).getElementsByClass(e.classes[0])
                # only replace if we match the start
                if targets:
                    targetsMatched += 1
                    # always assume we just want the first one?
                    targetToReplace = targets[0]
                    # environLocal.printDebug(['matchBySpan', matchBySpan,
                    #     'found target to replace:', targetToReplace])
                    # remove the target, place in removed Variant
                    removed.append(targetToReplace)
                    self.remove(targetToReplace)
                    # extract the variant component and insert into place
                    self.insert(oInStream, e)

                    if 'Measure' in targetToReplace.classes:
                        e.number = targetToReplace.number
            # only remove old and add removed if we matched
            if targetsMatched > 0:
                # remove the original variant
                self.remove(v)
                # place newly contained elements in position
                self.insert(vStart, removed)

        # matching by span means that we remove all elements with the
        # span defined by the variant
        else:
            deletedMeasures = []
            insertedMeasures = []
            highestNumber = None

            targets = v.replacedElements(self)

            # this will always remove elements before inserting
            for e in targets:
                # need to get time relative to variant container's position
                oInVariant = self.elementOffset(e) - vStart
                removed.insert(oInVariant, e)
                # environLocal.printDebug(
                #     ['matchBySpan', matchBySpan, 'activateVariants', 'removing', e])
                self.remove(e)
                if 'Measure' in e.classes:  # Save deleted measure numbers.
                    deletedMeasures.append(e.number)

            for e in v.elements:
                oInStream = vStart + e.getOffsetBySite(v.containedSite)
                self.insert(oInStream, e)
                if 'Measure' in e.classes:
                    if deletedMeasures:  # If there measure numbers left to use, use them.
                        # Assign the next highest deleted measure number
                        e.number = deletedMeasures.pop(False)
                        # Save the highest number used so far (for use in the case
                        # that there are extra measures with no numbers at the end)
                        highestNumber = e.number

                    else:
                        e.number = 0
                        # If no measure numbers left, add this
                        # numberless measure to insertedMeasures
                        insertedMeasures.append(e)
            # remove the source variant
            self.remove(v)
            # place newly contained elements in position
            self.insert(vStart, removed)

            # If deletedMeasures != [], then there were more deleted measures than
            # inserted and the remaining numbers in deletedMeasures are those that were removed.
            return deletedMeasures, (highestNumber, insertedMeasures)
            # In the case that the variant and stream are in the same time-signature,
            # this should return []

    def _insertDeletionVariant(self, v, matchBySpan=True):
        # noinspection PyShadowingNames
        '''
        Helper function for activateVariants used for inserting variants that are shorter than
        the region they replace. Inserts elements in the variant and deletes elements in the
        replaced region but does not close gaps.

        Returns a tuple describing the region where elements were removed, the
        gap is left behind to be dealt with by _removeOrExpandGaps.
        Tuple is of form (startOffset, lengthOfDeletedRegion, []). The empty list is
        expected by _removeOrExpandGaps
        and describes the list of elements which should be exempted from shifting
        for a particular gap. In the
        case of deletion, no elements need be exempted.

        >>> v = variant.Variant()
        >>> variantDataM1 = [('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'),
        ...                  ('a', 'quarter'),('b', 'quarter')]
        >>> variantDataM2 = [('c', 'quarter'), ('d', 'quarter'),
        ...                  ('e', 'quarter'), ('e', 'quarter')]
        >>> variantData = [variantDataM1, variantDataM2]
        >>> for d in variantData:
        ...    m = stream.Measure()
        ...    for pitchName,durType in d:
        ...        n = note.Note(pitchName)
        ...        n.duration.type = durType
        ...        m.append(n)
        ...    v.append(m)
        >>> v.groups = ['paris']
        >>> v.replacementDuration = 12.0

        >>> s = stream.Stream()
        >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')]
        >>> streamDataM2 = [('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'),
        ...                 ('a', 'quarter'), ('b', 'quarter')]
        >>> streamDataM3 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')]
        >>> streamDataM4 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')]
        >>> streamData = [streamDataM1, streamDataM2, streamDataM3, streamDataM4]
        >>> for d in streamData:
        ...    m = stream.Measure()
        ...    for pitchName,durType in d:
        ...        n = note.Note(pitchName)
        ...        n.duration.type = durType
        ...        m.append(n)
        ...    s.append(m)
        >>> s.insert(4.0, v)


        >>> deletedRegion, deletedMeasures, insertedMeasuresTuple = s._insertDeletionVariant(v)
        >>> deletedRegion
        (12.0, 4.0, [])
        >>> deletedMeasures
        [0]
        >>> insertedMeasuresTuple
        (0, [])
        >>> s.show('text')
        {0.0} <music21.stream.Measure 0 offset=0.0>
            {0.0} <music21.note.Note A>
            {1.0} <music21.note.Note B>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note G>
        {4.0} <music21.variant.Variant object of length 12.0>
        {4.0} <music21.stream.Measure 0 offset=4.0>
            {0.0} <music21.note.Note B>
            {0.5} <music21.note.Note C>
            {1.0} <music21.note.Note A>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note B>
        {8.0} <music21.stream.Measure 0 offset=8.0>
            {0.0} <music21.note.Note C>
            {1.0} <music21.note.Note D>
            {2.0} <music21.note.Note E>
            {3.0} <music21.note.Note E>

        '''
        from music21 import variant

        deletedMeasures = []  # For keeping track of what measure numbers are deleted
        # length of the deleted region
        lengthDifference = v.replacementDuration - v.containedHighestTime

        removed = variant.Variant()  # what group should this have?
        removed.groups = ['default']  # for now, default
        removed.replacementDuration = v.containedHighestTime

        vStart = self.elementOffset(v)
        deletionStart = vStart + v.containedHighestTime

        targets = v.replacedElements(self)

        # this will always remove elements before inserting
        for e in targets:
            if 'Measure' in e.classes:  # if a measure is deleted, save its number
                deletedMeasures.append(e.number)
            oInVariant = self.elementOffset(e) - vStart
            removed.insert(oInVariant, e)
            self.remove(e)

        # Next put in the elements from the variant
        highestNumber = None
        insertedMeasures = []
        for e in v.elements:
            if 'Measure' in e.classes:
                # If there are deleted numbers still saved, assign this measure the
                # next highest and remove it from the list.
                if deletedMeasures:
                    e.number = deletedMeasures.pop(False)
                    # Save the highest number assigned so far. If there are numberless
                    # inserted measures at the end, this will name where to begin numbering.
                    highestNumber = e.number
                else:
                    e.number = 0
                    # If there are no deleted numbers left (unlikely)
                    # save the inserted measures for renumbering later.
                    insertedMeasures.append(e)

            oInStream = vStart + e.getOffsetBySite(v.containedSite)
            self.insert(oInStream, e)

        # remove the source variant
        self.remove(v)
        # place newly contained elements in position
        self.insert(vStart, removed)

        # each variant leaves a gap, this saves the required information about those gaps
        # In most cases, inserted measures should be [].
        return (deletionStart, lengthDifference, []), deletedMeasures, (
            highestNumber, insertedMeasures)

    def _insertInsertionVariant(self, v, matchBySpan=True):
        # noinspection PyShadowingNames
        '''
        Helper function for activateVariants. _removeOrExpandGaps must be called on the
        expanded regions before this function
        or it will not work properly.


        >>> v = variant.Variant()
        >>> variantDataM1 = [('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'),
        ...                  ('a', 'quarter'),('b', 'quarter')]
        >>> variantDataM2 = [('c', 'quarter'), ('d', 'quarter'),
        ...                  ('e', 'quarter'), ('e', 'quarter')]
        >>> variantData = [variantDataM1, variantDataM2]
        >>> for d in variantData:
        ...    m = stream.Measure()
        ...    for pitchName,durType in d:
        ...        n = note.Note(pitchName)
        ...        n.duration.type = durType
        ...        m.append(n)
        ...    v.append(m)
        >>> v.groups = ['paris']
        >>> v.replacementDuration = 4.0

        >>> s = stream.Stream()
        >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')]
        >>> streamDataM2 = [('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'),
        ...                 ('a', 'quarter'), ('b', 'quarter')]
        >>> streamDataM3 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')]
        >>> streamDataM4 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')]
        >>> streamData = [streamDataM1, streamDataM2, streamDataM3, streamDataM4]
        >>> for d in streamData:
        ...    m = stream.Measure()
        ...    for pitchName,durType in d:
        ...        n = note.Note(pitchName)
        ...        n.duration.type = durType
        ...        m.append(n)
        ...    s.append(m)
        >>> s.insert(4.0, v)

        >>> insertionRegionsForExpansion = [(8.0, 4.0, [v])]
        >>> s._removeOrExpandGaps(insertionRegionsForExpansion, isRemove=False, inPlace=True)

        >>> insertedMeasuresTuple, deletedMeasures = s._insertInsertionVariant(v)
        >>> measurePrior, insertedMeasures = insertedMeasuresTuple
        >>> measurePrior
        0
        >>> len(insertedMeasures)
        1

        >>> s.show('text')
        {0.0} <music21.stream.Measure 0 offset=0.0>
            {0.0} <music21.note.Note A>
            {1.0} <music21.note.Note B>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note G>
        {4.0} <music21.variant.Variant object of length 4.0>
        {4.0} <music21.stream.Measure 0 offset=4.0>
            {0.0} <music21.note.Note B>
            {0.5} <music21.note.Note C>
            {1.0} <music21.note.Note A>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note B>
        {8.0} <music21.stream.Measure 0 offset=8.0>
            {0.0} <music21.note.Note C>
            {1.0} <music21.note.Note D>
            {2.0} <music21.note.Note E>
            {3.0} <music21.note.Note E>
        {12.0} <music21.stream.Measure 0 offset=12.0>
            {0.0} <music21.note.Note C>
            {1.0} <music21.note.Note B>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note A>
        {16.0} <music21.stream.Measure 0 offset=16.0>
            {0.0} <music21.note.Note C>
            {1.0} <music21.note.Note B>
            {2.0} <music21.note.Note A>
            {3.0} <music21.note.Note A>

        '''
        from music21 import variant

        deletedMeasures = []
        removed = variant.Variant()  # what group should this have?
        removed.groups = ['default']  # for now, default
        removed.replacementDuration = v.containedHighestTime
        vStart = self.elementOffset(v)

        # First deal with the elements in the overlapping section (limit by class)
        targets = v.replacedElements(self)

        # this will always remove elements before inserting
        for e in targets:
            if 'Measure' in e.classes:  # Save deleted measure numbers.
                deletedMeasures.append(e.number)
            oInVariant = self.elementOffset(e) - vStart
            removed.insert(oInVariant, e)
            self.remove(e)

        # Next put in the elements from the variant
        highestMeasure = None
        insertedMeasures = []
        for e in v.elements:
            if 'Measure' in e.classes:
                # If there are deleted measure numbers left, assign the next
                # inserted measure the next highest number and remove it.
                if deletedMeasures:
                    e.number = deletedMeasures.pop(False)
                    highestMeasure = e.number
                    # Save the highest number assigned so far
                    # so we know where to being numbering new measures.
                else:
                    e.number = 0
                    insertedMeasures.append(e)
                    # If there are no deleted measures, we have begun inserting as yet
                    # unnumbered measures, save which those are.
            oInStream = vStart + e.getOffsetBySite(v.containedSite)
            self.insert(oInStream, e)

        if highestMeasure is None:
            # If the highestMeasure is None (which will occur if the variant is
            # a strict insertion and replaces no measures,
            # we need to choose the highest measure number prior to the variant.
            measuresToCheck = self.getElementsByOffset(0.0,
                                                       self.elementOffset(v),
                                                       includeEndBoundary=True,
                                                       mustFinishInSpan=False,
                                                       mustBeginInSpan=True,
                                                       ).getElementsByClass('Measure')
            highestMeasure = 0
            for m in measuresToCheck:
                if highestMeasure is None or m.number > highestMeasure:
                    highestMeasure = m.number

        # remove the source variant
        self.remove(v)
        # place newly contained elements in position
        self.insert(vStart, removed)

        return (highestMeasure, insertedMeasures), deletedMeasures

    def _removeOrExpandGaps(self, listOffsetDurExemption,
                            isRemove=True, inPlace=False, exemptClasses=None):
        '''
        Helper for activateVariants. Takes a list of tuples in the form
        (startOffset, duration, [list, of, exempt, objects]). If isRemove is True,
        gaps with duration will be closed at each startOffset.
        Exempt objects are useful for gap-expansion with variants. The gap must push all objects
        that occur after the insertion ahead, but the variant object
        itself should not be moved except by other gaps. This is poorly written
        and should be re-written, but it is difficult to describe.


        >>> s = stream.Stream()
        >>> s.insert(5.0, note.Note('a'))
        >>> s.insert(10.0, note.Note('b'))
        >>> s.insert(11.0, note.Note('c'))
        >>> s.insert(12.0, note.Note('d'))
        >>> s.insert(13.0, note.Note('e'))
        >>> s.insert(20.0, note.Note('f'))
        >>> n = note.Note('g')
        >>> s.insert(15.0, n)

        >>> sGapsRemoved = s._removeOrExpandGaps(
        ...             [(0.0, 5.0, []), (6.0, 4.0, []), (14.0, 6.0, [n])], isRemove=True)
        >>> sGapsRemoved.show('text')
        {0.0} <music21.note.Note A>
        {1.0} <music21.note.Note B>
        {2.0} <music21.note.Note C>
        {3.0} <music21.note.Note D>
        {4.0} <music21.note.Note E>
        {5.0} <music21.note.Note F>
        {15.0} <music21.note.Note G>

        >>> sGapsExpanded = s._removeOrExpandGaps(
        ...            [(0.0, 5.0, []), (11.0, 5.0, []), (14.0, 1.0, [n])], isRemove=False)
        >>> sGapsExpanded.show('text')
        {10.0} <music21.note.Note A>
        {15.0} <music21.note.Note B>
        {21.0} <music21.note.Note C>
        {22.0} <music21.note.Note D>
        {23.0} <music21.note.Note E>
        {25.0} <music21.note.Note G>
        {31.0} <music21.note.Note F>
        '''
        if inPlace is True:
            returnObj = self
        else:
            returnObj = copy.deepcopy(self)

        returnObjDuration = returnObj.duration.quarterLength

        # If any classes should be exempt from gap closing or expanding, this deals with those.
        classList = []
        if exemptClasses is None:
            classList = None
        else:
            for e in returnObj.elements:
                if type(e) not in classList:  # pylint: disable=unidiomatic-typecheck
                    classList.append(type(e))
            for c in exemptClasses:
                if c in classList:
                    classList.remove(c)

        if isRemove is True:
            shiftDur = 0.0
            listSorted = sorted(listOffsetDurExemption, key=lambda target: target[0])
            for i, durTuple in enumerate(listSorted):
                startOffset, durationAmount, exemptObjects = durTuple
                if i + 1 < len(listSorted):
                    endOffset = listSorted[i + 1][0]
                    includeEnd = False
                else:
                    endOffset = returnObjDuration
                    includeEnd = True

                shiftDur = shiftDur + durationAmount
                for e in returnObj.getElementsByOffset(startOffset + durationAmount,
                                                       endOffset,
                                                       includeEndBoundary=includeEnd,
                                                       mustFinishInSpan=False,
                                                       mustBeginInSpan=True,
                                                       classList=classList):

                    if e in exemptObjects:
                        continue

                    elementOffset = e.getOffsetBySite(returnObj)
                    returnObj.setElementOffset(e, elementOffset - shiftDur)
        else:
            shiftDur = 0.0
            shiftsDict = {}
            listSorted = sorted(listOffsetDurExemption, key=lambda target: target[0])
            for i, durTuple in enumerate(listSorted):
                startOffset, durationAmount, exemptObjects = durTuple

                if i + 1 < len(listSorted):
                    endOffset = listSorted[i + 1][0]
                    includeEnd = False
                else:
                    endOffset = returnObjDuration
                    includeEnd = True

                exemptShift = shiftDur
                shiftDur = shiftDur + durationAmount
                shiftsDict[startOffset] = (shiftDur, endOffset, includeEnd,
                                           exemptObjects, exemptShift)

            for offset in sorted(shiftsDict, key=lambda off: -1 * off):
                shiftDur, endOffset, includeEnd, exemptObjects, exemptShift = shiftsDict[offset]
                for e in returnObj.getElementsByOffset(offset,
                                                       endOffset,
                                                       includeEndBoundary=includeEnd,
                                                       mustFinishInSpan=False,
                                                       mustBeginInSpan=True,
                                                       classList=classList):

                    if e in exemptObjects:
                        elementOffset = e.getOffsetBySite(returnObj)
                        returnObj.setElementOffset(e, elementOffset + exemptShift)
                        continue

                    elementOffset = e.getOffsetBySite(returnObj)
                    returnObj.setElementOffset(e, elementOffset + shiftDur)

        if inPlace is True:
            return
        else:
            return returnObj

    def _fixMeasureNumbers(self, deletedMeasures, insertedMeasures):
        # noinspection PyShadowingNames
        '''
        Corrects the measures numbers of a string of measures given a list of measure numbers
        that have been deleted and a
        list of tuples (highest measure number below insertion, number of inserted measures).

        >>> s = converter.parse('tinynotation: 4/4 d4 e4 f4 g4   a2 b-4 a4    g4 a8 g8 f4 e4  g1')
        >>> s[-1].offset = 20.0
        >>> s.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            {0.0} <music21.meter.TimeSignature 4/4>
            {0.0} <music21.note.Note D>
            {1.0} <music21.note.Note E>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note G>
        {4.0} <music21.stream.Measure 2 offset=4.0>
            {0.0} <music21.note.Note A>
            {2.0} <music21.note.Note B->
            {3.0} <music21.note.Note A>
        {8.0} <music21.stream.Measure 3 offset=8.0>
            {0.0} <music21.note.Note G>
            {1.0} <music21.note.Note A>
            {1.5} <music21.note.Note G>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note E>
        {20.0} <music21.stream.Measure 4 offset=20.0>
            {0.0} <music21.note.Note G>
            {4.0} <music21.bar.Barline type=final>
        >>> s.remove(s.measure(2))
        >>> s.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            ...
        {8.0} <music21.stream.Measure 3 offset=8.0>
            {0.0} <music21.note.Note G>
            ...
        {20.0} <music21.stream.Measure 4 offset=20.0>
            {0.0} <music21.note.Note G>
            {4.0} <music21.bar.Barline type=final>
        >>> deletedMeasures = [2]
        >>> m1 = stream.Measure()
        >>> m1.repeatAppend(note.Note('e'),4)
        >>> s.insert(12.0, m1)
        >>> m2 = stream.Measure()
        >>> m2.repeatAppend(note.Note('f'),4)
        >>> s.insert(16.0, m2)
        >>> s.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            ...
        {8.0} <music21.stream.Measure 3 offset=8.0>
            {0.0} <music21.note.Note G>
            ...
        {12.0} <music21.stream.Measure 0 offset=12.0>
            {0.0} <music21.note.Note E>
            {1.0} <music21.note.Note E>
            {2.0} <music21.note.Note E>
            {3.0} <music21.note.Note E>
        {16.0} <music21.stream.Measure 0 offset=16.0>
            {0.0} <music21.note.Note F>
            {1.0} <music21.note.Note F>
            {2.0} <music21.note.Note F>
            {3.0} <music21.note.Note F>
        {20.0} <music21.stream.Measure 4 offset=20.0>
            {0.0} <music21.note.Note G>
            {4.0} <music21.bar.Barline type=final>
        >>> insertedMeasures = [(3, [m1, m2])]

        >>> s._fixMeasureNumbers(deletedMeasures, insertedMeasures)
        >>> s.show('text')
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            {0.0} <music21.meter.TimeSignature 4/4>
            ...
        {8.0} <music21.stream.Measure 2 offset=8.0>
            {0.0} <music21.note.Note G>
            ...
        {12.0} <music21.stream.Measure 3 offset=12.0>
            {0.0} <music21.note.Note E>
            ...
        {16.0} <music21.stream.Measure 4 offset=16.0>
            {0.0} <music21.note.Note F>
            ...
        {20.0} <music21.stream.Measure 5 offset=20.0>
            {0.0} <music21.note.Note G>
            {4.0} <music21.bar.Barline type=final>
        >>> fixedNumbers = []
        >>> for m in s.getElementsByClass('Measure'):
        ...    fixedNumbers.append( m.number )
        >>> fixedNumbers
        [1, 2, 3, 4, 5]

        '''
        deletedMeasures.extend(insertedMeasures)
        allMeasures = deletedMeasures

        if not allMeasures:
            return

        def measureNumberSortRoutine(numOrNumTuple):
            if isinstance(numOrNumTuple, tuple):
                return measureNumberSortRoutine(numOrNumTuple[0])
            elif numOrNumTuple is None:
                return -9999
            else:
                return numOrNumTuple

        allMeasures.sort(key=measureNumberSortRoutine)

        oldMeasures = self.getElementsByClass('Measure').stream()
        newMeasures = []

        cumulativeNumberShift = 0
        oldCorrections = {}
        newCorrections = {}
        # the inserted measures must be treated differently than the original measures.
        # an inserted measure should not shift itself,
        # but it should shift measures with the same number.
        # However, inserted measures should still be shifted by every other correction.

        # First collect dictionaries of shift boundaries and the amount of the shift.
        # at the same time, five un-numbered measures numbers that make sense.
        for measureNumber in allMeasures:
            if isinstance(measureNumber, tuple):  # tuple implies insertion
                measurePrior, extendedMeasures = measureNumber
                if not extendedMeasures:  # No measures were added, therefore no shift.
                    continue
                cumulativeNumberShift += len(extendedMeasures)
                nextMeasure = measurePrior + 1
                for m in extendedMeasures:
                    oldMeasures.remove(m)
                    newMeasures.append(m)
                    m.number = nextMeasure
                    nextMeasure += 1
                oldCorrections[measurePrior + 1] = cumulativeNumberShift
                newCorrections[nextMeasure] = cumulativeNumberShift
            else:  # integer implies deletion
                cumulativeNumberShift -= 1
                oldCorrections[measureNumber + 1] = cumulativeNumberShift
                newCorrections[measureNumber + 1] = cumulativeNumberShift

        # Second, make corrections based on the dictionaries. The key is the measure number
        # above which measures should be shifted by the value up to the next key. It is easiest
        # to do this in reverse order so there is no overlapping.
        previousBoundary = None
        for k in sorted(oldCorrections, key=lambda x: -1 * x):
            shift = oldCorrections[k]
            for m in oldMeasures:
                if previousBoundary is None or m.number < previousBoundary:
                    if m.number >= k:
                        m.number = m.number + shift
            previousBoundary = k

        previousBoundary = None
        for k in sorted(newCorrections, key=lambda x: -1 * x):
            shift = newCorrections[k]
            for m in newMeasures:
                if previousBoundary is None or m.number < previousBoundary:
                    if m.number >= k:
                        m.number = m.number + shift
            previousBoundary = k

    def showVariantAsOssialikePart(self, containedPart, variantGroups, *, inPlace=False):
        # noinspection PyShadowingNames
        '''
        Takes a part within the score and a list of variant groups within that part.
        Puts the variant object
        in a part surrounded by hidden rests to mimic the appearance of an ossia despite limited
        musicXML support for ossia staves. Note that this will ignore variants with .lengthType
        'elongation' and 'deletion' as there is no good way to represent ossia staves like those
        by this method.

        >>> sPartStr = 'd4 e4 f4 g4   a2 b-4 a4    g4 a8 g8 f4 e4    d2 a2 '
        >>> v1Str =    '              a2. b-8 a8 '
        >>> v2Str =    '                                             d4 f4 a2 '

        >>> sPartStr += "d4 e4 f4 g4    a2 b-4 a4    g4 a8 b-8 c'4 c4    f1"

        >>> sPartStream = converter.parse('tinynotation: 4/4 ' + sPartStr)
        >>> sPartStream.makeMeasures(inPlace=True)  # maybe not necessary?
        >>> v1stream = converter.parse('tinynotation: 4/4 ' + v1Str)
        >>> v2stream = converter.parse('tinynotation: 4/4 ' + v2Str)

        >>> v1 = variant.Variant()
        >>> v1measure = stream.Measure()
        >>> v1.insert(0.0, v1measure)
        >>> for e in v1stream.notesAndRests:
        ...    v1measure.insert(e.offset, e)

        >>> v2 = variant.Variant()
        >>> v2measure = stream.Measure()
        >>> v2.insert(0.0, v2measure)
        >>> for e in v2stream.notesAndRests:
        ...    v2measure.insert(e.offset, e)

        >>> v3 = variant.Variant()
        >>> v2.replacementDuration = 4.0
        >>> v3.replacementDuration = 4.0
        >>> v1.groups = ['variant1']
        >>> v2.groups = ['variant2']
        >>> v3.groups = ['variant3']

        >>> sPart = stream.Part()
        >>> for e in sPartStream:
        ...    sPart.insert(e.offset, e)

        >>> sPart.insert(4.0, v1)
        >>> sPart.insert(12.0, v2)
        >>> sPart.insert(20.0, v3)  # This is a deletion variant and will be skipped
        >>> s = stream.Score()
        >>> s.insert(0.0, sPart)
        >>> streamWithOssia = s.showVariantAsOssialikePart(sPart,
        ...          ['variant1', 'variant2', 'variant3'], inPlace=False)
        >>> #_DOCS_SHOW streamWithOssia.show()

        '''
        from music21 import variant

        # containedPart must be in self, or an exception is raised.
        if not (containedPart in self):
            raise variant.VariantException(f'Could not find {containedPart} in {self}')

        if inPlace is True:
            returnObj = self
            returnPart = containedPart
        else:
            returnObj = copy.deepcopy(self)
            containedPartIndex = self.parts.stream().index(containedPart)
            returnPart = returnObj.parts[containedPartIndex]

        # First build a new part object that is the same length as returnPart
        # but entirely hidden rests.
        # This is done by copying the part and removing unnecessary objects
        # including irrelevant variants
        # but saving relevant variants.
        for variantGroup in variantGroups:
            newPart = copy.deepcopy(returnPart)
            expressedVariantsExist = False
            for e in newPart.elements:
                eClasses = e.classes
                if 'Variant' in eClasses:
                    elementGroups = e.groups
                    if (not(variantGroup in elementGroups)
                            or e.lengthType in ['elongation', 'deletion']):
                        newPart.remove(e)
                    else:
                        expressedVariantsExist = True
                elif 'GeneralNote' in eClasses:
                    nQuarterLength = e.duration.quarterLength
                    nOffset = e.getOffsetBySite(newPart)
                    newPart.remove(e)
                    r = note.Rest()
                    r.style.hideObjectOnPrint = True
                    r.duration.quarterLength = nQuarterLength
                    newPart.insert(nOffset, r)
                elif 'Measure' in eClasses:  # Recurse if measure
                    measureDuration = e.duration.quarterLength
                    for n in e.notesAndRests:
                        e.remove(n)
                    r = note.Rest()
                    r.duration.quarterLength = measureDuration
                    r.style.hideObjectOnPrint = True
                    e.insert(0.0, r)

                e.style.hideObjectOnPrint = True

            newPart.activateVariants(variantGroup, inPlace=True, matchBySpan=True)
            if expressedVariantsExist:
                returnObj.insert(0.0, newPart)

        if inPlace:
            return
        else:
            return returnObj

    def makeVariantBlocks(self):
        '''
        from music21 import *
        '''
        variantsToBeDone = self.variants.elements

        for v in variantsToBeDone:
            startOffset = self.elementOffset(v)
            endOffset = v.replacementDuration + startOffset
            conflictingVariants = self.getElementsByOffset(offsetStart=startOffset,
                                                           offsetEnd=endOffset,
                                                           includeEndBoundary=False,
                                                           mustFinishInSpan=False,
                                                           mustBeginInSpan=True,
                                                           classList=['Variant'])
            for cV in conflictingVariants:
                oldReplacementDuration = cV.replacementDuration
                if self.elementOffset(cV) == startOffset:
                    continue  # do nothing
                else:
                    shiftOffset = self.elementOffset(cV) - startOffset
                    r = note.SpacerRest()
                    r.duration.quarterLength = shiftOffset
                    r.style.hideObjectOnPrint = True
                    for el in cV._stream:
                        oldOffset = el.getOffsetBySite(cV._stream)
                        cV._stream.setElementOffset(el, oldOffset + shiftOffset)
                    cV.insert(0.0, r)
                    cV.replacementDuration = oldReplacementDuration
                    self.remove(cV)
                    self.insert(startOffset, cV)
                    variantsToBeDone.append(cV)


# -----------------------------------------------------------------------------


class Voice(Stream):
    '''
    A Stream subclass for declaring that all the music in the
    stream belongs to a certain "voice" for analysis or display
    purposes.

    Note that both Finale's Layers and Voices as concepts are
    considered Voices here.

    Voices have a sort order of 1 greater than time signatures
    '''
    recursionType = 'elementsFirst'
    classSortOrder = 5


# -----------------------------------------------------------------------------


class Measure(Stream):
    '''
    A representation of a Measure organized as a Stream.

    All properties of a Measure that are Music21 objects are found as part of
    the Stream's elements.

    Measure number can be explicitly set with the `number` keyword:

    >>> m4 = stream.Measure(number=4)
    >>> m4
    <music21.stream.Measure 4 offset=0.0>
    >>> m4.number
    4

    If passed a single integer as an argument, assumes that this int
    is the measure number.

    >>> m5 = stream.Measure(5)
    >>> m5
    <music21.stream.Measure 5 offset=0.0>

    Number can also be a string if there is a suffix:

    >>> m4 = stream.Measure(number='4a')
    >>> m4
    <music21.stream.Measure 4a offset=0.0>
    >>> m4.numberSuffix
    'a'

    Though they have all the features of general streams,
    Measures have specific attributes that allow for setting their number
    and numberSuffix, keep track of whether they have a different clef or
    key or timeSignature than previous measures, allow for padding (and pickups),
    and can be found as a "measure slice" within a score and parts.
    '''
    recursionType = 'elementsFirst'
    isMeasure = True

    # define order to present names in documentation; use strings
    _DOC_ORDER = ['']
    # documentation for all attributes (not properties or methods)
    _DOC_ATTR = {
        'timeSignatureIsNew': '''
            Boolean describing if the TimeSignature
            is different than the previous Measure.''',
        'clefIsNew': 'Boolean describing if the Clef is different than the previous Measure.',
        'keyIsNew': 'Boolean describing if KeySignature is different than the previous Measure.',
        'number': '''
            A number representing the displayed or shown
            Measure number as presented in a written Score.''',
        'numberSuffix': '''
            If a Measure number has a string annotation, such as "a" or similar,
            this string is stored here. Note that in MusicXML, such
            suffixes often appear as
            prefixes to measure numbers.  In music21 (like most measure
            numbering systems), these
            numbers appear as suffixes.''',
        'layoutWidth': '''
            A suggestion for layout width, though most rendering systems do not support
            this designation. Use :class:`~music21.layout.SystemLayout`
            objects instead.''',
        'paddingLeft': '''
            defines empty space at the front of the measure for purposes of determining
            beat, etc for pickup/anacrusis bars.  In 4/4, a
            measure with a one-beat pickup
            note will have a `paddingLeft` of 3.0.
            (The name comes from the CSS graphical term
            for the amount of padding on the left side of a region.)''',
        'paddingRight': '''
            defines empty space at the end of the measure for purposes of determining
            whether or not a measure is filled.
            In 4/4, a piece beginning a one-beat pickup
            note will often have a final measure of three beats, instead of four.
            The final
            measure should have a `paddingRight` of 1.0.
            (The name comes from the CSS graphical term
            for the amount of padding on the right side of a region.)''',
    }

    def __init__(self, *args, **keywords):
        if len(args) == 1 and isinstance(args[0], int) and 'number' not in keywords:
            keywords['number'] = args[0]
            args = ()

        super().__init__(*args, **keywords)

        # clef and timeSignature is defined as a property below
        self.timeSignatureIsNew = False
        self.clefIsNew = False
        self.keyIsNew = False

        self.filled = False

        # padding: defining a context for offsets contained within this Measure
        # padding defines dead regions of offsets into the measure
        # the paddingLeft is used by TimeSignature objects to determine beat
        # position; paddingRight defines a QL from the end of the time signature
        # to the last valid offset
        # paddingLeft is used to define pickup/anacrusis bars
        self.paddingLeft = 0
        self.paddingRight = 0

        self.numberSuffix = None  # for measure 14a would be 'a'
        if 'number' in keywords:
            num = keywords['number']
            if isinstance(num, str):
                realNum, suffix = common.getNumFromStr(num)
                self.number = int(realNum)
                if suffix:
                    self.numberSuffix = suffix
            else:
                self.number = keywords['number']
        else:
            self.number = 0  # 0 means undefined or pickup
        # we can request layout width, using the same units used
        # in layout.py for systems; most musicxml readers do not support this
        # on input
        self.layoutWidth = None

    # def addRepeat(self):
    #     # TODO: write
    #     pass

    # def addTimeDependentDirection(self, time, direction):
    #     # TODO: write
    #     pass

    def measureNumberWithSuffix(self):
        '''
        Return the measure `.number` with the `.numberSuffix` as a string.

        >>> m = stream.Measure()
        >>> m.number = 4
        >>> m.numberSuffix = 'A'
        >>> m.measureNumberWithSuffix()
        '4A'

        Test that it works as musicxml

        >>> xml = musicxml.m21ToXml.GeneralObjectExporter().parse(m)
        >>> print(xml.decode('utf-8'))
        <?xml version="1.0"...?>
        ...
        <part id="...">
            <!--========================= Measure 4 ==========================-->
            <measure number="4A">
        ...

        Test round tripping:

        >>> s2 = converter.parseData(xml)
        >>> print(s2.semiFlat.getElementsByClass('Measure')[0].measureNumberWithSuffix())
        4A

        Note that we use print here because in parsing the data will become a unicode string.
        '''
        if self.numberSuffix:
            return str(self.number) + self.numberSuffix
        else:
            return str(self.number)

    def _reprInternal(self):
        return self.measureNumberWithSuffix() + f' offset={self.offset}'

    # -------------------------------------------------------------------------
    def mergeAttributes(self, other):
        '''
        Given another Measure, configure all non-element attributes of this
        Measure with the attributes of the other Measure. No elements
        will be changed or copied.

        This method is necessary because Measures, unlike some Streams,
        have attributes independent of any stored elements.

        Overrides base.Music21Object.mergeAttributes

        >>> m1 = stream.Measure()
        >>> m1.id = 'MyMeasure'
        >>> m1.clefIsNew = True
        >>> m1.number = 2
        >>> m1.numberSuffix = 'b'
        >>> m1.layoutWidth = 200

        >>> m2 = stream.Measure()
        >>> m2.mergeAttributes(m1)
        >>> m2.layoutWidth
        200
        >>> m2.id
        'MyMeasure'
        >>> m2
        <music21.stream.Measure 2b offset=0.0>

        Try with not another Measure...

        >>> m3 = stream.Stream()
        >>> m3.id = 'hello'
        >>> m2.mergeAttributes(m3)
        >>> m2.id
        'hello'
        >>> m2.layoutWidth
        200
        '''
        # calling bass class sets id, groups
        super().mergeAttributes(other)

        for attr in ('timeSignatureIsNew', 'clefIsNew', 'keyIsNew', 'filled',
                     'paddingLeft', 'paddingRight', 'number', 'numberSuffix', 'layoutWidth'):
            if hasattr(other, attr):
                setattr(self, attr, getattr(other, attr))

    # -------------------------------------------------------------------------
    def makeNotation(self,
                     inPlace=False,
                     **subroutineKeywords):
        # noinspection PyShadowingNames
        '''
        This method calls a sequence of Stream methods on this
        :class:`~music21.stream.Measure` to prepare notation.

        If `inPlace` is True, this is done in-place; if
        `inPlace` is False, this returns a modified deep copy.

        >>> m = stream.Measure()
        >>> n1 = note.Note('g#')
        >>> n2 = note.Note('g')
        >>> m.append([n1, n2])
        >>> m.makeNotation(inPlace=True)
        >>> m.notes[1].pitch.accidental
        <accidental natural>
        '''
        # environLocal.printDebug(['Measure.makeNotation'])
        # TODO: this probably needs to look to see what processes need to be done;
        # for example, existing beaming may be destroyed.

        # do this before deepcopy...

        # assuming we are not trying to get context of previous measure
        if not inPlace:  # make a copy
            m = copy.deepcopy(self)
        else:
            m = self

        srkCopy = subroutineKeywords.copy()

        for illegalKey in ('meterStream', 'refStreamOrTimeRange', 'bestClef'):
            if illegalKey in srkCopy:
                del(srkCopy[illegalKey])

        m.makeAccidentals(searchKeySignatureByContext=True, inPlace=True, **srkCopy)
        # makeTies is for cross-bar associations, and cannot be used
        # at just the measure level
        # m.makeTies(meterStream, inPlace=True)

        # must have a time signature before calling make beams
        if m.timeSignature is None:
            # get a time signature if not defined, searching the context if
            # necessary
            contextMeters = m.getTimeSignatures(searchContext=True,
                                                returnDefault=False)
            defaultMeters = m.getTimeSignatures(searchContext=False,
                                                returnDefault=True)
            if contextMeters:
                ts = contextMeters[0]
            else:
                try:
                    ts = self.bestTimeSignature()
                except (StreamException, meter.MeterException):
                    # there must be one here
                    ts = defaultMeters[0]
            m.timeSignature = ts  # a Stream; get the first element

        # environLocal.printDebug(['have time signature', m.timeSignature])
        if m.streamStatus.beams is False:
            try:
                m.makeBeams(inPlace=True)
            except StreamException:
                # this is a result of makeMeasures not getting everything
                # note to measure allocation right
                pass
                # environLocal.printDebug(['skipping makeBeams exception', StreamException])
        if m.streamStatus.haveTupletBracketsBeenMade() is False:
            makeNotation.makeTupletBrackets(m, inPlace=True)

        if not inPlace:
            return m
        else:
            return None

    def barDurationProportion(self, barDuration=None):
        '''
        Return a floating point value greater than 0 showing the proportion
        of the bar duration that is filled based on the highest time of
        all elements. 0.0 is empty, 1.0 is filled; 1.5 specifies of an
        overflow of half.

        Bar duration refers to the duration of the Measure as suggested by
        the `TimeSignature`. This value cannot be determined without a `TimeSignature`.

        An already-obtained Duration object can be supplied with the `barDuration`
        optional argument.

        >>> import copy
        >>> m = stream.Measure()
        >>> m.timeSignature = meter.TimeSignature('3/4')
        >>> n = note.Note()
        >>> n.quarterLength = 1
        >>> m.append(copy.deepcopy(n))
        >>> m.barDurationProportion()
        Fraction(1, 3)
        >>> m.append(copy.deepcopy(n))
        >>> m.barDurationProportion()
        Fraction(2, 3)
        >>> m.append(copy.deepcopy(n))
        >>> m.barDurationProportion()
        1.0
        >>> m.append(copy.deepcopy(n))
        >>> m.barDurationProportion()
        Fraction(4, 3)
        '''
        # passing a barDuration may save time in the lookup process
        if barDuration is None:
            barDuration = self.barDuration
        return opFrac(self.highestTime / barDuration.quarterLength)

    def padAsAnacrusis(self, useGaps=True, useInitialRests=False):
        '''
        Given an incompletely filled Measure, adjust the `paddingLeft` value to to
        represent contained events as shifted to fill the right-most duration of the bar.

        Calling this method will overwrite any previously set `paddingLeft` value,
        based on the current TimeSignature-derived `barDuration` attribute.

        >>> m = stream.Measure()
        >>> m.timeSignature = meter.TimeSignature('3/4')
        >>> n = note.Note()
        >>> n.quarterLength = 1.0
        >>> m.append(n)
        >>> m.padAsAnacrusis()
        >>> m.paddingLeft
        2.0

        >>> m.timeSignature = meter.TimeSignature('5/4')
        >>> m.padAsAnacrusis()
        >>> m.paddingLeft
        4.0


        Empty space at the beginning of the measure will not be taken in account:

        >>> m = stream.Measure()
        >>> m.timeSignature = meter.TimeSignature('3/4')
        >>> n = note.Note(type='quarter')
        >>> m.insert(2.0, n)
        >>> m.padAsAnacrusis()
        >>> m.paddingLeft
        0

        If useInitialRests is True, then rests at the beginning of the measure
        are removed.  This is especially useful for formats that don't give a
        way to specify a pickup measure (such as tinynotation) or software
        that generates incorrect opening measures.  So, to fix the problem before,
        put a rest at the beginning and call useInitialRests:

        >>> r = note.Rest(type='half')
        >>> m.insert(0, r)
        >>> m.padAsAnacrusis(useInitialRests=True)
        >>> m.paddingLeft
        2.0

        And the rest is gone!

        >>> m.show('text')
        {0.0} <music21.meter.TimeSignature 3/4>
        {0.0} <music21.note.Note C>


        Only initial rests count for useInitialRests:

        >>> m = stream.Measure()
        >>> m.timeSignature = meter.TimeSignature('3/4')
        >>> m.append(note.Rest(type='eighth'))
        >>> m.append(note.Rest(type='eighth'))
        >>> m.append(note.Note('C4', type='quarter'))
        >>> m.append(note.Rest(type='eighth'))
        >>> m.append(note.Note('D4', type='eighth'))
        >>> m.show('text')
        {0.0} <music21.meter.TimeSignature 3/4>
        {0.0} <music21.note.Rest rest>
        {0.5} <music21.note.Rest rest>
        {1.0} <music21.note.Note C>
        {2.0} <music21.note.Rest rest>
        {2.5} <music21.note.Note D>
        >>> m.padAsAnacrusis(useInitialRests=True)
        >>> m.paddingLeft
        1.0
        >>> m.show('text')
        {0.0} <music21.meter.TimeSignature 3/4>
        {0.0} <music21.note.Note C>
        {1.0} <music21.note.Rest rest>
        {1.5} <music21.note.Note D>
        '''
        if useInitialRests:
            removeList = []
            for gn in self.getElementsByClass('GeneralNote'):
                if 'Rest' not in gn.classes:
                    break
                removeList.append(gn)
            if removeList:
                self.remove(removeList, shiftOffsets=True)

        # note: may need to set paddingLeft to 0 before examining

        # bar duration is that suggested by time signature; it may
        # may not be the same as Stream duration, which is based on contents
        barDuration = self.barDuration
        proportion = self.barDurationProportion(barDuration=barDuration)
        if proportion < 1:
            # get 1 complement
            proportionShift = 1 - proportion
            self.paddingLeft = opFrac(barDuration.quarterLength * proportionShift)

            # shift = barDuration.quarterLength * proportionShift
            # environLocal.printDebug(['got anacrusis shift:', shift,
            #                    barDuration.quarterLength, proportion])
            # this will shift all elements
            # self.shiftElements(shift, classFilterList=(note.GeneralNote,))
        else:
            pass
            # environLocal.printDebug(['padAsAnacrusis() called; however,
            # no anacrusis shift necessary:', barDuration.quarterLength, proportion])

    # --------------------------------------------------------------------------
    @property
    def barDuration(self):
        '''
        Return the bar duration, or the Duration specified by the TimeSignature,
        regardless of what elements are found in this Measure or the highest time.
        TimeSignature is found first within the Measure,
        or within a context based search.

        To get the duration of the total length of elements, just use the
        `.duration` property.

        Here we create a 3/4 measure and "over-stuff" it with five quarter notes.
        `barDuration` still gives a duration of 3.0, or a dotted quarter note,
        while `.duration` gives a whole note tied to a quarter.


        >>> m = stream.Measure()
        >>> m.timeSignature = meter.TimeSignature('3/4')
        >>> m.barDuration
        <music21.duration.Duration 3.0>
        >>> m.repeatAppend(note.Note(type='quarter'), 5)
        >>> m.barDuration
        <music21.duration.Duration 3.0>
        >>> m.duration
        <music21.duration.Duration 5.0>

        The objects returned by `barDuration` and `duration` are
        full :class:`~music21.duration.Duration`
        objects, will all the relevant properties:

        >>> m.barDuration.fullName
        'Dotted Half'
        >>> m.duration.fullName
        'Whole tied to Quarter (5 total QL)'
        '''
        # TODO: it is possible that this should be cached or exposed as a method
        # as this search may take some time.
        if self.timeSignature is not None:
            ts = self.timeSignature
        else:  # do a context-based search
            tsStream = self.getTimeSignatures(searchContext=True,
                                              returnDefault=False,
                                              sortByCreationTime=True)
            if not tsStream:
                try:
                    ts = self.bestTimeSignature()
                except exceptions21.Music21Exception:
                    return duration.Duration(self.highestTime)

                # raise StreamException(
                #   'cannot determine bar duration without a time signature reference')
            else:  # it is the first found
                ts = tsStream[0]
        return ts.barDuration

    # --------------------------------------------------------------------------
    # Music21Objects are stored in the Stream's elements list
    # properties are provided to store and access these attribute

    def bestTimeSignature(self):
        '''
        Given a Measure with elements in it,
        get a TimeSignature that contains all elements.
        Calls meter.bestTimeSignature(self)

        Note: this does not yet accommodate triplets.


        We create a simple stream that should be in 3/4


        >>> s = converter.parse('C4 D4 E8 F8', format='tinyNotation', makeNotation=False)
        >>> m = stream.Measure()
        >>> for el in s:
        ...     m.insert(el.offset, el)

        But there is no TimeSignature!

        >>> m.show('text')
        {0.0} <music21.note.Note C>
        {1.0} <music21.note.Note D>
        {2.0} <music21.note.Note E>
        {2.5} <music21.note.Note F>

        So, we get the best Time Signature and put it in the Stream.

        >>> ts = m.bestTimeSignature()
        >>> ts
        <music21.meter.TimeSignature 3/4>
        >>> m.timeSignature = ts
        >>> m.show('text')
        {0.0} <music21.meter.TimeSignature 3/4>
        {0.0} <music21.note.Note C>
        {1.0} <music21.note.Note D>
        {2.0} <music21.note.Note E>
        {2.5} <music21.note.Note F>

        For further details about complex time signatures, etc.
        see `meter.bestTimeSignature()`

        '''
        return meter.bestTimeSignature(self)

    def _getLeftBarline(self):
        barList = []
        # directly access _elements, as do not want to get any bars
        # in _endElements
        for e in self._elements:
            if 'Barline' in e.classes:  # take the first
                if self.elementOffset(e) == 0.0:
                    barList.append(e)
                    break
        if not barList:
            return None
        else:
            return barList[0]

    def _setLeftBarline(self, barlineObj):
        insert = True
        if isinstance(barlineObj, str):
            barlineObj = bar.Barline(barlineObj)
            barlineObj.location = 'left'
        elif barlineObj is None:  # assume removal
            insert = False
        else:  # assume an Barline object
            barlineObj.location = 'left'

        oldLeftBarline = self._getLeftBarline()
        if oldLeftBarline is not None:
            # environLocal.printDebug(['_setLeftBarline()', 'removing left barline'])
            junk = self.pop(self.index(oldLeftBarline))
        if insert:
            # environLocal.printDebug(['_setLeftBarline()',
            # 'inserting new left barline', barlineObj])
            self.insert(0, barlineObj)

    leftBarline = property(_getLeftBarline,
                           _setLeftBarline,
                           doc='''
        Get or set the left barline, or the Barline object
        found at offset zero of the Measure.  Can be set either with a string
        representing barline style or a bar.Barline() object or None.
        Note that not all bars have
        barline objects here -- regular barlines don't need them.
        ''')

    def _getRightBarline(self):
        # TODO: Move to Stream or make setting .rightBarline, etc. on Stream raise an exception...
        # look on _endElements
        barList = []
        for e in self._endElements:
            if 'Barline' in e.classes:  # take the first
                barList.append(e)
                break
        # barList = self.getElementsByClass(bar.Barline)
        if not barList:  # do this before searching for barQL
            return None
        else:
            return barList[0]

    def _setRightBarline(self, barlineObj):
        insert = True
        if isinstance(barlineObj, str):
            barlineObj = bar.Barline(barlineObj)
            barlineObj.location = 'right'
        elif barlineObj is None:  # assume removal
            insert = False
        else:  # assume an Barline object
            barlineObj.location = 'right'

        # if a repeat, setup direction if not assigned
        if barlineObj is not None and 'Repeat' in barlineObj.classes:
            # environLocal.printDebug(['got barline obj w/ direction', barlineObj.direction])
            if barlineObj.direction in ['start', None]:
                barlineObj.direction = 'end'
        oldRightBarline = self._getRightBarline()

        if oldRightBarline is not None:
            # environLocal.printDebug(['_setRightBarline()', 'removing right barline'])
            junk = self.pop(self.index(oldRightBarline))
        # insert into _endElements
        if insert:
            self.storeAtEnd(barlineObj)

        # environLocal.printDebug(['post _setRightBarline', barlineObj,
        #    'len of elements highest', len(self._endElements)])

    rightBarline = property(_getRightBarline,
                            _setRightBarline,
                            doc='''
        Get or set the right barline, or the Barline object
        found at the offset equal to the bar duration.

        >>> b = bar.Barline('final')
        >>> m = stream.Measure()
        >>> print(m.rightBarline)
        None
        >>> m.rightBarline = b
        >>> m.rightBarline.type
        'final'


        A string can also be used instead:

        >>> c = converter.parse('tinynotation: 3/8 C8 D E F G A B4.')
        >>> c.measure(1).rightBarline = 'light-light'
        >>> c.measure(3).rightBarline = 'light-heavy'
        >>> #_DOCS_SHOW c.show()

        .. image:: images/stream_barline_demo.*
            :width: 211

        OMIT_FROM_DOCS

        .measure currently isn't the same as the
        original measure...

        ''')


class Part(Stream):
    '''
    A Stream subclass for designating music that is considered a single part.

    When put into a Score object, Part objects are all collected in the `Score.parts`
    call.  Otherwise they mostly work like generic Streams.

    Generally the hierarchy goes: Score > Part > Measure > Voice, but you are not
    required to stick to this.

    Part groupings (piano braces, etc.) are found in the :ref:`moduleLayout` module
    in the :class:`~music21.layout.StaffGroup` Spanner object.

    OMIT_FROM_DOCS
    Check that this is True and works for everything before suggesting that it works!

    May be enclosed in a staff (for instance, 2nd and 3rd trombone
    on a single staff), may enclose staves (piano treble and piano bass),
    or may not enclose or be enclosed by a staff (in which case, it
    assumes that this part fits on one staff and shares it with no other
    part
    '''
    recursionType = 'flatten'

    # _DOC_ATTR = {
    # }

    def __init__(self, *args, **keywords):
        super().__init__(*args, **keywords)
        self._partName = None
        self._partAbbreviation = None

    def _getPartName(self):
        if self._partName is not None:
            return self._partName
        elif '_partName' in self._cache:
            return self._cache['_partName']
        else:
            pn = None
            for e in self.recurse().getElementsByClass('Instrument'):
                pn = e.partName
                if pn is None:
                    pn = e.instrumentName
                if pn is not None:
                    break
            self._cache['_partName'] = pn
            return pn

    def _setPartName(self, newName):
        self._partName = newName

    partName = property(_getPartName, _setPartName, doc='''
        Gets or sets a string representing the name of this part
        as a whole (not counting instrument changes, etc.).

        It can be set explicitly (or set on parsing) or it
        can take its name from the first :class:`~music21.instrument.Instrument` object
        encountered in the stream (or within a substream),
        first checking its .partName, then checking its .instrumentName

        Can also return None.

        >>> p = stream.Part()
        >>> p.partName is None
        True
        >>> cl = instrument.Clarinet()
        >>> p.insert(0, cl)
        >>> p.partName
        'Clarinet'
        >>> p.remove(cl)
        >>> p.partName is None
        True
        >>> p.insert(0, instrument.Flute())
        >>> p.partName
        'Flute'
        >>> p.partName = 'Reed 1'
        >>> p.partName
        'Reed 1'

        Note that changing an instrument's .partName or .instrumentName while it
        is already in the Stream will not automatically update this unless
        .coreElementsChanged() is called or this Stream's elements are otherwise altered.
        This is because the value is cached so that O(n) searches through the Stream
        do not need to be done every time.
    ''')

    def _getPartAbbreviation(self):
        if self._partAbbreviation is not None:
            return self._partAbbreviation
        elif '_partAbbreviation' in self._cache:
            return self._cache['_partAbbreviation']
        else:
            pn = None
            for e in self.recurse().getElementsByClass('Instrument'):
                pn = e.partAbbreviation
                if pn is None:
                    pn = e.instrumentAbbreviation
                if pn is not None:
                    break
            self._cache['_partAbbreviation'] = pn
            return pn

    def _setPartAbbreviation(self, newName):
        self._partAbbreviation = newName

    partAbbreviation = property(_getPartAbbreviation, _setPartAbbreviation, doc='''
        Gets or sets a string representing the abbreviated name of this part
        as a whole (not counting instrument changes, etc.).

        It can be set explicitly (or set on parsing) or it
        can take its name from the first :class:`~music21.instrument.Instrument` object
        encountered in the stream (or within a substream),
        first checking its .partAbbreviation, then checking its .instrumentAbbreviation

        Can also return None.

        >>> p = stream.Part()
        >>> p.partAbbreviation is None
        True
        >>> cl = instrument.Clarinet()
        >>> p.insert(0, cl)
        >>> p.partAbbreviation
        'Cl'
        >>> p.remove(cl)
        >>> p.partAbbreviation is None
        True
        >>> p.insert(0, instrument.Flute())
        >>> p.partAbbreviation
        'Fl'
        >>> p.partAbbreviation = 'Rd 1'
        >>> p.partAbbreviation
        'Rd 1'

        Note that changing an instrument's .partAbbreviation or .instrumentAbbreviation while it
        is already in the Stream will not automatically update this unless
        .coreElementsChanged() is called or this Stream's elements are otherwise altered.
        This is because the value is cached so that O(n) searches through the Stream
        do not need to be done every time.
    ''')

    def makeAccidentals(
        self,
        *,
        alteredPitches=None,
        cautionaryPitchClass=True,
        cautionaryAll=False,
        inPlace=True,
        overrideStatus=False,
        cautionaryNotImmediateRepeat=True,
        tiePitchSet=None,
    ):
        '''
        This overridden method of Stream.makeAccidentals
        provides the management of passing pitches from
        a past Measure to each new measure for processing.

        TODO: by default inPlace should be False
        '''
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self
        # process make accidentals for each measure
        measureStream = returnObj.getElementsByClass('Measure')
        ksLast = None
        for i in range(len(measureStream)):
            m = measureStream[i]
            if m.keySignature is not None:
                ksLast = m.keySignature
            # if beyond the first measure, use the pitches from the last
            # measure for context
            if i > 0:
                try:
                    previousNoteOrChord = measureStream[i - 1][-1]
                    tiePitchSet = makeNotation.getTiePitchSet(previousNoteOrChord)
                except (IndexError, StreamException):
                    tiePitchSet = None

                pitchPastMeasure = measureStream[i - 1].pitches

            else:
                pitchPastMeasure = None
                # use the tie pitchSet from the method argument

            m.makeAccidentals(
                pitchPastMeasure=pitchPastMeasure,
                useKeySignature=ksLast,
                alteredPitches=alteredPitches,
                searchKeySignatureByContext=False,
                cautionaryPitchClass=cautionaryPitchClass,
                cautionaryAll=cautionaryAll,
                inPlace=True,  # always, has have a copy or source
                overrideStatus=overrideStatus,
                cautionaryNotImmediateRepeat=cautionaryNotImmediateRepeat,
                tiePitchSet=tiePitchSet,
            )
        if not inPlace:
            return returnObj
        else:  # in place
            return None


class PartStaff(Part):
    '''
    A Part subclass for designating music that is
    represented on a single staff but may only be one
    of many staves for a single part.
    '''


# class Performer(Stream):
#     '''
#     A Stream subclass for designating music to be performed by a
#     single Performer.  Should only be used when a single performer
#     performs on multiple parts.  E.g. Bass Drum and Triangle on separate
#     staves performed by one player.
#
#     a Part + changes of Instrument is fine for designating most cases
#     where a player changes instrument in a piece.  A part plus staves
#     with individual instrument changes could also be a way of designating
#     music that is performed by a single performer (see, for instance
#     the Piano doubling Celesta part in Lukas Foss's Time Cycle).  The
#     Performer Stream-subclass could be useful for analyses of, for instance,
#     how 5 percussionists chose to play a piece originally designated for 4
#     (or 6) percussionists in the score.
#     '''
#     # NOTE: not yet implemented
#     pass

class System(Stream):
    '''
    Totally optional and used only in OMR and Capella: a designation that all the
    music in this Stream belongs in a single system.

    The system object has two attributes, systemNumber (which number is it)
    and systemNumbering which says at what point the numbering of
    systems resets.  It can be either "Score" (default), "Opus", or "Page".
    '''
    systemNumber = 0
    systemNumbering = 'Score'  # or Page; when do system numbers reset?


class Score(Stream):
    '''
    A Stream subclass for handling multi-part music.

    Almost totally optional (the largest containing Stream in a piece could be
    a generic Stream, or a Part, or a Staff).  And Scores can be
    embedded in other Scores (in fact, our original thought was to call
    this class a Fragment because of this possibility of continuous
    embedding; though it's probably better to embed a Score in an Opus),
    but we figure that many people will like calling the largest
    container a Score and that this will become a standard.
    '''
    recursionType = 'elementsOnly'

    @property
    def parts(self):
        '''
        Return all :class:`~music21.stream.Part` objects in a :class:`~music21.stream.Score`.

        It filters out all other things that might be in a Score object, such as Metadata
        returning just the Parts.


        >>> s = corpus.parse('bach/bwv66.6')
        >>> s.parts
        <music21.stream.iterator.StreamIterator for Score:0x104af3a58 @:0>
        >>> partStream = s.parts.stream()
        >>> partStream.classes
        ('Score', 'Stream', 'StreamCoreMixin', 'Music21Object', 'ProtoM21Object', 'object')
        >>> len(partStream)
        4

        The partStream object is a full `stream.Score` object, thus the elements inside it
        can be accessed by index number or by id string, or iterated over:

        >>> partStream[0]
        <music21.stream.Part Soprano>
        >>> partStream['Alto']
        <music21.stream.Part Alto>
        >>> for p in partStream:
        ...     print(p.id)
        Soprano
        Alto
        Tenor
        Bass


        OMIT_FROM_DOCS

        Ensure that getting from cache still will reset the iteration.

        >>> for i, p in enumerate(s.parts):
        ...     print(i, p)
        ...     break
        0 <music21.stream.Part Soprano>

        >>> for i, p in enumerate(s.parts):
        ...     print(i, p)
        ...     break
        0 <music21.stream.Part Soprano>
        '''
        # return self.getElementsByClass('Part')
        if 'parts' not in self._cache or self._cache['parts'] is None:
            partIterator = self.getElementsByClass('Part')
            partIterator.overrideDerivation = 'parts'
            self._cache['parts'] = partIterator
        return self._cache['parts']

    def measures(self,
                 numberStart,
                 numberEnd,
                 collect=('Clef', 'TimeSignature', 'Instrument', 'KeySignature'),
                 gatherSpanners=True,
                 indicesNotNumbers=False):
        # noinspection PyShadowingNames
        '''
        This method overrides the :meth:`~music21.stream.Stream.measures`
        method on Stream. This creates a new Score stream that has the same measure
        range for all Parts.

        The `collect` argument is a list of classes that will be collected; see
        Stream.measures()

        >>> s = corpus.parse('bwv66.6')
        >>> post = s.measures(3, 5)  # range is inclusive, i.e., [3, 5]
        >>> len(post.parts)
        4
        >>> len(post.parts[0].getElementsByClass('Measure'))
        3
        >>> len(post.parts[1].getElementsByClass('Measure'))
        3
        '''
        post = self.__class__()
        # this calls on Music21Object, transfers groups, id if not id(self)
        post.mergeAttributes(self)
        # note that this will strip all objects that are not Parts
        for p in self.parts:
            # insert all at zero
            measuredPart = p.measures(numberStart,
                                      numberEnd,
                                      collect,
                                      gatherSpanners=gatherSpanners,
                                      indicesNotNumbers=indicesNotNumbers)
            post.insert(0, measuredPart)
        # must manually add any spanners; do not need to add .flat,
        # as Stream.measures will handle lower level
        if gatherSpanners:
            spStream = self.spanners
            for sp in spStream:
                post.insert(0, sp)

        post.derivation.client = post
        post.derivation.origin = self
        post.derivation.method = 'measures'
        return post

    def measure(self,
                measureNumber,
                collect=('Clef', 'TimeSignature', 'Instrument', 'KeySignature'),
                gatherSpanners=True,
                indicesNotNumbers=False):
        '''
        Given a measure number (or measure index, if indicesNotNumbers is True)
        return another Score object which contains multiple parts but each of which has only a
        single :class:`~music21.stream.Measure` object if the
        Measure number exists, otherwise returns a score with parts that are empty.

        This method overrides the :meth:`~music21.stream.Stream.measure` method on Stream to
        allow for finding a single "measure slice" within parts:


        >>> bachIn = corpus.parse('bach/bwv324.xml')
        >>> excerpt = bachIn.measure(2)
        >>> excerpt
        <music21.stream.Score 0x10322b5f8>
        >>> len(excerpt.parts)
        4
        >>> excerpt.parts[0].show('text')
        {0.0} <music21.instrument.Instrument 'P1: Soprano: '>
        {0.0} <music21.clef.TrebleClef>
        {0.0} <music21.key.Key of e minor>
        {0.0} <music21.meter.TimeSignature 4/4>
        {0.0} <music21.stream.Measure 2 offset=0.0>
            {0.0} <music21.note.Note B>
            {1.0} <music21.note.Note B>
            {2.0} <music21.note.Note B>
            {3.0} <music21.note.Note B>

        Note that the parts created have all the meta-information outside the measure
        unless this information appears in the measure itself at the beginning:

        >>> bachIn.measure(1).parts[0].show('text')
        {0.0} <music21.instrument.Instrument 'P1: Soprano: '>
        {0.0} <music21.stream.Measure 1 offset=0.0>
            {0.0} <music21.clef.TrebleClef>
            {0.0} <music21.key.Key of e minor>
            {0.0} <music21.meter.TimeSignature 4/4>
            {0.0} <music21.layout.SystemLayout>
            {0.0} <music21.note.Note B>
            {2.0} <music21.note.Note D>

        This way the original measure objects can be returned without being altered.

        The final measure slice of the piece can be obtained with index -1.  Example:
        quickly get the last chord of the piece, without needing to run .chordify()
        on the whole piece:

        >>> excerpt = bachIn.measure(-1)
        >>> excerptChords = excerpt.chordify()
        >>> excerptChords.show('text')
        {0.0} <music21.instrument.Instrument 'P1: Soprano: '>
        {0.0} <music21.clef.TrebleClef>
        {0.0} <music21.key.Key of e minor>
        {0.0} <music21.meter.TimeSignature 4/4>
        {0.0} <music21.stream.Measure 9 offset=0.0>
            {0.0} <music21.chord.Chord E2 G3 B3 E4>
            {4.0} <music21.bar.Barline type=final>

        >>> lastChord = excerptChords.recurse().getElementsByClass('Chord')[-1]
        >>> lastChord
        <music21.chord.Chord E2 G3 B3 E4>

        Note that we still do a .getElementsByClass('Chord') since many pieces end
        with nothing but a rest...
        '''
        if measureNumber < 0:
            indicesNotNumbers = True

        startMeasureNumber = measureNumber
        endMeasureNumber = measureNumber
        if indicesNotNumbers:
            endMeasureNumber += 1
            if startMeasureNumber == -1:
                endMeasureNumber = None

        post = self.__class__()
        # this calls on Music21Object, transfers id, groups
        post.mergeAttributes(self)
        # note that this will strip all objects that are not Parts
        for p in self.getElementsByClass('Part'):
            # insert all at zero
            mStream = p.measures(startMeasureNumber,
                                 endMeasureNumber,
                                 collect=collect,
                                 gatherSpanners=gatherSpanners,
                                 indicesNotNumbers=indicesNotNumbers)
            post.insert(0, mStream)

        if gatherSpanners:
            spStream = self.spanners
            for sp in spStream:
                post.insert(0, sp)

        post.derivation.client = post
        post.derivation.origin = self
        post.derivation.method = 'measure'

        return post

    def expandRepeats(self):
        '''
        Expand all repeats, as well as all repeat indications
        given by text expressions such as D.C. al Segno.

        This method always returns a new Stream, with deepcopies
        of all contained elements at all level.
        '''
        post = self.cloneEmpty(derivationMethod='expandRepeats')
        # this calls on Music21Object, transfers id, groups
        post.mergeAttributes(self)

        # get all things in the score that are not Parts
        for e in self.iter.getElementsNotOfClass('Part'):
            eNew = copy.deepcopy(e)  # assume that this is needed
            post.insert(self.elementOffset(e), eNew)

        for p in self.iter.getElementsByClass('Part'):
            # get spanners at highest level, not by Part
            post.insert(0, p.expandRepeats(copySpanners=False))

        # spannerBundle = spanner.SpannerBundle(post.flat.spanners)
        spannerBundle = post.spannerBundle  # use property
        # iterate over complete semi flat (need containers); find
        # all new/old pairs
        for e in post.recurse(includeSelf=False):
            # update based on last id, new object
            if e.sites.hasSpannerSite():
                origin = e.derivation.origin
                if origin is not None and e.derivation.method == '__deepcopy__':
                    spannerBundle.replaceSpannedElement(id(origin), e)
        return post

    def measureOffsetMap(self, classFilterList=None):
        '''
        This Score method overrides the
        :meth:`~music21.stream.Stream.measureOffsetMap` method of Stream.
        This creates a map based on all contained Parts in this Score.
        Measures found in multiple Parts with the same offset will be
        appended to the same list.

        If no parts are found in the score, then the normal
        :meth:`~music21.stream.Stream.measureOffsetMap` routine is called.

        This method is smart and does not assume that all Parts
        have measures with identical offsets.
        '''
        parts = self.iter.parts
        if not parts:
            return Stream.measureOffsetMap(self, classFilterList)
        # else:
        offsetMap = {}
        for p in parts:
            mapPartial = p.measureOffsetMap(classFilterList)
            # environLocal.printDebug(['mapPartial', mapPartial])
            for k in mapPartial:
                if k not in offsetMap:
                    offsetMap[k] = []
                for m in mapPartial[k]:  # get measures from partial
                    if m not in offsetMap[k]:
                        offsetMap[k].append(m)
        orderedOffsetMap = collections.OrderedDict(sorted(offsetMap.items(), key=lambda o: o[0]))
        return orderedOffsetMap

    def sliceByGreatestDivisor(self, *, addTies=True, inPlace=False):
        '''
        Slice all duration of all part by the minimum duration
        that can be summed to each concurrent duration.

        Overrides method defined on Stream.
        '''
        if not inPlace:  # make a copy
            returnObj = copy.deepcopy(self)
        else:
            returnObj = self

        # find greatest divisor for each measure at a time
        # if no measures this will be zero
        mStream = returnObj.parts[0].getElementsByClass('Measure')
        mCount = len(mStream)
        if mCount == 0:
            mCount = 1  # treat as a single measure
        for i in range(mCount):  # may be 1
            uniqueQuarterLengths = []
            for p in returnObj.getElementsByClass('Part'):
                if p.hasMeasures():
                    m = p.getElementsByClass('Measure')[i]
                else:
                    m = p  # treat the entire part as one measure

                # collect all unique quarter lengths
                for e in m.notesAndRests:
                    # environLocal.printDebug(['examining e', i, e, e.quarterLength])
                    if e.quarterLength not in uniqueQuarterLengths:
                        uniqueQuarterLengths.append(e.quarterLength)

            # after ql for all parts, find divisor
            divisor = common.approximateGCD(uniqueQuarterLengths)
            # environLocal.printDebug(['Score.sliceByGreatestDivisor:
            # got divisor from unique ql:', divisor, uniqueQuarterLengths])

            for p in returnObj.getElementsByClass('Part'):
                # in place: already have a copy if nec
                # must do on measure at a time
                if p.hasMeasures():
                    m = p.getElementsByClass('Measure')[i]
                else:
                    m = p  # treat the entire part as one measure
                m.sliceByQuarterLengths(quarterLengthList=[divisor],
                                        target=None,
                                        addTies=addTies,
                                        inPlace=True)
        del mStream  # cleanup Streams
        returnObj.coreElementsChanged()
        if not inPlace:
            return returnObj

    def partsToVoices(self,
                      voiceAllocation: Union[int, List[Union[List, int]]] = 2,
                      permitOneVoicePerPart=False,
                      setStems=True):
        # noinspection PyShadowingNames
        '''
        Given a multi-part :class:`~music21.stream.Score`,
        return a new Score that combines parts into voices.

        The `voiceAllocation` parameter sets the maximum number
        of voices per Part.

        The `permitOneVoicePerPart` parameter, if True, will encode a
        single voice inside a single Part, rather than leaving it as
        a single Part alone, with no internal voices.


        >>> s = corpus.parse('bwv66.6')
        >>> len(s.flat.notes)
        165
        >>> post = s.partsToVoices(voiceAllocation=4)
        >>> len(post.parts)
        1
        >>> len(post.parts[0].getElementsByClass('Measure')[0].voices)
        4
        >>> len(post.flat.notes)
        165

        '''
        sub = []
        bundle = []
        if common.isNum(voiceAllocation):
            voicesPerPart = voiceAllocation
            for pIndex, p in enumerate(self.parts):
                if pIndex % voicesPerPart == 0:
                    sub = []
                    sub.append(p)
                else:
                    sub.append(p)
                if pIndex % voicesPerPart == voicesPerPart - 1:
                    bundle.append(sub)
                    sub = []
            if sub:  # get last
                bundle.append(sub)
        # else, assume it is a list of groupings
        elif common.isIterable(voiceAllocation):
            for group in voiceAllocation:
                sub = []
                # if a single entry
                if not common.isListLike(group):
                    # group is a single index
                    sub.append(self.parts[group])
                else:
                    for partId in group:
                        sub.append(self.parts[partId])
                bundle.append(sub)
        else:
            raise StreamException(f'incorrect voiceAllocation format: {voiceAllocation}')

        # environLocal.printDebug(['partsToVoices() bundle:', bundle])

        s = self.cloneEmpty(derivationMethod='partsToVoices')
        s.metadata = self.metadata

        for sub in bundle:  # each sub contains parts
            if len(sub) == 1 and not permitOneVoicePerPart:
                # probably need to create a new part and measure
                s.insert(0, sub[0])
                continue

            pActive = Part()
            # iterate through each part
            for pIndex, p in enumerate(sub):
                # only check for measures once per part
                if pActive.hasMeasures():
                    hasMeasures = True
                else:
                    hasMeasures = False

                for mIndex, m in enumerate(p.getElementsByClass('Measure')):
                    # environLocal.printDebug(['pIndex, p', pIndex, p,
                    #     'mIndex, m', mIndex, m, 'hasMeasures', hasMeasures])
                    # only create measures if non already exist
                    if not hasMeasures:
                        # environLocal.printDebug(['creating measure'])
                        mActive = Measure()
                        # some attributes may be none
                        # note: not copying here; and first part read will provide
                        # attributes; possible other parts may have other attributes
                        mActive.mergeAttributes(m)
                        mActive.mergeElements(m, classFilterList=(
                            'Barline', 'TimeSignature', 'Clef', 'KeySignature'))

                        # if m.timeSignature is not None:
                        #     mActive.timeSignature = m.timeSignature
                        # if m.keySignature is not None:
                        #     mActive.keySignature = m.keySignature
                        # if m.clef is not None:
                        #     mActive.clef = m.clef
                    else:
                        mActive = pActive.getElementsByClass('Measure')[mIndex]

                    # transfer elements into a voice
                    v = Voice()
                    v.id = pIndex
                    # for now, just take notes, including rests
                    for e in m.notesAndRests:  # m.getElementsByClass():
                        if setStems and hasattr(e, 'stemDirection'):
                            e.stemDirection = 'up' if pIndex % 2 == 0 else 'down'
                        v.insert(e.getOffsetBySite(m), e)
                    # insert voice in new measure
                    # environLocal.printDebug(['inserting voice', v, v.id, 'into measure', mActive])
                    mActive.insert(0, v)
                    # mActive.show('t')
                    # only insert measure if new part does not already have measures
                    if not hasMeasures:
                        pActive.insert(m.getOffsetBySite(p), mActive)

            s.insert(0, pActive)
            pActive = None
        return s

    def implode(self):
        '''
        Reduce a polyphonic work into two staves.

        Currently, this is just a synonym for `partsToVoices` with
        `voiceAllocation = 2`, and `permitOneVoicePerPart = False`,
        but someday this will have better methods for finding identical
        parts, etc.
        '''
        voiceAllocation = 2
        permitOneVoicePerPart = False

        return self.partsToVoices(
            voiceAllocation=voiceAllocation,
            permitOneVoicePerPart=permitOneVoicePerPart
        )

    def flattenParts(self, classFilterList=('Note', 'Chord')):
        # noinspection PyShadowingNames
        '''
        Given a Score, combine all Parts into a single Part
        with all elements found in each Measure of the Score.

        The `classFilterList` can be used to specify which objects
        contained in Measures are transferred.

        It also flattens all voices within a part.

        To be deprecated at some point...

        >>> s = corpus.parse('bwv66.6')
        >>> len(s.parts)
        4
        >>> len(s.flat.notes)
        165
        >>> post = s.flattenParts()
        >>> 'Part' in post.classes
        True
        >>> len(post.flat.notes)
        165
        '''
        post = self.parts[0].template(fillWithRests=False, retainVoices=False)
        for i, m in enumerate(post.getElementsByClass('Measure')):
            for p in self.parts:
                mNew = copy.deepcopy(p.getElementsByClass('Measure')[i]).flat
                for e in mNew:
                    match = False
                    for cf in classFilterList:
                        if cf in e.classes:
                            match = True
                            break
                    if match:
                        m.insert(e.getOffsetBySite(mNew), e)
        return post

    def makeNotation(self,
                     meterStream=None,
                     refStreamOrTimeRange=None,
                     inPlace=False,
                     bestClef=False,
                     **subroutineKeywords):
        '''
        This method overrides the makeNotation method on Stream,
        such that a Score object with one or more Parts or Streams
        that may not contain well-formed notation may be transformed
        and replaced by well-formed notation.

        If `inPlace` is True, this is done in-place;
        if `inPlace` is False, this returns a modified deep copy.
        '''
        if inPlace:
            returnStream = self
        else:
            returnStream = copy.deepcopy(self)
        returnStream.coreGatherMissingSpanners()  # get spanners needed but not here!

        # do not assume that we have parts here
        if self.hasPartLikeStreams():
            for s in returnStream.iter.getElementsByClass('Stream'):
                # process all component Streams inPlace
                s.makeNotation(meterStream=meterStream,
                               refStreamOrTimeRange=refStreamOrTimeRange,
                               inPlace=True,
                               bestClef=bestClef,
                               **subroutineKeywords)
            # note: while the local-streams have updated their caches, the
            # containing score has an out-of-date cache of flat.
            # this, must call elements changed
            returnStream.coreElementsChanged()
        else:  # call the base method
            super(Score, returnStream).makeNotation(meterStream=meterStream,
                                                    refStreamOrTimeRange=refStreamOrTimeRange,
                                                    inPlace=True,
                                                    bestClef=bestClef,
                                                    **subroutineKeywords)

        if inPlace:
            return None
        else:
            return returnStream



class Opus(Stream):
    '''
    A Stream subclass for handling multi-work music encodings.
    Many ABC files, for example, define multiple works or parts within a single file.

    Opus objects can contain multiple Score objects, or even other Opus objects!
    '''
    recursionType = 'elementsOnly'

    # TODO: get by title, possibly w/ regex

    def getNumbers(self):
        '''
        Return a list of all numbers defined in this Opus.

        >>> o = corpus.parse('josquin/oVenusBant')
        >>> o.getNumbers()
        ['1', '2', '3']
        '''
        post = []
        for s in self.iter.getElementsByClass('Score'):
            post.append(s.metadata.number)
        return post

    def getScoreByNumber(self, opusMatch):
        # noinspection PyShadowingNames
        '''
        Get Score objects from this Stream by number.
        Performs title search using the
        :meth:`~music21.metadata.Metadata.search` method,
        and returns the first result.

        >>> o = corpus.parse('josquin/oVenusBant')
        >>> o.getNumbers()
        ['1', '2', '3']
        >>> s = o.getScoreByNumber(2)
        >>> s.metadata.title
        'O Venus bant'
        >>> s.metadata.alternativeTitle
        'Tenor'
        '''
        for s in self.iter.getElementsByClass('Score'):
            match, unused_field = s.metadata.search(opusMatch, 'number')
            if match:
                return s

    # noinspection SpellCheckingInspection
    def getScoreByTitle(self, titleMatch):
        # noinspection SpellCheckingInspection, PyShadowingNames
        '''
        Get Score objects from this Stream by a title.
        Performs title search using the :meth:`~music21.metadata.Metadata.search` method,
        and returns the first result.

        >>> o = corpus.parse('essenFolksong/erk5')
        >>> s = o.getScoreByTitle('Vrienden, kommt alle gaere')
        >>> s.metadata.title
        'Vrienden, kommt alle gaere'

        Regular expressions work fine

        >>> s = o.getScoreByTitle('(.*)kommt(.*)')
        >>> s.metadata.title
        'Vrienden, kommt alle gaere'
        '''
        for s in self.getElementsByClass('Score'):
            match, unused_field = s.metadata.search(titleMatch, 'title')
            if match:
                return s

    @property
    def scores(self):
        '''
        Return all :class:`~music21.stream.Score` objects
        in an iterator
        '''
        return self.getElementsByClass(Score)

    def mergeScores(self):
        # noinspection PyShadowingNames
        '''
        Some Opus objects represent numerous scores
        that are individual parts of the same work.
        This method will treat each contained Score as a Part,
        merging and returning a single Score with merged Metadata.

        >>> from music21 import corpus
        >>> o = corpus.parse('josquin/milleRegrets')
        >>> s = o.mergeScores()
        >>> s.metadata.title
        'Mille regrets'
        >>> len(s.parts)
        4
        '''
        sNew = Score()
        mdNew = metadata.Metadata()

        for s in self.scores:
            p = s.parts[0]  # assuming only one part
            sNew.insert(0, p)

            md = s.metadata
            # presently just getting the first of attributes encountered
            if md is not None:
                # environLocal.printDebug(['sub-score meta data', md,
                #   'md.composer', md.composer, 'md.title', md.title])
                if md.title is not None and mdNew.title is None:
                    mdNew.title = md.title
                if md.composer is not None and mdNew.composer is None:
                    mdNew.composer = md.composer

        sNew.insert(0, mdNew)
        return sNew

    # -------------------------------------------------------------------------
    def write(self, fmt=None, fp=None, **keywords):
        '''
        Displays an object in a format provided by the `fmt` argument or, if not
        provided, the format set in the user's Environment.

        This method overrides the behavior specified in
        :class:`~music21.base.Music21Object` for all formats besides explicit
        lily.x calls.

        Individual files are written for each score; returns the last file written.

        >>> sc1 = stream.Score()
        >>> sc2 = stream.Score()
        >>> o = stream.Opus()
        >>> o.append([sc1, sc2])

        #_DOCS_SHOW >>> o.write()
        #_DOCS_SHOW PosixPath('/some/temp/path-2.xml')
        '''
        if not self.scores:
            return None

        if fmt is not None and 'lily' in fmt:
            return Stream.write(self, fmt, fp, **keywords)
        elif common.runningUnderIPython():
            return Stream.write(self, fmt, fp, **keywords)

        if fp is None:
            if fmt is None:
                suffix = '.' + environLocal['writeFormat']
            else:
                unused_format, suffix = common.findFormat(fmt)
            fp = environLocal.getTempFile(returnPathlib=False) + suffix
        if isinstance(fp, str):
            fp = pathlib.Path(fp)

        fpParent = fp.parent
        fpStem = fp.stem
        fpSuffix = '.'.join(fp.suffixes)

        post = []
        placesRequired = math.ceil(math.log10(len(self.scores)))
        for i, s in enumerate(self.scores):
            # if i = 9, num = 10, take log10(11) so the result is strictly greater than 1.0
            placesConsumed = math.ceil(math.log10(i + 2))
            zeroesNeeded = placesRequired - placesConsumed
            zeroes = '0' * zeroesNeeded
            scoreName = fpStem + '-' + zeroes + str(i + 1) + fpSuffix
            fpToUse = fpParent / scoreName
            fpReturned = s.write(fmt=fmt, fp=fpToUse, **keywords)
            environLocal.printDebug(f'Component {s} written to {fpReturned}')
            post.append(fpReturned)
        return post[-1] if post else None

    def show(self, fmt=None, app=None, **keywords):
        '''
        Show an Opus file.

        This method overrides the behavior specified in
        :class:`~music21.base.Music21Object` for all
        formats besides explicit lily.x calls. or when running under IPython notebook.
        '''
        if fmt is not None and 'lily' in fmt:
            return Stream.show(self, fmt, app, **keywords)
        elif common.runningUnderIPython():
            return Stream.show(self, fmt, app, **keywords)
        else:
            for s in self.scores:
                s.show(fmt=fmt, app=app, **keywords)


# -----------------------------------------------------------------------------
# Special stream sub-classes that are instantiated as hidden attributes within
# other Music21Objects (i.e., Spanner and Variant).


class SpannerStorage(Stream):
    '''
    For advanced use. This Stream subclass is only used
    inside of a Spanner object to provide object storage
    of connected elements (things the Spanner spans).

    This subclass name can be used to search in an
    object's .sites and find any and all
    locations that are SpannerStorage objects.

    A `spannerParent` keyword argument must be
    provided by the Spanner in creation.

    TODO: rename spannerParent to client.
    '''

    def __init__(self, *arguments, **keywords):
        super().__init__(*arguments, **keywords)

        # must provide a keyword argument with a reference to the spanner
        # parent could name spannerContainer or other?

        # environLocal.printDebug('keywords', keywords)
        # TODO: this might be better stored as weak ref

        self.spannerParent = None
        if 'spannerParent' in keywords:
            self.spannerParent = keywords['spannerParent']

    # NOTE: for serialization, this will need to properly tag
    # the spanner parent by updating the scaffolding code.

    def coreSelfActiveSite(self, el):
        '''
        Never set activeSite to spannerStorage
        '''
        pass


class VariantStorage(Stream):
    '''
    For advanced use. This Stream subclass is only
    used inside of a Variant object to provide object
    storage of connected elements (things the Variant
    defines).

    This subclass name can be used to search in an
    object's .sites and find any and all
    locations that are VariantStorage objects.

    A `variantParent` keyword argument must be provided
    by the Variant in creation.

    # TODO: rename variantParent to client
    '''

    def __init__(self, *arguments, **keywords):
        super().__init__(*arguments, **keywords)
        # must provide a keyword argument with a reference to the variant
        # parent
        self.variantParent = None
        if 'variantParent' in keywords:
            self.variantParent = keywords['variantParent']


# -----------------------------------------------------------------------------


# -----------------------------------------------------------------------------


class Test(unittest.TestCase):
    '''
    Note: all Stream tests are found in test/testStream.py
    '''

    def testCopyAndDeepcopy(self):
        '''Test copying all objects defined in this module
        '''
        for part in sys.modules[self.__module__].__dict__:
            if part.startswith('_') or part.startswith('__'):
                continue
            elif part in ['Test', 'TestExternal']:
                continue
            elif callable(part):  # pragma: no cover
                # environLocal.printDebug(['testing copying on', part])
                # noinspection PyTypeChecker
                obj = getattr(self.__module__, part)()
                a = copy.copy(obj)
                b = copy.deepcopy(obj)
                self.assertNotEqual(a, obj)
                self.assertNotEqual(b, obj)


# -----------------------------------------------------------------------------
# define presented order in documentation
_DOC_ORDER = [Stream, Measure, Part, Score, Opus]


if __name__ == '__main__':
    import music21
    music21.mainTest(Test)

