1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Name:         stream/iterator.py
4# Purpose:      classes for walking through streams...
5#
6# Authors:      Michael Scott Cuthbert
7#               Christopher Ariza
8#
9# Copyright:    Copyright © 2008-2016 Michael Scott Cuthbert and the music21 Project
10# License:      BSD, see license.txt
11# -----------------------------------------------------------------------------
12'''
13this class contains iterators and filters for walking through streams
14
15StreamIterators are explicitly allowed to access private methods on streams.
16'''
17import copy
18from typing import TypeVar, List, Union, Callable, Optional, Dict
19import unittest
20import warnings
21
22from music21 import common
23from music21.common.classTools import tempAttribute, saveAttributes
24from music21.common.enums import OffsetSpecial
25from music21.exceptions21 import StreamException
26from music21.stream import filters
27from music21 import prebase
28from music21 import base   # just for typing.
29
30from music21.sites import SitesException
31
32
33_SIter = TypeVar('_SIter')
34FilterType = Union[Callable, filters.StreamFilter]
35
36# -----------------------------------------------------------------------------
37
38
39class StreamIteratorException(StreamException):
40    pass
41
42
43class StreamIteratorInefficientWarning(PendingDeprecationWarning):
44    pass
45
46# -----------------------------------------------------------------------------
47
48
49class StreamIterator(prebase.ProtoM21Object):
50    '''
51    An Iterator object used to handle getting items from Streams.
52    The :meth:`~music21.stream.Stream.__iter__` method
53    returns this object, passing a reference to self.
54
55    Note that this iterator automatically sets the active site of
56    returned elements to the source Stream.
57
58    There is one property to know about: .overrideDerivation which overrides the set
59    derivation of the class when .stream() is called
60
61    Sets:
62
63    * StreamIterator.srcStream -- the Stream iterated over
64    * StreamIterator.index -- current index item
65    * StreamIterator.streamLength -- length of elements.
66
67    * StreamIterator.srcStreamElements -- srcStream._elements
68    * StreamIterator.cleanupOnStop -- should the StreamIterator delete the
69      reference to srcStream and srcStreamElements when stopping? default
70      False
71    * StreamIterator.activeInformation -- a dict that contains information
72      about where we are in the parse.  Especially useful for recursive
73      streams. 'stream' = the stream that is currently active, 'index'
74      where in `.elements` we are, `iterSection` is `_elements` or `_endElements`,
75      and `sectionIndex` is where we are in the iterSection, or -1 if
76      we have not started. This dict is shared among all sub iterators.
77
78    Constructor keyword only arguments:
79
80    * `filterList` is a list of stream.filters.Filter objects to apply
81
82    * if `restoreActiveSites` is True (default) then on iterating, the activeSite is set
83      to the Stream being iterated over.
84
85    * if `ignoreSorting` is True (default is False) then the Stream is not sorted before
86      iterating.  If the Stream is already sorted, then this value does not matter, and
87      no time will be saved by setting to False.
88
89    * For `activeInformation` see above.
90
91    Changed in v.5.2 -- all arguments except srcStream are keyword only.
92
93    OMIT_FROM_DOCS
94
95    Informative exception for user error:
96
97    >>> s = stream.Stream()
98    >>> sIter = stream.iterator.StreamIterator(s, filterList=[note.Note])
99    Traceback (most recent call last):
100    TypeError: filterList expects Filters or callables,
101    not types themselves; got <class 'music21.note.Note'>
102    '''
103    def __init__(self,
104                 srcStream: 'music21.stream.Stream',
105                 *,
106                 filterList: Union[List[FilterType], FilterType, None] = None,
107                 restoreActiveSites: bool = True,
108                 activeInformation: Optional[Dict] = None,
109                 ignoreSorting: bool = False):
110        if not ignoreSorting and srcStream.isSorted is False and srcStream.autoSort:
111            srcStream.sort()
112        self.srcStream: 'music21.stream.Stream' = srcStream
113        self.index: int = 0
114
115        # use .elements instead of ._elements/etc. so that it is sorted...
116        self.srcStreamElements = srcStream.elements
117        self.streamLength = len(self.srcStreamElements)
118
119        # this information can help in speed later
120        self.elementsLength = len(self.srcStream._elements)
121        self.sectionIndex = -1  # where we are within a given section (_elements or _endElements)
122        self.iterSection = '_elements'
123
124        self.cleanupOnStop = False
125        self.restoreActiveSites: bool = restoreActiveSites
126
127        self.overrideDerivation = None
128
129        if filterList is None:
130            filterList = []
131        elif not common.isIterable(filterList):
132            filterList = [filterList]
133        elif isinstance(filterList, (set, tuple)):
134            filterList = list(filterList)  # mutable....
135        for x in filterList:
136            if isinstance(x, type):
137                raise TypeError(
138                    f'filterList expects Filters or callables, not types themselves; got {x}')
139        # self.filters is a list of expressions that
140        # return True or False for an element for
141        # whether it should be yielded.
142        self.filters: List[FilterType] = filterList
143        self._len = None
144        self._matchingElements = None
145
146        # keep track of where we are in the parse.
147        # esp important for recursive streams...
148        if activeInformation is not None:
149            self.activeInformation = activeInformation
150        else:
151            self.activeInformation = {}  # in Py3.8 make a TypedDict
152            self.updateActiveInformation()
153
154    def _reprInternal(self):
155        streamClass = self.srcStream.__class__.__name__
156        srcStreamId = self.srcStream.id
157        try:
158            srcStreamId = hex(srcStreamId)
159        except TypeError:
160            pass
161
162        if streamClass == 'Measure' and self.srcStream.number != 0:
163            srcStreamId = 'm.' + str(self.srcStream.number)
164
165        return f'for {streamClass}:{srcStreamId} @:{self.index}'
166
167    def __call__(self) -> _SIter:
168        '''
169        Temporary workaround to support both prior usage of `.iter`
170        and new recommended usage of `.iter()`.
171        During the period where `.iter` is still supported, even calling `.iter()`
172        (recommended) will retrieve the property `.iter` and necessitate
173        this workaround.
174
175        Returns `self` without any changes.
176
177        TODO: manage and emit DeprecationWarnings in v.8
178        TODO: remove in v.9
179        '''
180        return self
181
182    def __iter__(self):
183        self.reset()
184        return self
185
186    def __next__(self) -> base.Music21Object:
187        while self.index < self.streamLength:
188            if self.index >= self.elementsLength:
189                self.iterSection = '_endElements'
190                self.sectionIndex = self.index - self.elementsLength
191            else:
192                self.sectionIndex = self.index
193
194            try:
195                e = self.srcStreamElements[self.index]
196            except IndexError:
197                # this may happen if the number of elements has changed
198                self.index += 1
199                continue
200
201            self.index += 1
202            if self.matchesFilters(e) is False:
203                continue
204
205            if self.restoreActiveSites is True:
206                self.srcStream.coreSelfActiveSite(e)
207
208            self.updateActiveInformation()
209            return e
210
211        self.cleanup()
212        raise StopIteration
213
214    def __getattr__(self, attr):
215        '''
216        In case an attribute is defined on Stream but not on a StreamIterator,
217        create a Stream and then return that attribute.  This is NOT performance
218        optimized -- calling this repeatedly will mean creating a lot of different
219        streams.  However, it will prevent most code that worked on v.2. from breaking
220        on v.3 and onwards.
221
222        >>> s = stream.Measure()
223        >>> s.insert(0, note.Rest())
224        >>> s.repeatAppend(note.Note('C'), 2)
225
226        >>> s.definesExplicitSystemBreaks
227        False
228
229        >>> s.notes
230        <music21.stream.iterator.StreamIterator for Measure:0x101c1a208 @:0>
231
232        >>> import warnings  #_DOCS_HIDE
233        >>> SIIW = stream.iterator.StreamIteratorInefficientWarning #_DOCS_HIDE
234        >>> with warnings.catch_warnings(): #_DOCS_HIDE
235        ...      warnings.simplefilter('ignore', SIIW) #_DOCS_HIDE
236        ...      explicit = s.notes.definesExplicitSystemBreaks #_DOCS_HIDE
237        >>> #_DOCS_SHOW explicit = s.notes.definesExplicitSystemBreaks
238        >>> explicit
239        False
240
241        Works with methods as well:
242
243        >>> with warnings.catch_warnings(): #_DOCS_HIDE
244        ...      warnings.simplefilter('ignore', SIIW) #_DOCS_HIDE
245        ...      popC = s.notes.pop(0) #_DOCS_HIDE
246        >>> #_DOCS_SHOW popC = s.notes.pop(0)
247        >>> popC
248        <music21.note.Note C>
249
250        But remember that a new Stream is being created each time that an attribute
251        only defined on a Stream is called, so for instance, so you can pop() forever,
252        always getting the same element.
253
254        >>> with warnings.catch_warnings(): #_DOCS_HIDE
255        ...      warnings.simplefilter('ignore', SIIW) #_DOCS_HIDE
256        ...      popC = s.notes.pop(0) #_DOCS_HIDE
257        >>> #_DOCS_SHOW popC = s.notes.pop(0)
258        >>> popC
259        <music21.note.Note C>
260        >>> with warnings.catch_warnings(): #_DOCS_HIDE
261        ...      warnings.simplefilter('ignore', SIIW) #_DOCS_HIDE
262        ...      popC = s.notes.pop(0) #_DOCS_HIDE
263        >>> #_DOCS_SHOW popC = s.notes.pop(0)
264        >>> popC
265        <music21.note.Note C>
266        >>> with warnings.catch_warnings(): #_DOCS_HIDE
267        ...      warnings.simplefilter('ignore', SIIW) #_DOCS_HIDE
268        ...      popC = s.notes.pop(0) #_DOCS_HIDE
269        >>> #_DOCS_SHOW popC = s.notes.pop(0)
270        >>> popC
271        <music21.note.Note C>
272
273        If run with -w, this call will send a StreamIteratorInefficientWarning to stderr
274        reminding developers that this is not an efficient call, and .stream() should be
275        called (and probably cached) explicitly.
276
277        Failures are explicitly given as coming from the StreamIterator object.
278
279        >>> s.asdf
280        Traceback (most recent call last):
281        AttributeError: 'Measure' object has no attribute 'asdf'
282        >>> s.notes.asdf
283        Traceback (most recent call last):
284        AttributeError: 'StreamIterator' object has no attribute 'asdf'
285
286        OMIT_FROM_DOCS
287
288        srcStream is accessible, but not with "__getattr__", which joblib uses
289
290        >>> s.notes.srcStream is s
291        True
292        >>> s.notes.__getattr__('srcStream') is None
293        True
294        '''
295        # Prevent infinite loop in feature extractor task serialization
296        # TODO: investigate if this can be removed once iter becomes iter()
297        if attr == 'srcStream':
298            return None
299
300        if not hasattr(self.srcStream, attr):
301            # original stream did not have the attribute, so new won't; but raise on iterator.
302            raise AttributeError(f'{self.__class__.__name__!r} object has no attribute {attr!r}')
303
304        warnings.warn(
305            attr + ' is not defined on StreamIterators. Call .stream() first for efficiency',
306            StreamIteratorInefficientWarning,
307            stacklevel=2)
308
309        sOut = self.stream()
310        return getattr(sOut, attr)
311
312    def __getitem__(self, k):
313        '''
314        if you are in the iterator, you should still be able to request other items...
315        uses self.srcStream.__getitem__
316
317        >>> s = stream.Stream()
318        >>> s.insert(0, note.Note('F#'))
319        >>> s.repeatAppend(note.Note('C'), 2)
320        >>> sI = s.iter()
321        >>> sI
322        <music21.stream.iterator.StreamIterator for Stream:0x104743be0 @:0>
323
324        >>> sI.srcStream is s
325        True
326
327        >>> for n in sI:
328        ...    printer = (repr(n), repr(sI[0]))
329        ...    print(printer)
330        ('<music21.note.Note F#>', '<music21.note.Note F#>')
331        ('<music21.note.Note C>', '<music21.note.Note F#>')
332        ('<music21.note.Note C>', '<music21.note.Note F#>')
333        >>> sI.srcStream is s
334        True
335
336        Slices work:
337
338        >>> nSlice = sI[1:]
339        >>> for n in nSlice:
340        ...     print(n)
341        <music21.note.Note C>
342        <music21.note.Note C>
343
344        Filters, such as "notes" apply.
345
346        >>> s.insert(0, clef.TrebleClef())
347        >>> s[0]
348        <music21.clef.TrebleClef>
349        >>> s.iter().notes[0]
350        <music21.note.Note F#>
351
352        Demo of cleanupOnStop = True
353
354        >>> sI.cleanupOnStop = True
355        >>> for n in sI:
356        ...    printer = (repr(n), repr(sI[0]))
357        ...    print(printer)
358        ('<music21.note.Note F#>', '<music21.note.Note F#>')
359        ('<music21.note.Note C>', '<music21.note.Note F#>')
360        ('<music21.note.Note C>', '<music21.note.Note F#>')
361        >>> sI.srcStream is None
362        True
363        >>> for n in sI:
364        ...    printer = (repr(n), repr(sI[0]))
365        ...    print(printer)
366
367        (nothing is printed)
368        '''
369        fe = self.matchingElements()
370        try:
371            e = fe[k]
372        except TypeError:
373            e = None
374            for el in fe:
375                if el.id.lower() == k.lower():
376                    e = el
377                    break
378        # TODO: Slices and everything else in Stream __getitem__ ; in fact, merge...
379        return e
380
381    def __len__(self) -> int:
382        '''
383        returns the length of the elements that
384        match the filter set.
385
386        >>> s = converter.parse('tinynotation: 3/4 c4 d e f g a', makeNotation=False)
387        >>> len(s)
388        7
389        >>> len(s.iter())
390        7
391        >>> len(s.iter().notes)
392        6
393        >>> [n.name for n in s.iter().notes]
394        ['C', 'D', 'E', 'F', 'G', 'A']
395        '''
396        if self._len is not None:
397            return self._len
398        self._len = len(self.matchingElements(restoreActiveSites=False))
399        self.reset()
400        return self._len
401
402    def __bool__(self) -> bool:
403        '''
404        return True if anything matches the filter
405        otherwise, return False
406
407        >>> s = converter.parse('tinyNotation: 2/4 c4 r4')
408        >>> bool(s)
409        True
410        >>> iterator = s.recurse()
411        >>> bool(iterator)
412        True
413        >>> bool(iterator.notesAndRests)
414        True
415        >>> bool(iterator.notes)
416        True
417
418        test cache
419
420        >>> len(iterator.notes)
421        1
422        >>> bool(iterator.notes)
423        True
424        >>> bool(iterator.notes)
425        True
426
427        >>> iterator = s.recurse()
428        >>> bool(iterator)
429        True
430        >>> bool(iterator)
431        True
432        >>> bool(iterator)
433        True
434
435        >>> bool(iterator.getElementsByClass('Chord'))
436        False
437
438        test false cache:
439
440        >>> len(iterator.getElementsByClass('Chord'))
441        0
442        >>> bool(iterator.getElementsByClass('Chord'))
443        False
444
445        '''
446        if self._len is not None:
447            return bool(self._len)
448
449        # do not change active site of first element in bool
450        with tempAttribute(self, 'restoreActiveSites', False):
451            for unused in self:
452                return True
453
454        return False
455
456    def clone(self: _SIter) -> _SIter:
457        '''
458        Returns a new copy of the same iterator.
459        (a shallow copy of some things except activeInformation)
460        '''
461        out: _SIter = type(self)(
462            self.srcStream,
463            filterList=copy.copy(self.filters),
464            restoreActiveSites=self.restoreActiveSites,
465            activeInformation=copy.copy(self.activeInformation),
466        )
467        return out
468
469    def first(self) -> Optional[base.Music21Object]:
470        '''
471        Efficiently return the first matching element, or None if no
472        elements match.
473
474        Does not require creating the whole list of matching elements.
475
476        >>> s = converter.parse('tinyNotation: 3/4 D4 E2 F4 r2 G2 r4')
477        >>> s.recurse().notes.first()
478        <music21.note.Note D>
479        >>> s.recurse().getElementsByClass('Rest').first()
480        <music21.note.Rest half>
481
482        If no elements match, returns None:
483
484        >>> print(s.recurse().getElementsByClass('Chord').first())
485        None
486
487        New in v7.
488
489        OMIT_FROM_DOCS
490
491        Ensure that next continues after the first note running:
492
493        >>> notes = s.recurse().notes
494        >>> notes.first()
495        <music21.note.Note D>
496        >>> next(notes)
497        <music21.note.Note E>
498
499        Now reset on new iteration:
500
501        >>> for n in notes:
502        ...     print(n)
503        <music21.note.Note D>
504        <music21.note.Note E>
505        ...
506
507        An Empty stream:
508
509        >>> s = stream.Stream()
510        >>> s.iter().notes.first() is None
511        True
512        '''
513        iter(self)
514        try:
515            return next(self)
516        except StopIteration:
517            return None
518
519    def last(self) -> Optional[base.Music21Object]:
520        '''
521        Returns the last matching element, or None if no elements match.
522
523        Currently is not efficient (does not iterate backwards, for instance),
524        but easier than checking for an IndexError.  Might be refactored later
525        to iterate the stream backwards instead if it gets a lot of use.
526
527        >>> s = converter.parse('tinyNotation: 3/4 D4 E2 F4 r2 G2 r4')
528        >>> s.recurse().notes.last()
529        <music21.note.Note G>
530        >>> s.recurse().getElementsByClass('Rest').last()
531        <music21.note.Rest quarter>
532
533        New in v7.
534
535        OMIT_FROM_DOCS
536
537        Check on empty Stream:
538
539        >>> s2 = stream.Stream()
540        >>> s2.iter().notes.last() is None
541        True
542
543        Next has a different feature from first(), will start again from beginning.
544        This behavior may change.
545
546        >>> notes = s.recurse().notes
547        >>> notes.last()
548        <music21.note.Note G>
549        >>> next(notes)
550        <music21.note.Note D>
551        '''
552        fe = self.matchingElements()
553        if not fe:
554            return None
555        return fe[-1]
556
557    # ---------------------------------------------------------------
558    # start and stop
559    def updateActiveInformation(self):
560        '''
561        Updates the (shared) activeInformation dictionary
562        with information about
563        where we are.
564
565        Call before any element return
566        '''
567        ai = self.activeInformation
568        ai['stream'] = self.srcStream
569        ai['index'] = self.index - 1
570        ai['iterSection'] = self.iterSection
571        ai['sectionIndex'] = self.sectionIndex
572
573    def reset(self):
574        '''
575        reset prior to iteration
576        '''
577        self.index = 0
578        self.iterSection = '_elements'
579        self.updateActiveInformation()
580        for f in self.filters:
581            if hasattr(f, 'reset'):
582                f.reset()
583
584    def resetCaches(self):
585        '''
586        reset any cached data. -- do not use this at
587        the start of iteration since we might as well
588        save this information. But do call it if
589        the filter changes.
590        '''
591        self._len = None
592        self._matchingElements = None
593
594    def cleanup(self):
595        '''
596        stop iteration; and cleanup if need be.
597        '''
598        if self.cleanupOnStop is not False:
599            self.reset()
600
601            del self.srcStream
602            del self.srcStreamElements
603            self.srcStream = None
604            self.srcStreamElements = ()
605
606    # ---------------------------------------------------------------
607    # getting items
608
609    def matchingElements(self, *, restoreActiveSites: bool = True):
610        '''
611        returns a list of elements that match the filter.
612
613        This sort of defeats the point of using a generator, so only used if
614        it's requested by __len__ or __getitem__ etc.
615
616        Subclasses should override to cache anything they need saved (index,
617        recursion objects, etc.)
618
619        activeSite will not be set.
620
621        Cached for speed.
622
623        >>> s = converter.parse('tinynotation: 3/4 c4 d e f g a', makeNotation=False)
624        >>> s.id = 'tn3/4'
625        >>> sI = s.iter()
626        >>> sI
627        <music21.stream.iterator.StreamIterator for Part:tn3/4 @:0>
628
629        >>> sI.matchingElements()
630        [<music21.meter.TimeSignature 3/4>, <music21.note.Note C>, <music21.note.Note D>,
631         <music21.note.Note E>, <music21.note.Note F>, <music21.note.Note G>,
632         <music21.note.Note A>]
633
634        >>> sI_notes = sI.notes
635        >>> sI_notes
636        <music21.stream.iterator.StreamIterator for Part:tn3/4 @:0>
637
638        Adding a filter to the Stream iterator returns a new Stream iterator; it
639        does not change the original.
640
641        >>> sI_notes is sI
642        False
643
644        >>> sI.filters
645        []
646
647        >>> sI_notes.filters
648        [<music21.stream.filters.ClassFilter NotRest>]
649
650        >>> sI_notes.matchingElements()
651        [<music21.note.Note C>, <music21.note.Note D>,
652         <music21.note.Note E>, <music21.note.Note F>, <music21.note.Note G>,
653         <music21.note.Note A>]
654
655        If restoreActiveSites is False then the elements will not have
656        their activeSites changed (callers should use it when they do not plan to actually
657        expose the elements to users, such as in `__len__`).
658
659        Added in v7. -- restoreActiveSites
660        '''
661        if self._matchingElements is not None:
662            return self._matchingElements
663
664        with saveAttributes(self, 'restoreActiveSites', 'index'):
665            self.restoreActiveSites = restoreActiveSites
666            me = [x for x in self]  # pylint: disable=unnecessary-comprehension
667            self.reset()
668
669        if restoreActiveSites == self.restoreActiveSites:
670            # cache, if we are using the iterator default.
671            self._matchingElements = me
672
673        return me
674
675    def matchesFilters(self, e):
676        '''
677        returns False if any filter returns False, True otherwise.
678        '''
679        for f in self.filters:
680            f: Union[Callable, filters.StreamFilter]
681            try:
682                try:
683                    if f(e, self) is False:
684                        return False
685                except TypeError:  # one element filters are acceptable.
686                    f: Callable
687                    if f(e) is False:
688                        return False
689            except StopIteration:  # pylint: disable=try-except-raise
690                raise  # clearer this way to see that this can happen...
691        return True
692
693    def _newBaseStream(self):
694        '''
695        Returns a new stream.Stream.  The same thing as calling:
696
697        >>> s = stream.Stream()
698
699        So why does this exist?  Since we can't import "music21.stream" here,
700        we will look in `srcStream.__class__.mro()` for the Stream
701        object to import.
702
703        This is used in places where returnStreamSubclass is False, so we
704        cannot just call `type(StreamIterator.srcStream)()`
705
706        >>> p = stream.Part()
707        >>> pi = p.iter()
708        >>> s = pi._newBaseStream()
709        >>> s
710        <music21.stream.Stream 0x1047eb2e8>
711
712        >>> pi.srcStream = note.Note()
713        >>> pi._newBaseStream()
714        Traceback (most recent call last):
715        music21.stream.iterator.StreamIteratorException: ...
716        '''
717        StreamBase = None
718        for x in self.srcStream.__class__.mro():
719            if x.__name__ == 'Stream':
720                StreamBase = x
721                break
722
723        try:
724            return StreamBase()
725        except TypeError:  # 'NoneType' object is not callable.
726            raise StreamIteratorException(
727                f"You've given a 'stream' that is not a stream! {self.srcStream}")
728
729    def stream(self, returnStreamSubClass=True):
730        '''
731        return a new stream from this iterator.
732
733        Does nothing except copy if there are no filters, but a drop in
734        replacement for the old .getElementsByClass() etc. if it does.
735
736        In other words:
737
738        `s.getElementsByClass()` == `s.iter().getElementsByClass().stream()`
739
740        >>> s = stream.Part()
741        >>> s.insert(0, note.Note('C'))
742        >>> s.append(note.Rest())
743        >>> s.append(note.Note('D'))
744        >>> b = bar.Barline()
745        >>> s.storeAtEnd(b)
746
747        >>> s2 = s.iter().getElementsByClass('Note').stream()
748        >>> s2.show('t')
749        {0.0} <music21.note.Note C>
750        {2.0} <music21.note.Note D>
751        >>> s2.derivation.method
752        'getElementsByClass'
753        >>> s2
754        <music21.stream.Part ...>
755
756        >>> s3 = s.iter().stream()
757        >>> s3.show('t')
758        {0.0} <music21.note.Note C>
759        {1.0} <music21.note.Rest quarter>
760        {2.0} <music21.note.Note D>
761        {3.0} <music21.bar.Barline type=regular>
762
763        >>> s3.elementOffset(b, returnSpecial=True)
764        <OffsetSpecial.AT_END>
765
766        >>> s4 = s.iter().getElementsByClass('Barline').stream()
767        >>> s4.show('t')
768        {0.0} <music21.bar.Barline type=regular>
769
770
771        Note that this routine can create Streams that have elements that the original
772        stream did not, in the case of recursion:
773
774        >>> bach = corpus.parse('bwv66.6')
775        >>> bn = bach.flatten()[30]
776        >>> bn
777        <music21.note.Note E>
778
779        >>> bn in bach
780        False
781        >>> bfn = bach.recurse().notes.stream()
782        >>> bn in bfn
783        True
784        >>> bn.getOffsetBySite(bfn)
785        2.0
786        >>> bn.getOffsetInHierarchy(bach)
787        2.0
788
789        OMIT_FROM_DOCS
790
791        >>> s4._endElements[0] is b
792        True
793        '''
794        ss = self.srcStream
795
796        # if this stream was sorted, the resultant stream is sorted
797        clearIsSorted = False
798
799        if returnStreamSubClass is True:
800            try:
801                found = ss.__class__()
802            except TypeError:
803                found = self._newBaseStream()
804        else:
805            found = self._newBaseStream()
806
807        found.mergeAttributes(ss)
808        found.derivation.origin = ss
809        if self.overrideDerivation is not None:
810            found.derivation.method = self.overrideDerivation
811        else:
812            derivationMethods = []
813            for f in self.filters:
814                if hasattr(f, 'derivationStr'):
815                    dStr = f.derivationStr
816                else:
817                    dStr = f.__name__  # function; lambda returns <lambda>
818                derivationMethods.append(dStr)
819            found.derivation.method = '.'.join(derivationMethods)
820
821        fe = self.matchingElements()
822        for e in fe:
823            try:
824                o = ss.elementOffset(e, returnSpecial=True)
825            except SitesException:
826                # this can happen in the case of, s.recurse().notes.stream() -- need to do new
827                # stream...
828                o = e.getOffsetInHierarchy(ss)
829                clearIsSorted = True  # now the stream is probably not sorted...
830
831            if not isinstance(o, str):
832                found.coreInsert(o, e, ignoreSort=True)
833            else:
834                if o == OffsetSpecial.AT_END:
835                    found.coreStoreAtEnd(e)
836                else:
837                    # TODO: something different...
838                    found.coreStoreAtEnd(e)
839
840        if fe:
841            found.coreElementsChanged(clearIsSorted=clearIsSorted)
842
843        return found
844
845    @property
846    def activeElementList(self):
847        '''
848        returns the element list ('_elements' or '_endElements')
849        for the current activeInformation
850        '''
851        return getattr(self.activeInformation['stream'], self.activeInformation['iterSection'])
852
853    # ------------------------------------------------------------
854
855    def addFilter(self: _SIter, newFilter, *, returnClone=True) -> _SIter:
856        '''
857        Return a new StreamIterator with an additional filter.
858        Also resets caches -- so do not add filters any other way.
859
860        If returnClone is False then adds without creating a new StreamIterator
861
862        Changed in v.6 -- Encourage creating new StreamIterators: change
863        default to return a new StreamIterator.
864        '''
865        if returnClone:
866            out = self.clone()
867        else:
868            out = self
869
870        out.resetCaches()
871        for f in out.filters:
872            if newFilter == f:
873                return out
874        out.filters.append(newFilter)
875
876        return out
877
878    def removeFilter(self: _SIter, oldFilter, *, returnClone=True) -> _SIter:
879        '''
880        Return a new StreamIterator where oldFilter is removed.
881        '''
882        if returnClone:
883            out = self.clone()
884        else:
885            out = self
886
887        out.resetCaches()
888        if oldFilter in out.filters:
889            out.filters.pop(out.filters.index(oldFilter))
890
891        return out
892
893    def getElementById(self, elementId):
894        '''
895        Returns a single element (or None) that matches elementId.
896
897        If chaining filters, this should be the last one, as it returns an element
898
899        >>> s = stream.Stream(id='s1')
900        >>> s.append(note.Note('C'))
901        >>> r = note.Rest()
902        >>> r.id = 'restId'
903        >>> s.append(r)
904        >>> r2 = s.recurse().getElementById('restId')
905        >>> r2 is r
906        True
907        >>> r2.id
908        'restId'
909        '''
910        out = self.addFilter(filters.IdFilter(elementId))
911        for e in out:
912            return e
913        return None
914
915    def getElementsByClass(self, classFilterList, *, returnClone=True):
916        '''
917        Add a filter to the Iterator to remove all elements
918        except those that match one
919        or more classes in the `classFilterList`. A single class
920        can also used for the `classFilterList` parameter instead of a List.
921
922        >>> s = stream.Stream(id='s1')
923        >>> s.append(note.Note('C'))
924        >>> r = note.Rest()
925        >>> s.append(r)
926        >>> s.append(note.Note('D'))
927        >>> for el in s.iter().getElementsByClass('Rest'):
928        ...     print(el)
929        <music21.note.Rest quarter>
930
931
932        ActiveSite is restored...
933
934        >>> s2 = stream.Stream(id='s2')
935        >>> s2.insert(0, r)
936        >>> r.activeSite.id
937        's2'
938
939        >>> for el in s.iter().getElementsByClass('Rest'):
940        ...     print(el.activeSite.id)
941        s1
942
943
944        Classes work in addition to strings...
945
946        >>> for el in s.iter().getElementsByClass(note.Rest):
947        ...     print(el)
948        <music21.note.Rest quarter>
949
950        '''
951        return self.addFilter(filters.ClassFilter(classFilterList), returnClone=returnClone)
952
953    def getElementsByQuerySelector(self, querySelector: str, *, returnClone=True):
954        '''
955        First implementation of a query selector, similar to CSS QuerySelectors used in
956        HTML DOM:
957
958        * A leading `#` indicates the id of an element, so '#hello' will find elements
959          with `el.id=='hello'` (should only be one)
960        * A leading `.` indicates the group of an element, so '.high' will find elements
961          with `'high' in el.groups`
962        * Any other string is considered to be the type/class of the element.  So `Note`
963          will find all Note elements.  Can be fully qualified like `note.Note`
964
965        Eventually, more complex query selectors will be implemented.  This is just a start.
966
967        Setting up an example:
968
969        >>> s = converter.parse('tinyNotation: 4/4 GG4 AA4 BB4 r4 C4 D4 E4 F4 r1')
970        >>> s[note.Note].last().id = 'last'
971        >>> for n in s[note.Note]:
972        ...     if n.octave == 3:
973        ...         n.groups.append('tenor')
974
975        >>> list(s.recurse().getElementsByQuerySelector('.tenor'))
976        [<music21.note.Note C>,
977         <music21.note.Note D>,
978         <music21.note.Note E>,
979         <music21.note.Note F>]
980
981        >>> list(s.recurse().getElementsByQuerySelector('Rest'))
982        [<music21.note.Rest quarter>,
983         <music21.note.Rest whole>]
984
985        Note that unlike with stream slices, the querySelector does not do anything special
986        for id searches.  `.first()` will need to be called to find the element (if any)
987
988        >>> s.recurse().getElementsByQuerySelector('#last').first()
989        <music21.note.Note F>
990
991        New in v.7
992        '''
993        if querySelector.startswith('#'):
994            return self.addFilter(filters.IdFilter(querySelector[1:]), returnClone=returnClone)
995        if querySelector.startswith('.'):
996            return self.addFilter(filters.GroupFilter(querySelector[1:]), returnClone=returnClone)
997        return self.addFilter(filters.ClassFilter(querySelector), returnClone=returnClone)
998
999
1000    def getElementsNotOfClass(self, classFilterList, *, returnClone=True):
1001        '''
1002        Adds a filter, removing all Elements that do not
1003        match the one or more classes in the `classFilterList`.
1004
1005        In lieu of a list, a single class can be used as the `classFilterList` parameter.
1006
1007        >>> a = stream.Stream()
1008        >>> a.repeatInsert(note.Rest(), range(10))
1009        >>> for x in range(4):
1010        ...     n = note.Note('G#')
1011        ...     n.offset = x * 3
1012        ...     a.insert(n)
1013        >>> found = a.iter().getElementsNotOfClass(note.Note)
1014        >>> len(found)
1015        10
1016        >>> found = a.iter().getElementsNotOfClass('Rest')
1017        >>> len(found)
1018        4
1019        >>> found = a.iter().getElementsNotOfClass(['Note', 'Rest'])
1020        >>> len(found)
1021        0
1022
1023        >>> b = stream.Stream()
1024        >>> b.repeatInsert(note.Rest(), range(15))
1025        >>> a.insert(b)
1026
1027        >>> found = a.recurse().getElementsNotOfClass([note.Rest, 'Stream'])
1028        >>> len(found)
1029        4
1030        >>> found = a.recurse().getElementsNotOfClass([note.Note, 'Stream'])
1031        >>> len(found)
1032        25
1033        '''
1034        return self.addFilter(filters.ClassNotFilter(classFilterList), returnClone=returnClone)
1035
1036    def getElementsByGroup(self, groupFilterList, *, returnClone=True):
1037        '''
1038        >>> n1 = note.Note('C')
1039        >>> n1.groups.append('trombone')
1040        >>> n2 = note.Note('D')
1041        >>> n2.groups.append('trombone')
1042        >>> n2.groups.append('tuba')
1043        >>> n3 = note.Note('E')
1044        >>> n3.groups.append('tuba')
1045        >>> s1 = stream.Stream()
1046        >>> s1.append(n1)
1047        >>> s1.append(n2)
1048        >>> s1.append(n3)
1049
1050        >>> tboneSubStream = s1.iter().getElementsByGroup('trombone')
1051        >>> for thisNote in tboneSubStream:
1052        ...     print(thisNote.name)
1053        C
1054        D
1055        >>> tubaSubStream = s1.iter().getElementsByGroup('tuba')
1056        >>> for thisNote in tubaSubStream:
1057        ...     print(thisNote.name)
1058        D
1059        E
1060        '''
1061        return self.addFilter(filters.GroupFilter(groupFilterList), returnClone=returnClone)
1062
1063    def getElementsByOffset(
1064        self,
1065        offsetStart,
1066        offsetEnd=None,
1067        *,
1068        includeEndBoundary=True,
1069        mustFinishInSpan=False,
1070        mustBeginInSpan=True,
1071        includeElementsThatEndAtStart=True,
1072        stopAfterEnd=True,
1073        returnClone=True,
1074    ) -> 'StreamIterator':
1075        '''
1076        Adds a filter keeping only Music21Objects that
1077        are found at a certain offset or within a certain
1078        offset time range (given the start and optional stop values).
1079
1080        There are several attributes that govern how this range is
1081        determined:
1082
1083
1084        If `mustFinishInSpan` is True then an event that begins
1085        between offsetStart and offsetEnd but which ends after offsetEnd
1086        will not be included.  The default is False.
1087
1088
1089        For instance, a half note at offset 2.0 will be found in
1090        getElementsByOffset(1.5, 2.5) or getElementsByOffset(1.5, 2.5,
1091        mustFinishInSpan = False) but not by getElementsByOffset(1.5, 2.5,
1092        mustFinishInSpan = True).
1093
1094        The `includeEndBoundary` option determines if an element
1095        begun just at the offsetEnd should be included.  For instance,
1096        the half note at offset 2.0 above would be found by
1097        getElementsByOffset(0, 2.0) or by getElementsByOffset(0, 2.0,
1098        includeEndBoundary = True) but not by getElementsByOffset(0, 2.0,
1099        includeEndBoundary = False).
1100
1101        Setting includeEndBoundary to False at the same time as
1102        mustFinishInSpan is set to True is probably NOT what you want to do
1103        unless you want to find things like clefs at the end of the region
1104        to display as courtesy clefs.
1105
1106        The `mustBeginInSpan` option determines whether notes or other
1107        objects that do not begin in the region but are still sounding
1108        at the beginning of the region are excluded.  The default is
1109        True -- that is, these notes will not be included.
1110        For instance the half note at offset 2.0 from above would not be found by
1111        getElementsByOffset(3.0, 3.5) or getElementsByOffset(3.0, 3.5,
1112        mustBeginInSpan = True) but it would be found by
1113        getElementsByOffset(3.0, 3.5, mustBeginInSpan = False)
1114
1115        Setting includeElementsThatEndAtStart to False is useful for zeroLength
1116        searches that set mustBeginInSpan == False to not catch notes that were
1117        playing before the search but that end just before the end of the search type.
1118        See the code for allPlayingWhileSounding for a demonstration.
1119
1120        This chart, and the examples below, demonstrate the various
1121        features of getElementsByOffset.  It is one of the most complex
1122        methods of music21 but also one of the most powerful, so it
1123        is worth learning at least the basics.
1124
1125            .. image:: images/getElementsByOffset.*
1126                :width: 600
1127
1128        >>> st1 = stream.Stream()
1129        >>> n0 = note.Note('C')
1130        >>> n0.duration.type = 'half'
1131        >>> n0.offset = 0
1132        >>> st1.insert(n0)
1133        >>> n2 = note.Note('D')
1134        >>> n2.duration.type = 'half'
1135        >>> n2.offset = 2
1136        >>> st1.insert(n2)
1137        >>> out1 = list(st1.iter().getElementsByOffset(2))
1138        >>> len(out1)
1139        1
1140        >>> out1[0].step
1141        'D'
1142        >>> out2 = list(st1.iter().getElementsByOffset(1, 3))
1143        >>> len(out2)
1144        1
1145        >>> out2[0].step
1146        'D'
1147        >>> out3 = list(st1.iter().getElementsByOffset(1, 3, mustFinishInSpan=True))
1148        >>> len(out3)
1149        0
1150        >>> out4 = list(st1.iter().getElementsByOffset(1, 2))
1151        >>> len(out4)
1152        1
1153        >>> out4[0].step
1154        'D'
1155        >>> out5 = list(st1.iter().getElementsByOffset(1, 2, includeEndBoundary=False))
1156        >>> len(out5)
1157        0
1158        >>> out6 = list(st1.iter().getElementsByOffset(1, 2, includeEndBoundary=False,
1159        ...                                          mustBeginInSpan=False))
1160        >>> len(out6)
1161        1
1162        >>> out6[0].step
1163        'C'
1164        >>> out7 = list(st1.iter().getElementsByOffset(1, 3, mustBeginInSpan=False))
1165        >>> len(out7)
1166        2
1167        >>> [el.step for el in out7]
1168        ['C', 'D']
1169
1170        Note, that elements that end at the start offset are included if mustBeginInSpan is False
1171
1172        >>> out8 = list(st1.iter().getElementsByOffset(2, 4, mustBeginInSpan=False))
1173        >>> len(out8)
1174        2
1175        >>> [el.step for el in out8]
1176        ['C', 'D']
1177
1178        To change this behavior set includeElementsThatEndAtStart=False
1179
1180        >>> out9 = list(st1.iter().getElementsByOffset(2, 4, mustBeginInSpan=False,
1181        ...                                          includeElementsThatEndAtStart=False))
1182        >>> len(out9)
1183        1
1184        >>> [el.step for el in out9]
1185        ['D']
1186
1187        >>> a = stream.Stream(id='a')
1188        >>> n = note.Note('G')
1189        >>> n.quarterLength = 0.5
1190        >>> a.repeatInsert(n, list(range(8)))
1191        >>> b = stream.Stream(id='b')
1192        >>> b.repeatInsert(a, [0, 3, 6])
1193        >>> c = list(b.iter().getElementsByOffset(2, 6.9))
1194        >>> len(c)
1195        2
1196        >>> c = list(b.flatten().iter().getElementsByOffset(2, 6.9))
1197        >>> len(c)
1198        10
1199
1200        Testing multiple zero-length elements with mustBeginInSpan:
1201
1202        >>> c = clef.TrebleClef()
1203        >>> ts = meter.TimeSignature('4/4')
1204        >>> ks = key.KeySignature(2)
1205        >>> s = stream.Stream()
1206        >>> s.insert(0.0, c)
1207        >>> s.insert(0.0, ts)
1208        >>> s.insert(0.0, ks)
1209        >>> len(list(s.iter().getElementsByOffset(0.0, mustBeginInSpan=True)))
1210        3
1211        >>> len(list(s.iter().getElementsByOffset(0.0, mustBeginInSpan=False)))
1212        3
1213
1214        On a :class:`~music21.stream.iterator.RecursiveIterator`,
1215        `.getElementsByOffset(0.0)`, will get everything
1216        at the start of the piece, which is useful:
1217
1218        >>> bwv66 = corpus.parse('bwv66.6')
1219        >>> list(bwv66.recurse().getElementsByOffset(0.0))
1220        [<music21.metadata.Metadata object at 0x10a32f490>,
1221         <music21.stream.Part Soprano>,
1222         <music21.instrument.Instrument 'P1: Soprano: Instrument 1'>,
1223         <music21.stream.Measure 0 offset=0.0>,
1224         <music21.clef.TrebleClef>,
1225         <music21.key.Key of f# minor>,
1226         <music21.meter.TimeSignature 4/4>,
1227         <music21.note.Note C#>,
1228         <music21.stream.Part Alto>,
1229         ...
1230         <music21.note.Note E>,
1231         <music21.stream.Part Tenor>,
1232         ...]
1233
1234        However, any other offset passed to `getElementsByOffset` on a
1235        `RecursiveIterator` without additional arguments, is unlikely to be useful,
1236        because the iterator ends as soon as it encounters an element
1237        with an offset beyond the `offsetEnd` point.  For instance,
1238        calling `.getElementsByOffset(1.0).notes` on a :class:`~music21.stream.Part`,
1239        in bwv66.6 only gets the note that appears at offset 1.0 of a measure that begins
1240        or includes offset 1.0.
1241        (Fortunately, this piece begins with a one-beat pickup, so there is such a note):
1242
1243        >>> soprano = bwv66.parts['Soprano']
1244        >>> for el in soprano.recurse().getElementsByOffset(1.0):
1245        ...     print(el, el.offset, el.getOffsetInHierarchy(bwv66), el.activeSite)
1246        <music21.stream.Measure 1 offset=1.0> 1.0 1.0 <music21.stream.Part Soprano>
1247        <music21.note.Note B> 1.0 2.0 <music21.stream.Measure 1 offset=1.0>
1248
1249
1250        RecursiveIterators will probably want to use
1251        :meth:`~music21.stream.iterator.RecursiveIterator.getElementsByOffsetInHierarchy`
1252        instead.  Or to get all elements with a particular local offset, such as everything
1253        on the third quarter note of a measure, use the `stopAfterEnd=False` keyword,
1254        which lets the iteration continue to search for elements even after encountering
1255        some within Streams whose offsets are greater than the end element.
1256
1257        >>> len(soprano.recurse().getElementsByOffset(2.0, stopAfterEnd=False))
1258        9
1259
1260        Changed in v5.5 -- all arguments changing behavior are keyword only.
1261        Added in v6.5 -- `stopAfterEnd` keyword.
1262
1263        OMIT_FROM_DOCS
1264
1265        Same test as above, but with floats
1266
1267        >>> out1 = list(st1.iter().getElementsByOffset(2.0))
1268        >>> len(out1)
1269        1
1270        >>> out1[0].step
1271        'D'
1272        >>> out2 = list(st1.iter().getElementsByOffset(1.0, 3.0))
1273        >>> len(out2)
1274        1
1275        >>> out2[0].step
1276        'D'
1277        >>> out3 = list(st1.iter().getElementsByOffset(1.0, 3.0, mustFinishInSpan=True))
1278        >>> len(out3)
1279        0
1280        >>> out3b = list(st1.iter().getElementsByOffset(0.0, 3.001, mustFinishInSpan=True))
1281        >>> len(out3b)
1282        1
1283        >>> out3b[0].step
1284        'C'
1285        >>> out3b = list(st1.iter().getElementsByOffset(1.0, 3.001, mustFinishInSpan=True,
1286        ...                                           mustBeginInSpan=False))
1287        >>> len(out3b)
1288        1
1289        >>> out3b[0].step
1290        'C'
1291
1292        >>> out4 = list(st1.iter().getElementsByOffset(1.0, 2.0))
1293        >>> len(out4)
1294        1
1295        >>> out4[0].step
1296        'D'
1297        >>> out5 = list(st1.iter().getElementsByOffset(1.0, 2.0, includeEndBoundary=False))
1298        >>> len(out5)
1299        0
1300        >>> out6 = list(st1.iter().getElementsByOffset(1.0, 2.0, includeEndBoundary=False,
1301        ...                                          mustBeginInSpan=False))
1302        >>> len(out6)
1303        1
1304        >>> out6[0].step
1305        'C'
1306        >>> out7 = list(st1.iter().getElementsByOffset(1.0, 3.0, mustBeginInSpan=False))
1307        >>> len(out7)
1308        2
1309        >>> [el.step for el in out7]
1310        ['C', 'D']
1311        '''
1312        return self.addFilter(
1313            filters.OffsetFilter(
1314                offsetStart,
1315                offsetEnd,
1316                includeEndBoundary=includeEndBoundary,
1317                mustFinishInSpan=mustFinishInSpan,
1318                mustBeginInSpan=mustBeginInSpan,
1319                includeElementsThatEndAtStart=includeElementsThatEndAtStart,
1320                stopAfterEnd=stopAfterEnd,
1321            ),
1322            returnClone=returnClone
1323        )
1324
1325    # ------------------------------------------------------------
1326    # properties -- historical...
1327
1328    @property
1329    def notes(self):
1330        '''
1331        Returns all NotRest objects
1332
1333        (will sometime become simply Note and Chord objects...)
1334
1335        >>> s = stream.Stream()
1336        >>> s.append(note.Note('C'))
1337        >>> s.append(note.Rest())
1338        >>> s.append(note.Note('D'))
1339        >>> for el in s.iter().notes:
1340        ...     print(el)
1341        <music21.note.Note C>
1342        <music21.note.Note D>
1343        '''
1344        return self.addFilter(filters.ClassFilter('NotRest'))
1345
1346    @property
1347    def notesAndRests(self):
1348        '''
1349        Returns all GeneralNote objects
1350
1351        >>> s = stream.Stream()
1352        >>> s.append(meter.TimeSignature('4/4'))
1353        >>> s.append(note.Note('C'))
1354        >>> s.append(note.Rest())
1355        >>> s.append(note.Note('D'))
1356        >>> for el in s.iter().notesAndRests:
1357        ...     print(el)
1358        <music21.note.Note C>
1359        <music21.note.Rest quarter>
1360        <music21.note.Note D>
1361
1362        chained filters... (this makes no sense since notes is a subset of notesAndRests)
1363
1364        >>> for el in s.iter().notesAndRests.notes:
1365        ...     print(el)
1366        <music21.note.Note C>
1367        <music21.note.Note D>
1368        '''
1369        return self.addFilter(filters.ClassFilter('GeneralNote'))
1370
1371    @property
1372    def parts(self):
1373        '''
1374        Adds a ClassFilter for Part objects
1375        '''
1376        return self.addFilter(filters.ClassFilter('Part'))
1377
1378    @property
1379    def spanners(self):
1380        '''
1381        Adds a ClassFilter for Spanner objects
1382        '''
1383        return self.addFilter(filters.ClassFilter('Spanner'))
1384
1385    @property
1386    def variants(self):
1387        '''
1388        Deprecated in version 7
1389
1390        Adds a ClassFilter for Variant
1391        '''
1392        return self.addFilter(filters.ClassFilter('Variant'))
1393
1394    @property
1395    def voices(self):
1396        '''
1397        Adds a ClassFilter for Voice objects
1398        '''
1399        return self.addFilter(filters.ClassFilter('Voice'))
1400
1401
1402# -----------------------------------------------------------------------------
1403class OffsetIterator(StreamIterator):
1404    '''
1405    An iterator that with each iteration returns a list of elements
1406    that are at the same offset (or all at end)
1407
1408    >>> s = stream.Stream()
1409    >>> s.insert(0, note.Note('C'))
1410    >>> s.insert(0, note.Note('D'))
1411    >>> s.insert(1, note.Note('E'))
1412    >>> s.insert(2, note.Note('F'))
1413    >>> s.insert(2, note.Note('G'))
1414    >>> s.storeAtEnd(bar.Repeat('end'))
1415    >>> s.storeAtEnd(clef.TrebleClef())
1416
1417    >>> oIter = stream.iterator.OffsetIterator(s)
1418    >>> for groupedElements in oIter:
1419    ...     print(groupedElements)
1420    [<music21.note.Note C>, <music21.note.Note D>]
1421    [<music21.note.Note E>]
1422    [<music21.note.Note F>, <music21.note.Note G>]
1423    [<music21.bar.Repeat direction=end>, <music21.clef.TrebleClef>]
1424
1425    Does it work again?
1426
1427    >>> for groupedElements2 in oIter:
1428    ...     print(groupedElements2)
1429    [<music21.note.Note C>, <music21.note.Note D>]
1430    [<music21.note.Note E>]
1431    [<music21.note.Note F>, <music21.note.Note G>]
1432    [<music21.bar.Repeat direction=end>, <music21.clef.TrebleClef>]
1433
1434
1435    >>> for groupedElements in oIter.notes:
1436    ...     print(groupedElements)
1437    [<music21.note.Note C>, <music21.note.Note D>]
1438    [<music21.note.Note E>]
1439    [<music21.note.Note F>, <music21.note.Note G>]
1440
1441    >>> for groupedElements in stream.iterator.OffsetIterator(s).getElementsByClass('Clef'):
1442    ...     print(groupedElements)
1443    [<music21.clef.TrebleClef>]
1444    '''
1445
1446    def __init__(self,
1447                 srcStream,
1448                 *,
1449                 filterList=None,
1450                 restoreActiveSites=True,
1451                 activeInformation=None,
1452                 ignoreSorting=False
1453                 ):
1454        super().__init__(srcStream,
1455                         filterList=filterList,
1456                         restoreActiveSites=restoreActiveSites,
1457                         activeInformation=activeInformation,
1458                         ignoreSorting=ignoreSorting,
1459                         )
1460        self.raiseStopIterationNext = False
1461        self.nextToYield = []
1462        self.nextOffsetToYield = None
1463
1464    def __next__(self) -> List[base.Music21Object]:
1465        if self.raiseStopIterationNext:
1466            raise StopIteration
1467
1468        retElementList = None
1469        # make sure that cleanup is not called during the loop...
1470        try:
1471            if self.nextToYield:
1472                retElementList = self.nextToYield
1473                retElOffset = self.nextOffsetToYield
1474            else:
1475                retEl = super().__next__()
1476                retElOffset = self.srcStream.elementOffset(retEl)
1477                retElementList = [retEl]
1478
1479            while self.index <= self.streamLength:
1480                nextEl = super().__next__()
1481                nextElOffset = self.srcStream.elementOffset(nextEl)
1482                if nextElOffset == retElOffset:
1483                    retElementList.append(nextEl)
1484                else:
1485                    self.nextToYield = [nextEl]
1486                    self.nextOffsetToYield = nextElOffset
1487                    return retElementList
1488
1489        except StopIteration:
1490            if retElementList:
1491                self.raiseStopIterationNext = True
1492                return retElementList
1493            else:
1494                raise StopIteration
1495
1496    def reset(self):
1497        '''
1498        runs before iteration
1499        '''
1500        super().reset()
1501        self.nextToYield = []
1502        self.nextOffsetToYield = None
1503        self.raiseStopIterationNext = False
1504
1505
1506# -----------------------------------------------------------------------------
1507class RecursiveIterator(StreamIterator):
1508    '''
1509    One of the most powerful iterators in music21.  Generally not called
1510    directly, but created by being invoked on a stream with `Stream.recurse()`
1511
1512    >>> b = corpus.parse('bwv66.6')
1513    >>> ri = stream.iterator.RecursiveIterator(b, streamsOnly=True)
1514    >>> for x in ri:
1515    ...     print(x)
1516    <music21.stream.Part Soprano>
1517    <music21.stream.Measure 0 offset=0.0>
1518    <music21.stream.Measure 1 offset=1.0>
1519    <music21.stream.Measure 2 offset=5.0>
1520    ...
1521    <music21.stream.Part Alto>
1522    <music21.stream.Measure 0 offset=0.0>
1523    ...
1524    <music21.stream.Part Tenor>
1525    ...
1526    <music21.stream.Part Bass>
1527    ...
1528
1529    But this is how you'll actually use it:
1530
1531    >>> for x in b.recurse(streamsOnly=True, includeSelf=True):
1532    ...     print(x)
1533    <music21.stream.Score 0x10484fd68>
1534    <music21.stream.Part Soprano>
1535    <music21.stream.Measure 0 offset=0.0>
1536    <music21.stream.Measure 1 offset=1.0>
1537    <music21.stream.Measure 2 offset=5.0>
1538    ...
1539    <music21.stream.Part Alto>
1540    <music21.stream.Measure 0 offset=0.0>
1541    ...
1542    <music21.stream.Part Tenor>
1543    ...
1544    <music21.stream.Part Bass>
1545    ...
1546
1547    >>> hasExpressions = lambda el, i: True if (hasattr(el, 'expressions')
1548    ...       and el.expressions) else False
1549    >>> expressive = b.recurse().addFilter(hasExpressions)
1550    >>> expressive
1551    <music21.stream.iterator.RecursiveIterator for Score:0x10487f550 @:0>
1552
1553    >>> for el in expressive:
1554    ...     print(el, el.expressions)
1555    <music21.note.Note C#> [<music21.expressions.Fermata>]
1556    <music21.note.Note A> [<music21.expressions.Fermata>]
1557    <music21.note.Note F#> [<music21.expressions.Fermata>]
1558    <music21.note.Note C#> [<music21.expressions.Fermata>]
1559    <music21.note.Note G#> [<music21.expressions.Fermata>]
1560    <music21.note.Note F#> [<music21.expressions.Fermata>]
1561
1562    >>> len(expressive)
1563    6
1564    >>> expressive[-1].measureNumber
1565    9
1566    >>> bool(expressive)
1567    True
1568    '''
1569
1570    def __init__(self,
1571                 srcStream,
1572                 *,
1573                 filterList=None,
1574                 restoreActiveSites=True,
1575                 activeInformation=None,
1576                 streamsOnly=False,
1577                 includeSelf=False,
1578                 ignoreSorting=False
1579                 ):  # , parentIterator=None):
1580        super().__init__(srcStream,
1581                         filterList=filterList,
1582                         restoreActiveSites=restoreActiveSites,
1583                         activeInformation=activeInformation,
1584                         ignoreSorting=ignoreSorting,
1585                         )
1586        if 'lastYielded' not in self.activeInformation:
1587            self.activeInformation['lastYielded'] = None
1588
1589        self.returnSelf = includeSelf
1590        self.includeSelf = includeSelf
1591        self.ignoreSorting = ignoreSorting
1592
1593        # within the list of parent/child recursive iterators, where does this start?
1594        self.iteratorStartOffsetInHierarchy = 0.0
1595
1596        if streamsOnly is True:
1597            self.filters.append(filters.ClassFilter('Stream'))
1598        self.childRecursiveIterator = None
1599        # not yet used.
1600        # self.parentIterator = None
1601
1602    def __next__(self) -> base.Music21Object:
1603        '''
1604        Get the next element of the stream under iteration.
1605
1606        The same __iter__ as the superclass is used.
1607        '''
1608        while self.index < self.streamLength:
1609            # wrap this in a while loop instead of
1610            # returning self.__next__() because
1611            # in a long score with a miserly filter
1612            # it is possible to exceed maximum recursion
1613            # depth
1614            if self.childRecursiveIterator is not None:
1615                try:
1616                    return next(self.childRecursiveIterator)
1617                except StopIteration:
1618                    # self.childRecursiveIterator.parentIterator = None
1619                    self.childRecursiveIterator = None
1620
1621            if self.returnSelf is True and self.matchesFilters(self.srcStream):
1622                self.activeInformation['stream'] = None
1623                self.activeInformation['index'] = -1
1624                self.activeInformation['lastYielded'] = self.srcStream
1625                self.returnSelf = False
1626                return self.srcStream
1627
1628            elif self.returnSelf is True:
1629                self.returnSelf = False
1630
1631            if self.index >= self.elementsLength:
1632                self.iterSection = '_endElements'
1633                self.sectionIndex = self.index - self.elementsLength
1634            else:
1635                self.sectionIndex = self.index
1636
1637            try:
1638                e = self.srcStreamElements[self.index]
1639            except IndexError:
1640                self.index += 1
1641                # this may happen if the number of elements has changed
1642                continue
1643
1644            self.index += 1
1645
1646            # in a recursive filter, the stream does not need to match the filter,
1647            # only the internal elements.
1648            if e.isStream:
1649                self.childRecursiveIterator = RecursiveIterator(
1650                    srcStream=e,
1651                    restoreActiveSites=self.restoreActiveSites,
1652                    filterList=self.filters,  # shared list...
1653                    activeInformation=self.activeInformation,  # shared dict
1654                    includeSelf=False,  # always for inner streams
1655                    ignoreSorting=self.ignoreSorting,
1656                    # parentIterator=self,
1657                )
1658                newStartOffset = (self.iteratorStartOffsetInHierarchy
1659                                  + self.srcStream.elementOffset(e))
1660                self.childRecursiveIterator.iteratorStartOffsetInHierarchy = newStartOffset
1661            if self.matchesFilters(e) is False:
1662                continue
1663
1664            if self.restoreActiveSites is True:
1665                self.srcStream.coreSelfActiveSite(e)
1666
1667            self.updateActiveInformation()
1668            self.activeInformation['lastYielded'] = e
1669            return e
1670
1671        # the last element can still set a recursive iterator, so make sure we handle it.
1672        if self.childRecursiveIterator is not None:
1673            try:
1674                return next(self.childRecursiveIterator)
1675            except StopIteration:
1676                # self.childRecursiveIterator.parentIterator = None
1677                self.childRecursiveIterator = None
1678
1679        self.activeInformation['lastYielded'] = None  # always clean this up, no matter what...
1680        self.cleanup()
1681        raise StopIteration
1682
1683    def reset(self):
1684        '''
1685        reset prior to iteration
1686        '''
1687        self.returnSelf = self.includeSelf
1688        self.childRecursiveIterator = None
1689        self.activeInformation['lastYielded'] = None
1690        super().reset()
1691
1692    def matchingElements(self, *, restoreActiveSites=True):
1693        # saved parent iterator later?
1694        # will this work in mid-iteration? Test, or do not expose till then.
1695        with tempAttribute(self, 'childRecursiveIterator'):
1696            fe = super().matchingElements(restoreActiveSites=restoreActiveSites)
1697        return fe
1698
1699    def iteratorStack(self):
1700        '''
1701        Returns a stack of RecursiveIterators at this point in the iteration.  Last is most recent.
1702
1703        >>> b = corpus.parse('bwv66.6')
1704        >>> bRecurse = b.recurse()
1705        >>> i = 0
1706        >>> for _ in bRecurse:
1707        ...     i += 1
1708        ...     if i > 12:
1709        ...         break
1710        >>> bRecurse.iteratorStack()
1711        [<music21.stream.iterator.RecursiveIterator for Score:0x10475cdd8 @:2>,
1712         <music21.stream.iterator.RecursiveIterator for Part:Soprano @:3>,
1713         <music21.stream.iterator.RecursiveIterator for Measure:m.1 @:3>]
1714        '''
1715        iterStack = [self]
1716        x = self
1717        while x.childRecursiveIterator is not None:
1718            x = x.childRecursiveIterator
1719            iterStack.append(x)
1720        return iterStack
1721
1722    def streamStack(self):
1723        '''
1724        Returns a stack of Streams at this point.  Last is most recent.
1725
1726        However, the current element may be the same as the last element in the stack
1727
1728        >>> b = corpus.parse('bwv66.6')
1729        >>> bRecurse = b.recurse()
1730        >>> i = 0
1731        >>> for x in bRecurse:
1732        ...     i += 1
1733        ...     if i > 12:
1734        ...         break
1735        >>> bRecurse.streamStack()
1736        [<music21.stream.Score 0x1049a0710>,
1737         <music21.stream.Part Soprano>,
1738         <music21.stream.Measure 1 offset=1.0>]
1739        '''
1740        return [i.srcStream for i in self.iteratorStack()]
1741
1742    def currentHierarchyOffset(self):
1743        '''
1744        Called on the current iterator, returns the current offset in the hierarchy.
1745        Or None if we are not currently iterating.
1746
1747        >>> b = corpus.parse('bwv66.6')
1748        >>> bRecurse = b.recurse().notes
1749        >>> print(bRecurse.currentHierarchyOffset())
1750        None
1751        >>> for n in bRecurse:
1752        ...     print(n.measureNumber, bRecurse.currentHierarchyOffset(), n)
1753        0 0.0 <music21.note.Note C#>
1754        0 0.5 <music21.note.Note B>
1755        1 1.0 <music21.note.Note A>
1756        1 2.0 <music21.note.Note B>
1757        1 3.0 <music21.note.Note C#>
1758        1 4.0 <music21.note.Note E>
1759        2 5.0 <music21.note.Note C#>
1760        ...
1761        9 34.5 <music21.note.Note E#>
1762        9 35.0 <music21.note.Note F#>
1763        0 0.0 <music21.note.Note E>
1764        1 1.0 <music21.note.Note F#>
1765        ...
1766
1767        After iteration completes, the figure is reset to None:
1768
1769        >>> print(bRecurse.currentHierarchyOffset())
1770        None
1771
1772        The offsets are with respect to the position inside the stream
1773        being iterated, so for instance, this will not change the output from above:
1774
1775        >>> o = stream.Opus()
1776        >>> o.insert(20.0, b)
1777        >>> bRecurse = b.recurse().notes
1778        >>> for n in bRecurse:
1779        ...     print(n.measureNumber, bRecurse.currentHierarchyOffset(), n)
1780        0 0.0 <music21.note.Note C#>
1781        ...
1782
1783        But of course, this will add 20.0 to all numbers:
1784
1785        >>> oRecurse = o.recurse().notes
1786        >>> for n in oRecurse:
1787        ...     print(n.measureNumber, oRecurse.currentHierarchyOffset(), n)
1788        0 20.0 <music21.note.Note C#>
1789        ...
1790
1791        New in v.4
1792        '''
1793        lastYield = self.activeInformation['lastYielded']
1794        if lastYield is None:
1795            return None
1796
1797        iteratorStack = self.iteratorStack()
1798        newestIterator = iteratorStack[-1]
1799        lastStream = newestIterator.srcStream
1800        lastStartOffset = newestIterator.iteratorStartOffsetInHierarchy
1801
1802        if lastYield is lastStream:
1803            return common.opFrac(lastStartOffset)
1804        else:
1805            return common.opFrac(lastStartOffset + lastStream.elementOffset(lastYield))
1806            # will still return numbers even if _endElements
1807
1808    def getElementsByOffsetInHierarchy(
1809            self,
1810            offsetStart,
1811            offsetEnd=None,
1812            *,
1813            includeEndBoundary=True,
1814            mustFinishInSpan=False,
1815            mustBeginInSpan=True,
1816            includeElementsThatEndAtStart=True) -> 'StreamIterator':
1817        '''
1818        Adds a filter keeping only Music21Objects that
1819        are found at a certain offset or within a certain
1820        offset time range (given the `offsetStart` and optional `offsetEnd` values) from
1821        the beginning of the hierarchy.
1822
1823        >>> b = corpus.parse('bwv66.6')
1824        >>> for n in b.recurse().getElementsByOffsetInHierarchy(8, 9.5).notes:
1825        ...     print(n,
1826        ...           n.getOffsetInHierarchy(b),
1827        ...           n.measureNumber,
1828        ...           n.getContextByClass('Part').id)
1829        <music21.note.Note C#> 8.0 2 Soprano
1830        <music21.note.Note A> 9.0 3 Soprano
1831        <music21.note.Note B> 9.5 3 Soprano
1832        <music21.note.Note G#> 8.0 2 Alto
1833        <music21.note.Note F#> 9.0 3 Alto
1834        <music21.note.Note G#> 9.5 3 Alto
1835        <music21.note.Note C#> 8.0 2 Tenor
1836        <music21.note.Note C#> 9.0 3 Tenor
1837        <music21.note.Note D> 9.5 3 Tenor
1838        <music21.note.Note E#> 8.0 2 Bass
1839        <music21.note.Note F#> 9.0 3 Bass
1840        <music21.note.Note B> 9.5 3 Bass
1841
1842        Changed in v5.5 -- all behavior changing options are keyword only.
1843        '''
1844        f = filters.OffsetHierarchyFilter(
1845            offsetStart,
1846            offsetEnd,
1847            includeEndBoundary=includeEndBoundary,
1848            mustFinishInSpan=mustFinishInSpan,
1849            mustBeginInSpan=mustBeginInSpan,
1850            includeElementsThatEndAtStart=includeElementsThatEndAtStart)
1851        return self.addFilter(f)
1852
1853
1854class Test(unittest.TestCase):
1855    def testSimpleClone(self):
1856        from music21 import note
1857        from music21 import stream
1858        s = stream.Stream()
1859        r = note.Rest()
1860        n = note.Note()
1861        s.append([r, n])
1862        all_s = list(s.iter())
1863        self.assertEqual(len(all_s), 2)
1864        self.assertIs(all_s[0], r)
1865        self.assertIs(all_s[1], n)
1866        s_notes = list(s.iter().notes)
1867        self.assertEqual(len(s_notes), 1)
1868        self.assertIs(s_notes[0], n)
1869
1870    def testAddingFiltersMidIteration(self):
1871        from music21 import note
1872        from music21 import stream
1873        s = stream.Stream()
1874        r = note.Rest()
1875        n = note.Note()
1876        s.append([r, n])
1877        sIter = s.iter()
1878        r0 = next(sIter)
1879        self.assertIs(r0, r)
1880
1881        # adding a filter gives a new StreamIterator that restarts at 0
1882        sIter2 = sIter.getElementsByClass('GeneralNote')  # this filter does nothing here.
1883        obj0 = next(sIter2)
1884        self.assertIs(obj0, r)
1885
1886        # original StreamIterator should be at its original spot, so this should
1887        # move to next element
1888        n0 = next(sIter)
1889        self.assertIs(n0, n)
1890
1891    def testRecursiveActiveSites(self):
1892        from music21 import converter
1893        s = converter.parse('tinyNotation: 4/4 c1 c4 d=id2 e f')
1894        rec = s.recurse()
1895        n = rec.getElementById('id2')
1896        self.assertEqual(n.activeSite.number, 2)
1897
1898    def testCurrentHierarchyOffsetReset(self):
1899        from music21 import note
1900        from music21 import stream
1901        p = stream.Part()
1902        m = stream.Measure()
1903        m.append(note.Note('D'))
1904        m.append(note.Note('E'))
1905        p.insert(0, note.Note('C'))
1906        p.append(m)
1907        pRecurse = p.recurse(includeSelf=True)
1908        allOffsets = []
1909        for unused in pRecurse:
1910            allOffsets.append(pRecurse.currentHierarchyOffset())
1911        self.assertListEqual(allOffsets, [0.0, 0.0, 1.0, 1.0, 2.0])
1912        currentOffset = pRecurse.currentHierarchyOffset()
1913        self.assertIsNone(currentOffset)
1914
1915    def testAddingFiltersMidRecursiveIteration(self):
1916        from music21 import note
1917        from music21 import stream
1918        from music21.stream.iterator import RecursiveIterator as ImportedRecursiveIterator
1919        m = stream.Measure()
1920        r = note.Rest()
1921        n = note.Note()
1922        m.append([r, n])
1923        p = stream.Part()
1924        p.append(m)
1925
1926        sc = stream.Score()
1927        sc.append(p)
1928
1929        sIter = sc.recurse()
1930        p0 = next(sIter)
1931        self.assertIs(p0, p)
1932
1933        child = sIter.childRecursiveIterator
1934        self.assertIsInstance(child, ImportedRecursiveIterator)
1935
1936
1937
1938
1939_DOC_ORDER = [StreamIterator, RecursiveIterator, OffsetIterator]
1940
1941if __name__ == '__main__':
1942    import music21
1943    music21.mainTest(Test)  # , runTest='testCurrentHierarchyOffsetReset')
1944