1# -*- coding: utf-8 -*- 2# ----------------------------------------------------------------------------- 3# Name: stream/base.py 4# Purpose: base classes for dealing with groups of positioned objects 5# 6# Authors: Michael Scott Cuthbert 7# Christopher Ariza 8# Josiah Wolf Oberholtzer 9# Evan Lynch 10# 11# Copyright: Copyright © 2008-2021 Michael Scott Cuthbert and the music21 Project 12# License: BSD, see license.txt 13# ----------------------------------------------------------------------------- 14''' 15The :class:`~music21.stream.Stream` and its subclasses, 16a subclass of the :class:`~music21.base.Music21Object`, 17is the fundamental container of offset-positioned notation and 18musical elements in music21. Common Stream subclasses, such 19as the :class:`~music21.stream.Measure` 20and :class:`~music21.stream.Score` objects, are defined in 21this module. 22''' 23import collections 24import copy 25import itertools 26import math 27import os 28import pathlib 29import unittest 30import sys 31 32from collections import namedtuple 33from fractions import Fraction 34from math import isclose 35from typing import Dict, Union, List, Optional, Set, Tuple, Sequence, TypeVar 36 37from music21 import base 38 39from music21 import bar 40from music21 import common 41from music21 import clef 42from music21 import chord 43from music21 import defaults 44from music21 import derivation 45from music21 import duration 46from music21 import exceptions21 47from music21 import interval 48from music21 import key 49from music21 import metadata 50from music21 import meter 51from music21 import note 52from music21 import pitch # for typing 53from music21 import tie 54from music21 import repeat 55from music21 import sites 56from music21 import style 57from music21 import tempo 58 59from music21.stream import core 60from music21.stream import makeNotation 61from music21.stream import streamStatus 62from music21.stream import iterator 63from music21.stream import filters 64 65from music21.common.numberTools import opFrac 66from music21.common.enums import GatherSpanners, OffsetSpecial 67 68from music21 import environment 69 70environLocal = environment.Environment('stream') 71 72StreamException = exceptions21.StreamException 73ImmutableStreamException = exceptions21.ImmutableStreamException 74 75T = TypeVar('T') 76StreamType = TypeVar('StreamType', bound='music21.stream.Stream') 77 78BestQuantizationMatch = namedtuple('BestQuantizationMatch', 79 ['error', 'tick', 'match', 'signedError', 'divisor']) 80 81class StreamDeprecationWarning(UserWarning): 82 # Do not subclass Deprecation warning, because these 83 # warnings need to be passed to users... 84 pass 85 86 87# ----------------------------------------------------------------------------- 88# Metaclass 89_OffsetMap = collections.namedtuple('OffsetMap', ['element', 'offset', 'endTime', 'voiceIndex']) 90 91 92# ----------------------------------------------------------------------------- 93 94 95class Stream(core.StreamCoreMixin, base.Music21Object): 96 ''' 97 This is the fundamental container for Music21Objects; 98 objects may be ordered and/or placed in time based on 99 offsets from the start of this container. 100 101 As a subclass of Music21Object, Streams have offsets, 102 priority, id, and groups. 103 104 Streams may be embedded within other Streams. As each 105 Stream can have its own offset, when Streams are 106 embedded the offset of an element is relatively only 107 to its parent Stream. The :meth:`~music21.stream.Stream.flatten` 108 and method provides access to a flat version of all 109 embedded Streams, with offsets relative to the 110 top-level Stream. 111 112 The Stream :attr:`~music21.stream.Stream.elements` attribute 113 returns the contents of the Stream as a list. Direct access 114 to, and manipulation of, the elements list is not recommended. 115 Instead, use the host of high-level methods available. 116 117 The Stream, like all Music21Objects, has a 118 :class:`music21.duration.Duration` that is usually the 119 "release" time of the chronologically last element in the Stream 120 (that is, the highest onset plus the duration of 121 any element in the Stream). 122 The duration, however, can be "unlinked" and explicitly 123 set independent of the Stream's contents. 124 125 The first element passed to the Stream is an optional single 126 Music21Object or a list, tuple, or other Stream of Music21Objects 127 which is used to populate the Stream by inserting each object at 128 its :attr:`~music21.base.Music21Object.offset` 129 property. One special case is when every such object, such as a newly created 130 one, has no offset. Then, so long as the entire list is not composed of 131 non-Measure Stream subclasses representing synchrony like Parts or Voices, 132 each element is appended, creating a sequence of elements in time, 133 rather than synchrony. 134 135 Other arguments and keywords are ignored, but are 136 allowed so that subclassing the Stream is easier. 137 138 >>> s1 = stream.Stream() 139 >>> s1.append(note.Note('C#4', type='half')) 140 >>> s1.append(note.Note('D5', type='quarter')) 141 >>> s1.duration.quarterLength 142 3.0 143 >>> for thisNote in s1.notes: 144 ... print(thisNote.octave) 145 ... 146 4 147 5 148 149 This is a demonstration of creating a Stream with other elements, 150 including embedded Streams (in this case, :class:`music21.stream.Part`, 151 a Stream subclass): 152 153 >>> c1 = clef.TrebleClef() 154 >>> c1.offset = 0.0 155 >>> c1.priority = -1 156 >>> n1 = note.Note('E-6', type='eighth') 157 >>> n1.offset = 1.0 158 >>> p1 = stream.Part() 159 >>> p1.offset = 0.0 160 >>> p1.id = 'embeddedPart' 161 >>> p1.append(note.Rest()) # quarter rest 162 >>> s2 = stream.Stream([c1, n1, p1]) 163 >>> s2.duration.quarterLength 164 1.5 165 >>> s2.show('text') 166 {0.0} <music21.clef.TrebleClef> 167 {0.0} <music21.stream.Part embeddedPart> 168 {0.0} <music21.note.Rest quarter> 169 {1.0} <music21.note.Note E-> 170 171 New in v7 -- providing a single element now works: 172 173 >>> s = stream.Stream(meter.TimeSignature()) 174 >>> s.first() 175 <music21.meter.TimeSignature 4/4> 176 177 New in v7 -- providing a list of objects or Measures or Scores (but not other Stream 178 subclasses such as Parts or Voices) now positions sequentially, i.e. appends: 179 180 >>> s2 = stream.Measure([note.Note(), note.Note(), bar.Barline()]) 181 >>> s2.show('text') 182 {0.0} <music21.note.Note C> 183 {1.0} <music21.note.Note C> 184 {2.0} <music21.bar.Barline type=regular> 185 186 A list of measures will let each be appended: 187 188 >>> m1 = stream.Measure(n1, number=1) 189 >>> m2 = stream.Measure(note.Rest(), number=2) 190 >>> s3 = stream.Part([m1, m2]) 191 >>> s3.show('text') 192 {0.0} <music21.stream.Measure 1 offset=0.0> 193 {1.0} <music21.note.Note E-> 194 {1.5} <music21.stream.Measure 2 offset=1.5> 195 {0.0} <music21.note.Rest quarter> 196 197 Here, every element is a Stream that's not a Measure, so we instead insert: 198 199 >>> s4 = stream.Score([stream.PartStaff(n1), stream.PartStaff(note.Rest())]) 200 >>> s4.show('text') 201 {0.0} <music21.stream.PartStaff 0x...> 202 {1.0} <music21.note.Note E-> 203 {0.0} <music21.stream.PartStaff 0x...> 204 {0.0} <music21.note.Rest quarter> 205 206 Create nested streams in one fell swoop: 207 208 >>> s5 = stream.Score(stream.Part(stream.Measure(chord.Chord('C2 A2')))) 209 >>> s5.show('text') 210 {0.0} <music21.stream.Part 0x...> 211 {0.0} <music21.stream.Measure 0 offset=0.0> 212 {0.0} <music21.chord.Chord C2 A2> 213 ''' 214 # this static attributes offer a performance boost over other 215 # forms of checking class 216 isStream = True 217 isMeasure = False 218 classSortOrder = -20 219 recursionType = 'elementsFirst' 220 221 _styleClass = style.StreamStyle 222 223 # define order to present names in documentation; use strings 224 _DOC_ORDER = ['append', 'insert', 'storeAtEnd', 'insertAndShift', 225 'recurse', 'flat', 226 'notes', 'pitches', 227 'transpose', 228 'augmentOrDiminish', 'scaleOffsets', 'scaleDurations'] 229 # documentation for all attributes (not properties or methods) 230 _DOC_ATTR = { 231 'recursionType': ''' 232 Class variable: 233 234 String of ('elementsFirst' (default), 'flatten', 'elementsOnly) 235 that decides whether the stream likely holds relevant 236 contexts for the elements in it. 237 238 Define this for a stream class, not an individual object. 239 240 see :meth:`~music21.base.Music21Object.contextSites` 241 ''', 242 'isSorted': ''' 243 Boolean describing whether the Stream is sorted or not. 244 ''', 245 'autoSort': ''' 246 Boolean describing whether the Stream is automatically sorted by 247 offset whenever necessary. 248 ''', 249 'isFlat': ''' 250 Boolean describing whether this Stream contains embedded 251 sub-Streams or Stream subclasses (not flat). 252 ''', 253 'definesExplicitSystemBreaks': ''' 254 Boolean that says whether all system breaks in the piece are 255 explicitly defined. Only used on musicxml output (maps to the 256 musicxml <supports attribute="new-system"> tag) and only if this is 257 the outermost Stream being shown 258 ''', 259 'definesExplicitPageBreaks': ''' 260 Boolean that says whether all page breaks in the piece are 261 explicitly defined. Only used on musicxml output (maps to the 262 musicxml <supports attribute="new-page"> tag) and only if this is 263 the outermost Stream being shown 264 ''', 265 } 266 267 def __init__(self, givenElements=None, *args, **keywords): 268 base.Music21Object.__init__(self, **keywords) 269 core.StreamCoreMixin.__init__(self) 270 271 self.streamStatus = streamStatus.StreamStatus(self) 272 self._unlinkedDuration = None 273 274 self.autoSort = True 275 276 # these should become part of style or something else... 277 self.definesExplicitSystemBreaks = False 278 self.definesExplicitPageBreaks = False 279 280 # property for transposition status; 281 self._atSoundingPitch = 'unknown' # True, False, or unknown 282 283 # experimental 284 self._mutable = True 285 286 if givenElements is None: 287 return 288 289 if isinstance(givenElements, base.Music21Object) or not common.isIterable(givenElements): 290 givenElements = [givenElements] 291 292 # Append rather than insert if every offset is 0.0 293 # but not if every element is a stream subclass other than a Measure or Score 294 # (i.e. Part or Voice generally, but even Opus theoretically) 295 # because these classes usually represent synchrony 296 append: bool = False 297 try: 298 append = all(e.offset == 0.0 for e in givenElements) 299 except AttributeError: 300 pass # appropriate failure will be raised by coreGuardBeforeAddElement() 301 if append and all( 302 (e.isStream and e.classSet.isdisjoint((Measure, Score))) 303 for e in givenElements): 304 append = False 305 306 for e in givenElements: 307 self.coreGuardBeforeAddElement(e) 308 if append: 309 self.coreAppend(e) 310 else: 311 self.coreInsert(e.offset, e) 312 313 self.coreElementsChanged() 314 315 def _reprInternal(self): 316 if self.id is not None: 317 if self.id != id(self) and str(self.id) != str(id(self)): 318 return str(self.id) 319 else: 320 return hex(self.id) 321 else: # pragma: no cover 322 return '' 323 324 def write(self, fmt=None, fp=None, **keywords): 325 # ... --- see base.py calls .write( 326 if self.isSorted is False and self.autoSort: # pragma: no cover 327 self.sort() 328 return super().write(fmt=fmt, fp=fp, **keywords) 329 330 def show(self, fmt=None, app=None, **keywords): 331 # ... --- see base.py calls .write( 332 if self.isSorted is False and self.autoSort: 333 self.sort() 334 return super().show(fmt=fmt, app=app, **keywords) 335 336 # -------------------------------------------------------------------------- 337 # sequence like operations 338 339 def __len__(self): 340 ''' 341 Get the total number of elements in the Stream. 342 This method does not recurse into and count elements in contained Streams. 343 344 >>> import copy 345 >>> a = stream.Stream() 346 >>> for x in range(4): 347 ... n = note.Note('G#') 348 ... n.offset = x * 3 349 ... a.insert(n) 350 >>> len(a) 351 4 352 353 >>> b = stream.Stream() 354 >>> for x in range(4): 355 ... b.insert(copy.deepcopy(a)) # append streams 356 >>> len(b) 357 4 358 >>> len(b.flatten()) 359 16 360 361 Includes end elements: 362 363 >>> b.storeAtEnd(bar.Barline('double')) 364 >>> len(b) 365 5 366 ''' 367 return len(self._elements) + len(self._endElements) 368 369 def __iter__(self): 370 ''' 371 The Stream iterator, used in all for 372 loops and similar iteration routines. This method returns the 373 specialized :class:`music21.stream.StreamIterator` class, which 374 adds necessary Stream-specific features. 375 ''' 376 return iterator.StreamIterator(self) 377 378 @property 379 def iter(self): 380 ''' 381 The Stream iterator, used in all for 382 loops and similar iteration routines. This method returns the 383 specialized :class:`music21.stream.StreamIterator` class, which 384 adds necessary Stream-specific features. 385 386 Generally you don't need this, just iterate over a stream, but it is necessary 387 to add custom filters to an iterative search before iterating. 388 ''' 389 return self.__iter__() 390 391 def __getitem__(self, k): 392 ''' 393 Get a Music21Object from the Stream using a variety of keys or indices. 394 395 If an int is given, the Music21Object at the index is returned, as if it were a list 396 or tuple: 397 398 >>> c = note.Note('C') 399 >>> d = note.Note('D') 400 >>> e = note.Note('E') 401 >>> r1 = note.Rest() 402 >>> f = note.Note('F') 403 >>> g = note.Note('G') 404 >>> r2 = note.Rest() 405 >>> a = note.Note('A') 406 >>> s = stream.Stream([c, d, e, r1, f, g, r2, a]) 407 408 >>> s[0] 409 <music21.note.Note C> 410 >>> s[-1] 411 <music21.note.Note A> 412 413 Out of range notes raise an IndexError: 414 415 >>> s[99] 416 Traceback (most recent call last): 417 IndexError: attempting to access index 99 while elements is of size 8 418 419 If a slice of indices is given, a list of elements is returned, as if the Stream 420 were a list or Tuple. 421 422 >>> subslice = s[2:5] 423 >>> subslice 424 [<music21.note.Note E>, <music21.note.Rest quarter>, <music21.note.Note F>] 425 >>> len(subslice) 426 3 427 >>> s[1].offset 428 1.0 429 >>> subslice[1].offset 430 3.0 431 432 433 If a class is given then an iterator of elements 434 that match the requested class(es) is returned, similar 435 to `Stream().recurse().getElementsByClass()`. 436 437 >>> len(s) 438 8 439 >>> len(s[note.Rest]) 440 2 441 >>> len(s[note.Note]) 442 6 443 444 >>> for n in s[note.Note]: 445 ... print(n.name, end=' ') 446 C D E F G A 447 448 Note that this iterator is recursive by default. 449 450 >>> c_sharp = note.Note('C#') 451 >>> v = stream.Voice([c_sharp]) 452 >>> s.insert(0.5, c_sharp) 453 454 >>> len(s[note.Note]) 455 7 456 457 458 The actual object returned by `s[module.Class]` is a 459 :class:`~music21.stream.iterator.RecursiveIterator` and has all the functions 460 available on it: 461 462 >>> s[note.Note] 463 <...> 464 465 If no elements of the class are found, no error is raised in version 7: 466 467 >>> list(s[layout.StaffLayout]) 468 [] 469 470 471 If the key is a string, it is treated as a `querySelector` as defined in 472 :meth:`~music21.stream.iterator.getElementsByQuerySelector`, namely that bare strings 473 are treated as class names, strings beginning with `#` are id-queries, and strings 474 beginning with `.` are group queries. 475 476 We can set some ids and groups for demonstrating. 477 478 >>> a.id = 'last_a' 479 >>> c.groups.append('ghost') 480 >>> e.groups.append('ghost') 481 482 'Note' is treated as a class name and returns a `RecursiveIterator`: 483 484 >>> for n in s['Note']: 485 ... print(n.name, end=' ') 486 C C# D E F G A 487 488 '.ghost', because it begins with `.`, is treated as a class name and 489 returns a `RecursiveIterator`: 490 491 492 >>> for n in s['.ghost']: 493 ... print(n.name, end=' ') 494 C E 495 496 A query selector with a `#`: 497 498 >>> s['#last_a'] 499 <music21.note.Note A> 500 501 >>> s['#nothing'] is None 502 True 503 504 505 Any other query raises a TypeError: 506 507 >>> s[0.5] 508 Traceback (most recent call last): 509 TypeError: Streams can get items by int, slice, class, or string query; got <class 'float'> 510 511 512 Changed in v7: 513 514 - out of range indexes now raise an IndexError, not StreamException 515 - strings ('Note', '#id', '.group') are now treated like a query selector. 516 - slices with negative indices now supported 517 - Unsupported types now raise TypeError 518 - Class and Group searches now return a recursive `StreamIterator` rather than a `Stream` 519 - Slice searches now return a list of elements rather than a `Stream` 520 ''' 521 # need to sort if not sorted, as this call may rely on index positions 522 if not self.isSorted and self.autoSort: 523 self.sort() # will set isSorted to True 524 525 if isinstance(k, int): 526 match = None 527 # handle easy and most common case first 528 if 0 <= k < len(self._elements): 529 match = self._elements[k] 530 # if using negative indices, or trying to access end elements, 531 # then need to use .elements property 532 else: 533 try: 534 match = self.elements[k] 535 except IndexError: 536 raise IndexError( 537 f'attempting to access index {k} ' 538 + f'while elements is of size {len(self.elements)}' 539 ) 540 # setting active site as cautionary measure 541 self.coreSelfActiveSite(match) 542 return match 543 544 elif isinstance(k, slice): # get a slice of index values 545 # manually inserting elements is critical to setting the element 546 # locations 547 searchElements = self._elements 548 if (k.start is not None and k.start < 0) or (k.stop is not None and k.stop < 0): 549 # Must use .elements property to incorporate end elements 550 searchElements = self.elements 551 552 return searchElements[k] 553 554 elif isinstance(k, type): 555 return self.recurse().getElementsByClass(k) 556 557 elif isinstance(k, str): 558 querySelectorIterator = self.recurse().getElementsByQuerySelector(k) 559 if '#' in k: 560 # an id anywhere in the query selector should return only one element 561 return querySelectorIterator.first() 562 else: 563 return querySelectorIterator 564 565 raise TypeError( 566 f'Streams can get items by int, slice, class, or string query; got {type(k)}' 567 ) 568 569 def first(self) -> Optional[base.Music21Object]: 570 ''' 571 Return the first element of a Stream. (Added for compatibility with StreamIterator) 572 Or None if the Stream is empty. 573 574 Unlike s.iter().first(), which is a significant performance gain, s.first() is the 575 same speed as s[0], except for not raising an IndexError. 576 577 >>> nC = note.Note('C4') 578 >>> nD = note.Note('D4') 579 >>> s = stream.Stream() 580 >>> s.append([nC, nD]) 581 >>> s.first() 582 <music21.note.Note C> 583 584 >>> empty = stream.Stream() 585 >>> print(empty.first()) 586 None 587 588 New in v7. 589 ''' 590 try: 591 return self[0] 592 except IndexError: 593 return None 594 595 def last(self) -> Optional[base.Music21Object]: 596 ''' 597 Return the last element of a Stream. (Added for compatibility with StreamIterator) 598 Or None if the Stream is empty. 599 600 s.first() is the same speed as s[-1], except for not raising an IndexError. 601 602 >>> nC = note.Note('C4') 603 >>> nD = note.Note('D4') 604 >>> s = stream.Stream() 605 >>> s.append([nC, nD]) 606 >>> s.last() 607 <music21.note.Note D> 608 609 >>> empty = stream.Stream() 610 >>> print(empty.last()) 611 None 612 613 New in v7. 614 ''' 615 try: 616 return self[-1] 617 except IndexError: 618 return None 619 620 def __contains__(self, el): 621 ''' 622 Returns True if `el` definitely is in the stream and False otherwise. 623 624 >>> nC = note.Note('C4') 625 >>> nD = note.Note('D4') 626 >>> s = stream.Stream() 627 >>> s.append(nC) 628 >>> nC in s 629 True 630 >>> nD in s 631 False 632 633 Note that we match on actual `id()` equality (`x is y`) and not on 634 `==` equality. 635 636 >>> nC2 = note.Note('C4') 637 >>> nC == nC2 638 True 639 >>> nC2 in s 640 False 641 642 To get the latter, compare on `.elements` which uses Python's 643 default `__contains__` for tuples. 644 645 >>> nC2 in s.elements 646 True 647 ''' 648 for sEl in self.elements: # for speed do not set active sites 649 if el is sEl: 650 return True 651 return False 652 653 @property 654 def elements(self) -> Tuple[base.Music21Object]: 655 ''' 656 .elements is a Tuple representing the elements contained in the Stream. 657 658 Directly getting, setting, and manipulating this Tuple is 659 reserved for advanced usage. Instead, use the the 660 provided high-level methods. The elements retrieved here may not 661 have this stream as an activeSite, therefore they might not be properly ordered. 662 663 In other words: Don't use unless you really know what you're doing. 664 Treat a Stream like a list! 665 666 When setting .elements, a list of Music21Objects can be provided, or a complete Stream. 667 If a complete Stream is provided, elements are extracted 668 from that Stream. This has the advantage of transferring 669 offset correctly and getting elements stored at the end. 670 671 >>> a = stream.Stream() 672 >>> a.repeatInsert(note.Note('C'), list(range(10))) 673 >>> b = stream.Stream() 674 >>> b.repeatInsert(note.Note('D'), list(range(10))) 675 >>> b.offset = 6 676 >>> c = stream.Stream() 677 >>> c.repeatInsert(note.Note('E'), list(range(10))) 678 >>> c.offset = 12 679 >>> b.insert(c) 680 >>> b.isFlat 681 False 682 683 >>> a.isFlat 684 True 685 686 Assigning from a Stream works well, and is actually much safer than assigning 687 from `.elements` of the other Stream, since the active sites may have changed 688 of that stream's elements in the meantime. 689 690 >>> a.elements = b 691 >>> a.isFlat 692 False 693 694 >>> len(a.recurse().notes) == len(b.recurse().notes) == 20 695 True 696 ''' 697 # combines _elements and _endElements into one. 698 if 'elements' not in self._cache or self._cache['elements'] is None: 699 # this list concatenation may take time; thus, only do when 700 # coreElementsChanged has been called 701 if not self.isSorted and self.autoSort: 702 self.sort() # will set isSorted to True 703 self._cache['elements'] = self._elements + self._endElements 704 return tuple(self._cache['elements']) 705 706 @elements.setter 707 def elements(self, value: Union['Stream', Sequence[base.Music21Object]]): 708 ''' 709 Sets this stream's elements to the elements in another stream (just give 710 the stream, not the stream's .elements), or to a list of elements. 711 712 Safe: 713 714 newStream.elements = oldStream 715 716 Unsafe: 717 718 newStream.elements = oldStream.elements 719 720 Why? 721 722 The activeSites of some elements may have changed between retrieving 723 and setting (esp. if a lot else has happened in the meantime). Where 724 are we going to get the new stream's elements' offsets from? why 725 from their active sites! So don't do this! 726 ''' 727 if (not common.isListLike(value) 728 and hasattr(value, 'isStream') 729 and value.isStream): 730 # set from a Stream. Best way to do it 731 self._offsetDict = {} 732 self._elements = list(value._elements) # copy list. 733 for e in self._elements: 734 self.coreSetElementOffset(e, value.elementOffset(e), addElement=True) 735 e.sites.add(self) 736 self.coreSelfActiveSite(e) 737 self._endElements = list(value._endElements) 738 for e in self._endElements: 739 self.coreSetElementOffset(e, 740 value.elementOffset(e, returnSpecial=True), 741 addElement=True) 742 e.sites.add(self) 743 self.coreSelfActiveSite(e) 744 else: 745 # replace the complete elements list 746 self._elements = list(value) 747 self._endElements = [] 748 self._offsetDict = {} 749 for e in self._elements: 750 self.coreSetElementOffset(e, e.offset, addElement=True) 751 e.sites.add(self) 752 self.coreSelfActiveSite(e) 753 self.coreElementsChanged() 754 755 def __setitem__(self, k, value): 756 ''' 757 Insert an item at a currently filled index position, 758 as represented in the elements list. 759 760 >>> a = stream.Stream() 761 >>> a.repeatInsert(note.Note('C'), list(range(10))) 762 >>> b = stream.Stream() 763 >>> b.repeatInsert(note.Note('C'), list(range(10))) 764 >>> b.offset = 6 765 >>> c = stream.Stream() 766 >>> c.repeatInsert(note.Note('C'), list(range(10))) 767 >>> c.offset = 12 768 >>> b.insert(c) 769 >>> a.isFlat 770 True 771 >>> a[3] = b 772 >>> a.isFlat 773 False 774 ''' 775 # remove old value at this position 776 oldValue = self._elements[k] 777 del self._offsetDict[id(oldValue)] 778 oldValue.sites.remove(self) 779 oldValue.activeSite = None 780 781 # assign in new position 782 self._elements[k] = value 783 self.coreSetElementOffset(value, value.offset, addElement=True) 784 self.coreSelfActiveSite(value) 785 # must get native offset 786 787 value.sites.add(self) 788 if isinstance(value, Stream): 789 # know that this is now not flat 790 self.coreElementsChanged(updateIsFlat=False) # set manually 791 self.isFlat = False 792 else: 793 # cannot be sure if this is flat, as we do not know if 794 # we replaced a Stream at this index 795 self.coreElementsChanged() 796 797 def __delitem__(self, k): 798 ''' 799 Delete element at an index position. Index positions are based 800 on positions in self.elements. 801 802 >>> a = stream.Stream() 803 >>> a.repeatInsert(note.Note('C'), list(range(10))) 804 >>> del a[0] 805 >>> len(a) 806 9 807 ''' 808 del self._elements[k] 809 self.coreElementsChanged() 810 811 def __add__(self: T, other: 'Stream') -> T: 812 ''' 813 Add, or concatenate, two Streams. 814 815 Presently, this does not manipulate the offsets of the incoming elements 816 to actually be at the end of the Stream. This may be a problem that 817 makes this method not so useful? 818 819 >>> a = stream.Part() 820 >>> a.repeatInsert(note.Note('C'), [0, 1]) 821 >>> b = stream.Stream() 822 >>> b.repeatInsert(note.Note('G'), [0, 1, 2]) 823 >>> c = a + b 824 >>> c.pitches # autoSort is True, thus a sorted version results 825 [<music21.pitch.Pitch C>, 826 <music21.pitch.Pitch G>, 827 <music21.pitch.Pitch C>, 828 <music21.pitch.Pitch G>, 829 <music21.pitch.Pitch G>] 830 >>> len(c.notes) 831 5 832 833 The autoSort of the first stream becomes the autoSort of the 834 destination. The class of the first becomes the class of the destination. 835 836 >>> a.autoSort = False 837 >>> d = a + b 838 >>> [str(p) for p in d.pitches] 839 ['C', 'C', 'G', 'G', 'G'] 840 >>> d.__class__.__name__ 841 'Part' 842 843 Works with Streams with Store at end, which does put both at the end. 844 845 >>> a.autoSort = True 846 >>> a.storeAtEnd(bar.Barline('final')) 847 >>> b.storeAtEnd(clef.TrebleClef()) 848 >>> f = a + b 849 >>> f.show('text') 850 {0.0} <music21.note.Note C> 851 {0.0} <music21.note.Note G> 852 {1.0} <music21.note.Note C> 853 {1.0} <music21.note.Note G> 854 {2.0} <music21.note.Note G> 855 {3.0} <music21.bar.Barline type=final> 856 {3.0} <music21.clef.TrebleClef> 857 ''' 858 if other is None or not isinstance(other, Stream): 859 raise TypeError('cannot concatenate a Stream with a non-Stream') 860 861 s = self.cloneEmpty(derivationMethod='__add__') 862 # may want to keep activeSite of source Stream? 863 # s.elements = self._elements + other._elements 864 # need to iterate over elements and re-assign to create new locations 865 for e in self._elements: 866 s.insert(self.elementOffset(e), e) 867 for e in other._elements: 868 s.insert(other.elementOffset(e), e) 869 870 for e in self._endElements: 871 s.storeAtEnd(e) 872 for e in other._endElements: 873 s.storeAtEnd(e) 874 875 # s.coreElementsChanged() 876 return s 877 878 def __bool__(self): 879 ''' 880 As a container class, Streams return True if they are non-empty 881 and False if they are empty: 882 883 >>> def testBool(s): 884 ... if s: 885 ... return True 886 ... else: 887 ... return False 888 889 >>> s = stream.Stream() 890 >>> testBool(s) 891 False 892 >>> s.append(note.Note()) 893 >>> testBool(s) 894 True 895 >>> s.append(note.Note()) 896 >>> testBool(s) 897 True 898 899 >>> s = stream.Stream() 900 >>> s.storeAtEnd(bar.Barline('final')) 901 >>> testBool(s) 902 True 903 ''' 904 if self._elements: # blindingly faster than if len(self._elements) > 0 905 return True # and even about 5x faster than if any(self._elements) 906 if self._endElements: 907 return True 908 return False 909 910 # ------------------------------ 911 @property 912 def clef(self) -> Optional['music21.clef.Clef']: 913 ''' 914 Finds or sets a :class:`~music21.clef.Clef` at offset 0.0 in the Stream 915 (generally a Measure): 916 917 >>> m = stream.Measure() 918 >>> m.number = 10 919 >>> m.clef = clef.TrebleClef() 920 >>> thisTrebleClef = m.clef 921 >>> thisTrebleClef.sign 922 'G' 923 >>> thisTrebleClef.getOffsetBySite(m) 924 0.0 925 926 Setting the clef for the measure a second time removes the previous clef 927 from the measure and replaces it with the new one: 928 929 >>> m.clef = clef.BassClef() 930 >>> m.clef.sign 931 'F' 932 933 And the TrebleClef is no longer in the measure: 934 935 >>> thisTrebleClef.getOffsetBySite(m) 936 Traceback (most recent call last): 937 music21.sites.SitesException: an entry for this object <music21.clef.TrebleClef> is not 938 stored in stream <music21.stream.Measure 10 offset=0.0> 939 940 The `.clef` appears in a `.show()` or other call 941 just like any other element 942 943 >>> m.append(note.Note('D#', type='whole')) 944 >>> m.show('text') 945 {0.0} <music21.clef.BassClef> 946 {0.0} <music21.note.Note D#> 947 ''' 948 clefList = self.getElementsByClass('Clef').getElementsByOffset(0) 949 # casting to list added 20microseconds... 950 return clefList.first() 951 952 @clef.setter 953 def clef(self, clefObj: Optional['music21.clef.Clef']): 954 # if clef is None; remove object? 955 oldClef = self.clef 956 if oldClef is not None: 957 # environLocal.printDebug(['removing clef', oldClef]) 958 junk = self.pop(self.index(oldClef)) 959 if clefObj is None: 960 # all that is needed is to remove the old clef 961 # there is no new clef - suppresses the clef of a stream 962 return 963 self.insert(0.0, clefObj) 964 965 @property 966 def timeSignature(self) -> Optional['music21.meter.TimeSignature']: 967 ''' 968 Gets or sets the timeSignature at offset 0.0 of the Stream (generally a Measure) 969 970 >>> m1 = stream.Measure(number=1) 971 >>> m1.timeSignature = meter.TimeSignature('2/4') 972 >>> m1.timeSignature.numerator, m1.timeSignature.denominator 973 (2, 4) 974 >>> m1.show('text') 975 {0.0} <music21.meter.TimeSignature 2/4> 976 977 Setting timeSignature to None removes any TimeSignature at offset 0.0: 978 979 >>> m1.timeSignature = None 980 >>> m1.elements 981 () 982 983 984 Only the time signature at offset 0 is found: 985 986 >>> m2 = stream.Measure(number=2) 987 >>> m2.insert(0.0, meter.TimeSignature('5/4')) 988 >>> m2.insert(2.0, meter.TimeSignature('7/4')) 989 >>> ts = m2.timeSignature 990 >>> ts.numerator, ts.denominator 991 (5, 4) 992 993 >>> m2.timeSignature = meter.TimeSignature('2/8') 994 >>> m2.timeSignature 995 <music21.meter.TimeSignature 2/8> 996 997 After setting a new `.timeSignature`, the old one is no longer in the Stream: 998 999 >>> ts in m2 1000 False 1001 1002 This property is not recursive, so a Part will not have the time signature of 1003 the measure within it: 1004 1005 >>> p = stream.Part() 1006 >>> p.append(m2) 1007 >>> p.timeSignature is None 1008 True 1009 ''' 1010 # there could be more than one 1011 tsList = self.getElementsByClass('TimeSignature').getElementsByOffset(0) 1012 # environLocal.printDebug([ 1013 # 'matched Measure classes of type TimeSignature', tsList, len(tsList)]) 1014 # only return timeSignatures at offset = 0.0 1015 return tsList.first() 1016 1017 @timeSignature.setter 1018 def timeSignature(self, tsObj: Optional['music21.meter.TimeSignature']): 1019 oldTimeSignature = self.timeSignature 1020 if oldTimeSignature is not None: 1021 # environLocal.printDebug(['removing ts', oldTimeSignature]) 1022 junk = self.pop(self.index(oldTimeSignature)) 1023 if tsObj is None: 1024 # all that is needed is to remove the old time signature 1025 # there is no new time signature - suppresses the time signature of a stream 1026 return 1027 self.insert(0, tsObj) 1028 1029 @property 1030 def keySignature(self) -> Optional['music21.key.KeySignature']: 1031 ''' 1032 Find or set a Key or KeySignature at offset 0.0 of a stream. 1033 1034 >>> a = stream.Measure() 1035 >>> a.keySignature = key.KeySignature(-2) 1036 >>> ks = a.keySignature 1037 >>> ks.sharps 1038 -2 1039 >>> a.show('text') 1040 {0.0} <music21.key.KeySignature of 2 flats> 1041 1042 A key.Key object can be used instead of key.KeySignature, 1043 since the former derives from the latter. 1044 1045 >>> a.keySignature = key.Key('E', 'major') 1046 >>> for k in a: 1047 ... print(k.offset, repr(k)) 1048 0.0 <music21.key.Key of E major> 1049 1050 Notice that setting a new key signature replaces any previous ones: 1051 1052 >>> len(a.getElementsByClass('KeySignature')) 1053 1 1054 1055 `.keySignature` can be set to None: 1056 1057 >>> a.keySignature = None 1058 >>> a.keySignature is None 1059 True 1060 ''' 1061 try: 1062 return next(self.iter().getElementsByClass('KeySignature').getElementsByOffset(0)) 1063 except StopIteration: 1064 return None 1065 1066 @keySignature.setter 1067 def keySignature(self, keyObj: Optional['music21.key.KeySignature']): 1068 ''' 1069 >>> a = stream.Measure() 1070 >>> a.keySignature = key.KeySignature(6) 1071 >>> a.keySignature.sharps 1072 6 1073 ''' 1074 oldKey = self.keySignature 1075 if oldKey is not None: 1076 # environLocal.printDebug(['removing key', oldKey]) 1077 junk = self.pop(self.index(oldKey)) 1078 if keyObj is None: 1079 # all that is needed is to remove the old key signature 1080 # there is no new key signature - suppresses the key signature of a stream 1081 return 1082 self.insert(0, keyObj) 1083 1084 @property 1085 def staffLines(self) -> int: 1086 ''' 1087 Returns the number of staffLines for the Stream, as defined by 1088 the first StaffLayout object found at offset 0 that defines staffLines 1089 1090 >>> m = stream.Measure() 1091 >>> m.staffLines 1092 5 1093 >>> m.staffLines = 4 1094 >>> m.staffLines 1095 4 1096 >>> m.show('text') 1097 {0.0} <music21.layout.StaffLayout distance None, staffNumber None, 1098 staffSize None, staffLines 4> 1099 1100 >>> staffLayout = m.getElementsByClass('StaffLayout').first() 1101 >>> staffLayout.staffLines = 1 1102 >>> m.staffLines 1103 1 1104 1105 >>> p = stream.Part() 1106 >>> p.insert(0, m) 1107 >>> p.staffLines 1108 1 1109 1110 >>> p2 = stream.Part() 1111 >>> m0 = stream.Measure() 1112 >>> m0.insert(0, note.Note(type='whole')) 1113 >>> p2.append(m0) 1114 >>> p2.append(m) 1115 >>> p2.staffLines 1116 5 1117 1118 OMIT_FROM_DOCS 1119 1120 Check that staffLayout is altered by staffLayout setter: 1121 1122 >>> m.staffLines = 2 1123 >>> staffLayout.staffLines 1124 2 1125 1126 ''' 1127 staffLayouts = self.recurse().getElementsByClass('StaffLayout') 1128 sl: 'music21.layout.StaffLayout' 1129 for sl in staffLayouts: 1130 if sl.getOffsetInHierarchy(self) > 0: 1131 break 1132 if sl.staffLines is not None: 1133 return sl.staffLines 1134 return 5 1135 1136 @staffLines.setter 1137 def staffLines(self, newStaffLines: int): 1138 staffLayouts = self.recurse().getElementsByOffset(0.0).getElementsByClass('StaffLayout') 1139 if not staffLayouts: 1140 from music21 import layout 1141 sl: 'music21.layout.StaffLayout' = layout.StaffLayout(staffLines=newStaffLines) 1142 self.insert(0.0, sl) 1143 else: 1144 firstLayout = staffLayouts.first() 1145 firstLayout.staffLines = newStaffLines 1146 1147 def clear(self) -> None: 1148 ''' 1149 Remove all elements in a stream. 1150 1151 >>> m = stream.Measure(number=3) 1152 >>> m.append(note.Note('C')) 1153 >>> m.storeAtEnd(bar.Barline('final')) 1154 >>> len(m) 1155 2 1156 >>> m.clear() 1157 >>> len(m) 1158 0 1159 1160 Does not remove any other attributes 1161 1162 >>> m.number 1163 3 1164 ''' 1165 self.elements = [] 1166 1167 def cloneEmpty(self: StreamType, derivationMethod: Optional[str] = None) -> StreamType: 1168 ''' 1169 Create a Stream that is identical to this one except that the elements are empty 1170 and set derivation. 1171 1172 >>> p = stream.Part() 1173 >>> p.autoSort = False 1174 >>> p.id = 'hi' 1175 >>> p.insert(0, note.Note()) 1176 >>> q = p.cloneEmpty(derivationMethod='demo') 1177 >>> q.autoSort 1178 False 1179 >>> q 1180 <music21.stream.Part hi> 1181 >>> q.derivation.origin is p 1182 True 1183 >>> q.derivation.method 1184 'demo' 1185 >>> len(q) 1186 0 1187 ''' 1188 returnObj: StreamType = self.__class__() 1189 returnObj.derivation.client = returnObj 1190 returnObj.derivation.origin = self 1191 if derivationMethod is not None: 1192 returnObj.derivation.method = derivationMethod 1193 returnObj.mergeAttributes(self) # get groups, optional id 1194 return returnObj 1195 1196 def mergeAttributes(self, other: 'Stream'): 1197 ''' 1198 Merge relevant attributes from the Other stream into this one. 1199 1200 >>> s = stream.Stream() 1201 >>> s.append(note.Note()) 1202 >>> s.autoSort = False 1203 >>> s.id = 'hi' 1204 >>> t = stream.Stream() 1205 >>> t.mergeAttributes(s) 1206 >>> t.autoSort 1207 False 1208 >>> t 1209 <music21.stream.Stream hi> 1210 >>> len(t) 1211 0 1212 ''' 1213 super().mergeAttributes(other) 1214 1215 for attr in ('autoSort', 'isSorted', 'definesExplicitSystemBreaks', 1216 'definesExplicitPageBreaks', '_atSoundingPitch', '_mutable'): 1217 if hasattr(other, attr): 1218 setattr(self, attr, getattr(other, attr)) 1219 1220 def hasElement(self, obj): 1221 ''' 1222 Return True if an element, provided as an argument, is contained in 1223 this Stream. 1224 1225 This method is based on object equivalence, not parameter equivalence 1226 of different objects. 1227 1228 >>> s = stream.Stream() 1229 >>> n1 = note.Note('g') 1230 >>> n2 = note.Note('g#') 1231 >>> s.append(n1) 1232 >>> s.hasElement(n1) 1233 True 1234 ''' 1235 objId = id(obj) 1236 return self.coreHasElementByMemoryLocation(objId) 1237 1238 def hasElementOfClass(self, className, forceFlat=False): 1239 ''' 1240 Given a single class name as string, 1241 return True or False if an element with the 1242 specified class is found. 1243 1244 Only a single class name can be given. 1245 1246 >>> s = stream.Stream() 1247 >>> s.append(meter.TimeSignature('5/8')) 1248 >>> s.append(note.Note('d-2')) 1249 >>> s.insert(dynamics.Dynamic('fff')) 1250 >>> s.hasElementOfClass('TimeSignature') 1251 True 1252 >>> s.hasElementOfClass('Measure') 1253 False 1254 1255 To be deprecated in v.7 -- to be removed in version 8, use: 1256 1257 >>> bool(s.getElementsByClass('TimeSignature')) 1258 True 1259 >>> bool(s.getElementsByClass('Measure')) 1260 False 1261 1262 forceFlat does nothing, while getElementsByClass can be done on recurse() 1263 ''' 1264 # environLocal.printDebug(['calling hasElementOfClass()', className]) 1265 for e in self.elements: 1266 if className in e.classSet: 1267 return True 1268 return False 1269 1270 def mergeElements(self, other, classFilterList=None): 1271 ''' 1272 Given another Stream, store references of each element 1273 in the other Stream in this Stream. This does not make 1274 copies of any elements, but simply stores all of them in this Stream. 1275 1276 Optionally, provide a list of classes to include with the `classFilter` list. 1277 1278 This method provides functionality like a shallow copy, 1279 but manages locations properly, only copies elements, 1280 and permits filtering by class type. 1281 1282 1283 >>> s1 = stream.Stream() 1284 >>> s2 = stream.Stream() 1285 >>> n1 = note.Note('f#') 1286 >>> n2 = note.Note('g') 1287 >>> s1.append(n1) 1288 >>> s1.append(n2) 1289 >>> s2.mergeElements(s1) 1290 >>> len(s2) 1291 2 1292 >>> s1[0] is s2[0] 1293 True 1294 >>> s1[1] is s2[1] 1295 True 1296 1297 >>> viola = instrument.Viola() 1298 >>> trumpet = instrument.Trumpet() 1299 >>> s1.insert(0, viola) 1300 >>> s1.insert(0, trumpet) 1301 >>> s2.mergeElements(s1, classFilterList=('BrassInstrument',)) 1302 >>> len(s2) 1303 3 1304 >>> viola in s2 1305 False 1306 ''' 1307 if classFilterList is not None: 1308 classFilterSet = set(classFilterList) 1309 else: 1310 classFilterSet = None 1311 1312 for e in other._elements: 1313 # self.insert(other.offset, e) 1314 if classFilterList is not None: 1315 if classFilterSet.intersection(e.classSet): 1316 self.coreInsert(other.elementOffset(e), e) 1317 else: 1318 self.coreInsert(other.elementOffset(e), e) 1319 1320 # for c in classFilterList: 1321 # if c in e.classes: 1322 # match = True 1323 # break 1324 # 1325 # if len(classFilterList) == 0 or match: 1326 # self.insert(e.getOffsetBySite(other), e) 1327 1328 for e in other._endElements: 1329 if classFilterList is not None: 1330 if classFilterSet.intersection(e.classSet): 1331 self.coreStoreAtEnd(e) 1332 else: 1333 self.coreStoreAtEnd(e) 1334 1335 # match = False 1336 # for c in classFilterList: 1337 # if c in e.classes: 1338 # match = True 1339 # break 1340 # if len(classFilterList) == 0 or match: 1341 # self.storeAtEnd(e) 1342 self.coreElementsChanged() 1343 1344 def index(self, el): 1345 ''' 1346 Return the first matched index for 1347 the specified object. 1348 1349 Raises a StreamException if cannot 1350 be found. 1351 1352 >>> s = stream.Stream() 1353 >>> n1 = note.Note('g') 1354 >>> n2 = note.Note('g#') 1355 1356 >>> s.insert(0, n1) 1357 >>> s.insert(5, n2) 1358 >>> len(s) 1359 2 1360 >>> s.index(n1) 1361 0 1362 >>> s.index(n2) 1363 1 1364 1365 >>> n3 = note.Note('a') 1366 >>> s.index(n3) 1367 Traceback (most recent call last): 1368 music21.exceptions21.StreamException: cannot find object (<music21.note.Note A>) in Stream 1369 ''' 1370 if not self.isSorted and self.autoSort: 1371 self.sort() # will set isSorted to True 1372 1373 if 'index' in self._cache and self._cache['index'] is not None: 1374 try: 1375 return self._cache['index'][id(el)] 1376 except KeyError: 1377 pass # not in cache 1378 else: 1379 self._cache['index'] = {} 1380 1381 objId = id(el) 1382 1383 count = 0 1384 1385 for e in self._elements: 1386 if e is el: 1387 self._cache['index'][objId] = count 1388 return count 1389 count += 1 1390 for e in self._endElements: 1391 if e is el: 1392 self._cache['index'][objId] = count 1393 return count # this is the index 1394 count += 1 # cumulative indices 1395 raise StreamException(f'cannot find object ({el}) in Stream') 1396 1397 def remove(self, 1398 targetOrList: Union[base.Music21Object, List[base.Music21Object]], 1399 *, 1400 shiftOffsets=False, 1401 recurse=False): 1402 # noinspection PyShadowingNames 1403 ''' 1404 Remove an object from this Stream. Additionally, this Stream is 1405 removed from the object's sites in :class:`~music21.sites.Sites`. 1406 1407 If a list of objects is passed, they will all be removed. 1408 If shiftOffsets is True, then offsets will be 1409 corrected after object removal. It is more efficient to pass 1410 a list of objects than to call remove on 1411 each object individually if shiftOffsets is True. 1412 1413 >>> import copy 1414 >>> s = stream.Stream() 1415 >>> n1 = note.Note('g') 1416 >>> n2 = note.Note('g#') 1417 1418 Copies of an object are not the same as the object 1419 1420 >>> n3 = copy.deepcopy(n2) 1421 >>> s.insert(10, n1) 1422 >>> s.insert(5, n2) 1423 >>> s.remove(n1) 1424 >>> len(s) 1425 1 1426 >>> s.insert(20, n3) 1427 >>> s.remove(n3) 1428 >>> [e for e in s] == [n2] 1429 True 1430 1431 No error is raised if the target is not found. 1432 1433 >>> s.remove(n3) 1434 1435 >>> s2 = stream.Stream() 1436 >>> c = clef.TrebleClef() 1437 >>> n1, n2, n3, n4 = note.Note('a'), note.Note('b'), note.Note('c'), note.Note('d') 1438 >>> n5, n6, n7, n8 = note.Note('e'), note.Note('f'), note.Note('g'), note.Note('a') 1439 >>> s2.insert(0.0, c) 1440 >>> s2.append([n1, n2, n3, n4, n5, n6, n7, n8]) 1441 >>> s2.remove(n1, shiftOffsets=True) 1442 >>> s2.show('text') 1443 {0.0} <music21.clef.TrebleClef> 1444 {0.0} <music21.note.Note B> 1445 {1.0} <music21.note.Note C> 1446 {2.0} <music21.note.Note D> 1447 {3.0} <music21.note.Note E> 1448 {4.0} <music21.note.Note F> 1449 {5.0} <music21.note.Note G> 1450 {6.0} <music21.note.Note A> 1451 1452 >>> s2.remove([n3, n6, n4], shiftOffsets=True) 1453 >>> s2.show('text') 1454 {0.0} <music21.clef.TrebleClef> 1455 {0.0} <music21.note.Note B> 1456 {1.0} <music21.note.Note E> 1457 {2.0} <music21.note.Note G> 1458 {3.0} <music21.note.Note A> 1459 1460 With the recurse=True parameter, we can remove elements deeply nested. 1461 However, shiftOffsets 1462 does not work with recurse=True yet. 1463 1464 >>> p1 = stream.Part() 1465 >>> m1 = stream.Measure(number=1) 1466 >>> c = clef.BassClef() 1467 >>> m1.insert(0, c) 1468 >>> m1.append(note.Note(type='whole')) 1469 >>> p1.append(m1) 1470 >>> m2 = stream.Measure(number=2) 1471 >>> n2 = note.Note('D', type='half') 1472 >>> m2.append(n2) 1473 >>> n3 = note.Note(type='half') 1474 >>> m2.append(n3) 1475 >>> p1.append(m2) 1476 >>> p1.show('text') 1477 {0.0} <music21.stream.Measure 1 offset=0.0> 1478 {0.0} <music21.clef.BassClef> 1479 {0.0} <music21.note.Note C> 1480 {4.0} <music21.stream.Measure 2 offset=4.0> 1481 {0.0} <music21.note.Note D> 1482 {2.0} <music21.note.Note C> 1483 1484 Without recurse=True: 1485 1486 >>> p1.remove(n2) 1487 >>> p1.show('text') 1488 {0.0} <music21.stream.Measure 1 offset=0.0> 1489 {0.0} <music21.clef.BassClef> 1490 {0.0} <music21.note.Note C> 1491 {4.0} <music21.stream.Measure 2 offset=4.0> 1492 {0.0} <music21.note.Note D> 1493 {2.0} <music21.note.Note C> 1494 1495 With recurse=True: 1496 1497 >>> p1.remove(n2, recurse=True) 1498 >>> p1.show('text') 1499 {0.0} <music21.stream.Measure 1 offset=0.0> 1500 {0.0} <music21.clef.BassClef> 1501 {0.0} <music21.note.Note C> 1502 {4.0} <music21.stream.Measure 2 offset=4.0> 1503 {2.0} <music21.note.Note C> 1504 1505 With recurse=True and a list to remove: 1506 1507 >>> p1.remove([c, n3], recurse=True) 1508 >>> p1.show('text') 1509 {0.0} <music21.stream.Measure 1 offset=0.0> 1510 {0.0} <music21.note.Note C> 1511 {4.0} <music21.stream.Measure 2 offset=4.0> 1512 <BLANKLINE> 1513 1514 1515 Can also remove elements stored at end: 1516 1517 >>> streamWithBarline = stream.Stream(note.Note()) 1518 >>> barline = bar.Barline('final') 1519 >>> streamWithBarline.storeAtEnd(barline) 1520 >>> barline in streamWithBarline 1521 True 1522 >>> streamWithBarline.remove(barline) 1523 >>> barline in streamWithBarline 1524 False 1525 1526 Changed in v5.3 -- firstMatchOnly removed -- impossible to have element 1527 in stream twice. recurse and shiftOffsets changed to keywordOnly arguments 1528 ''' 1529 # experimental 1530 if self._mutable is False: # pragma: no cover 1531 raise ImmutableStreamException('Cannot remove from an immutable stream') 1532 # TODO: Next to clean up... a doozy -- filter out all the different options. 1533 1534 # TODO: Add a renumber measures option 1535 # TODO: Shift offsets if recurse is True 1536 if shiftOffsets is True and recurse is True: # pragma: no cover 1537 raise StreamException( 1538 'Cannot do both shiftOffsets and recurse search at the same time...yet') 1539 1540 if not common.isListLike(targetOrList): 1541 targetList = [targetOrList] 1542 elif len(targetOrList) > 1: 1543 try: 1544 targetList = sorted(targetOrList, key=self.elementOffset) 1545 except sites.SitesException: 1546 # will not be found if recursing, it's not such a big deal... 1547 targetList = targetOrList 1548 else: 1549 targetList = targetOrList 1550 1551 shiftDur = 0.0 # for shiftOffsets 1552 1553 for i, target in enumerate(targetList): 1554 try: 1555 indexInStream = self.index(target) 1556 except StreamException as se: 1557 if not isinstance(target, base.Music21Object): 1558 raise TypeError(f'{target} is not a Music21Object; got {type(target)}') from se 1559 if recurse is True: 1560 for s in self.recurse(streamsOnly=True): 1561 try: 1562 indexInStream = s.index(target) 1563 s.remove(target) 1564 break 1565 except (StreamException, sites.SitesException): 1566 continue 1567 # recursion matched or didn't or wasn't run. either way no need for rest... 1568 continue 1569 1570 # Anything that messes with ._elements or ._endElements should be in core.py 1571 # TODO: move it... 1572 matchedEndElement = False 1573 baseElementCount = len(self._elements) 1574 matchOffset = 0.0 # to avoid possibility of undefined 1575 1576 if indexInStream < baseElementCount: 1577 match = self._elements.pop(indexInStream) 1578 else: 1579 match = self._endElements.pop(indexInStream - baseElementCount) 1580 matchedEndElement = True 1581 1582 if match is not None: 1583 if shiftOffsets is True: 1584 matchOffset = self.elementOffset(match) 1585 1586 try: 1587 del self._offsetDict[id(match)] 1588 except KeyError: # pragma: no cover 1589 pass 1590 self.coreElementsChanged(clearIsSorted=False) 1591 match.sites.remove(self) 1592 match.activeSite = None 1593 1594 if shiftOffsets is True and matchedEndElement is False: 1595 matchDuration = match.duration.quarterLength 1596 shiftedRegionStart = matchOffset + matchDuration 1597 if (i + 1) < len(targetList): 1598 shiftedRegionEnd = self.elementOffset(targetList[i + 1]) 1599 else: 1600 shiftedRegionEnd = self.duration.quarterLength 1601 1602 shiftDur += matchDuration 1603 if shiftDur != 0.0: 1604 # can this be done with recurse??? 1605 for e in self.getElementsByOffset(shiftedRegionStart, 1606 shiftedRegionEnd, 1607 includeEndBoundary=False, 1608 mustFinishInSpan=False, 1609 mustBeginInSpan=True): 1610 1611 elementOffset = self.elementOffset(e) 1612 self.coreSetElementOffset(e, elementOffset - shiftDur) 1613 # if renumberMeasures is True and matchedEndElement is False: 1614 # pass # This should maybe just call a function renumberMeasures 1615 self.coreElementsChanged(clearIsSorted=False) 1616 1617 def pop(self, index: int) -> base.Music21Object: 1618 ''' 1619 Return and remove the object found at the 1620 user-specified index value. Index values are 1621 those found in `elements` and are not necessary offset order. 1622 1623 >>> a = stream.Stream() 1624 >>> a.repeatInsert(note.Note('C'), list(range(10))) 1625 >>> junk = a.pop(0) 1626 >>> len(a) 1627 9 1628 ''' 1629 eLen = len(self._elements) 1630 # if less then base length, its in _elements 1631 if index < eLen: 1632 post = self._elements.pop(index) 1633 else: # it is in the _endElements 1634 post = self._endElements.pop(index - eLen) 1635 1636 self.coreElementsChanged(clearIsSorted=False) 1637 1638 try: 1639 del self._offsetDict[id(post)] 1640 except KeyError: # pragma: no cover 1641 pass 1642 1643 post.sites.remove(self) 1644 post.activeSite = None 1645 return post 1646 1647 def _removeIteration(self, streamIterator): 1648 ''' 1649 helper method to remove different kinds of elements. 1650 ''' 1651 popDict = {'_elements': [], 1652 '_endElements': [] 1653 } 1654 for unused_el in streamIterator: 1655 ai = streamIterator.activeInformation 1656 popDict[ai['iterSection']].append(ai['sectionIndex']) 1657 1658 # do not pop while iterating... 1659 1660 for section in popDict: 1661 sectionList = getattr(self, section) # self._elements or self._endElements 1662 popList = popDict[section] 1663 for popIndex in reversed(popList): 1664 removeElement = sectionList.pop(popIndex) 1665 try: 1666 del self._offsetDict[id(removeElement)] 1667 except KeyError: # pragma: no cover 1668 pass 1669 1670 # TODO: to make recursive, store a tuple of active sites and index 1671 removeElement.sites.remove(self) 1672 removeElement.activeSite = None 1673 1674 # call elements changed once; sorted arrangement has not changed 1675 self.coreElementsChanged(clearIsSorted=False) 1676 1677 def removeByClass(self, classFilterList) -> None: 1678 ''' 1679 Remove all elements from the Stream 1680 based on one or more classes given 1681 in a list. 1682 1683 >>> s = stream.Stream() 1684 >>> s.append(meter.TimeSignature('4/4')) 1685 >>> s.repeatAppend(note.Note('C'), 8) 1686 >>> len(s) 1687 9 1688 >>> s.removeByClass('GeneralNote') 1689 >>> len(s) 1690 1 1691 >>> len(s.notes) 1692 0 1693 1694 Test that removing from end elements works. 1695 1696 >>> s = stream.Measure() 1697 >>> s.append(meter.TimeSignature('4/4')) 1698 >>> s.repeatAppend(note.Note('C'), 4) 1699 >>> s.rightBarline = bar.Barline('final') 1700 >>> len(s) 1701 6 1702 >>> s.removeByClass('Barline') 1703 >>> len(s) 1704 5 1705 ''' 1706 elFilter = self.iter().getElementsByClass(classFilterList) 1707 self._removeIteration(elFilter) 1708 1709 def removeByNotOfClass(self, classFilterList): 1710 ''' 1711 Remove all elements not of the specified 1712 class or subclass in the Stream in place. 1713 1714 >>> s = stream.Stream() 1715 >>> s.append(meter.TimeSignature('4/4')) 1716 >>> s.repeatAppend(note.Note('C'), 8) 1717 >>> len(s) 1718 9 1719 >>> s.removeByNotOfClass('TimeSignature') 1720 >>> len(s) 1721 1 1722 >>> len(s.notes) 1723 0 1724 ''' 1725 elFilter = self.iter().getElementsNotOfClass(classFilterList) 1726 return self._removeIteration(elFilter) 1727 1728 def _deepcopySubclassable(self, memo=None, ignoreAttributes=None, removeFromIgnore=None): 1729 # NOTE: this is a performance critical operation 1730 defaultIgnoreSet = {'_offsetDict', 'streamStatus', '_elements', '_endElements', '_cache', 1731 } 1732 if ignoreAttributes is None: 1733 ignoreAttributes = defaultIgnoreSet 1734 else: # pragma: no cover 1735 ignoreAttributes = ignoreAttributes | defaultIgnoreSet 1736 new = super()._deepcopySubclassable(memo, ignoreAttributes, removeFromIgnore) 1737 1738 if removeFromIgnore is not None: # pragma: no cover 1739 ignoreAttributes = ignoreAttributes - removeFromIgnore 1740 1741 if '_offsetDict' in ignoreAttributes: 1742 newValue = {} 1743 setattr(new, '_offsetDict', newValue) 1744 # all subclasses of Music21Object that define their own 1745 # __deepcopy__ methods must be sure to not try to copy activeSite 1746 if '_offsetDict' in self.__dict__: 1747 newValue = {} 1748 setattr(new, '_offsetDict', newValue) 1749 if 'streamStatus' in ignoreAttributes: 1750 # update the client 1751 if self.streamStatus is not None: 1752 # storedClient = self.streamStatus.client # should be self 1753 # self.streamStatus.client = None 1754 newValue = copy.deepcopy(self.streamStatus) 1755 newValue.client = new 1756 setattr(new, 'streamStatus', newValue) 1757 # self.streamStatus.client = storedClient 1758 if '_elements' in ignoreAttributes: 1759 # must manually add elements to new Stream 1760 for e in self._elements: 1761 # environLocal.printDebug(['deepcopy()', e, 'old', old, 'id(old)', id(old), 1762 # 'new', new, 'id(new)', id(new), 'old.hasElement(e)', old.hasElement(e), 1763 # 'e.activeSite', e.activeSite, 'e.getSites()', e.getSites(), 'e.getSiteIds()', 1764 # e.getSiteIds()], format='block') 1765 # 1766 # this will work for all with __deepcopy___ 1767 # get the old offset from the activeSite Stream 1768 # user here to provide new offset 1769 # 1770 # new.insert(e.getOffsetBySite(old), newElement, 1771 # ignoreSort=True) 1772 offset = self.elementOffset(e) 1773 if not e.isStream: 1774 # noinspection PyArgumentList 1775 newElement = copy.deepcopy(e, memo) 1776 else: # this prevents needing to make multiple replacements of spanner bundles 1777 newElement = e._deepcopySubclassable(memo) 1778 1779 # ## TEST on copying!!!! 1780 # if isinstance(newElement, note.Note): 1781 # newElement.pitch.ps += 2.0 1782 new.coreInsert(offset, newElement, ignoreSort=True) 1783 if '_endElements' in ignoreAttributes: 1784 # must manually add elements to 1785 for e in self._endElements: 1786 # this will work for all with __deepcopy___ 1787 # get the old offset from the activeSite Stream 1788 # user here to provide new offset 1789 1790 # noinspection PyArgumentList 1791 new.coreStoreAtEnd(copy.deepcopy(e, memo)) 1792 1793 new.coreElementsChanged() 1794 1795 return new 1796 1797 def __deepcopy__(self, memo=None): 1798 ''' 1799 Deepcopy the stream from copy.deepcopy() 1800 ''' 1801 # does not purgeOrphans -- q: is that a bug or by design? 1802 new = self._deepcopySubclassable(memo) 1803 if new._elements: 1804 self._replaceSpannerBundleForDeepcopy(new) 1805 1806 # purging these orphans works in nearly all cases, but there are a few 1807 # cases where we rely on a Stream having access to Stream it was 1808 # part of after deepcopying 1809 # new.purgeOrphans() 1810 return new 1811 1812 def _replaceSpannerBundleForDeepcopy(self, new): 1813 # perform the spanner bundle replacement on the outer stream. 1814 # caching this is CRUCIAL! using new.spannerBundle every time below added 1815 # 40% to the test suite time! 1816 newSpannerBundle = new.spannerBundle 1817 # only proceed if there are spanners, otherwise creating semiFlat 1818 if not newSpannerBundle: 1819 return 1820 # iterate over complete semi-flat (need containers); find 1821 # all new/old pairs 1822 for e in new.recurse(includeSelf=False): 1823 # update based on id of old object, and ref to new object 1824 if 'music21.spanner.Spanner' in e.classSet: 1825 continue 1826 if e.derivation.method != '__deepcopy__': 1827 continue 1828 1829 origin = e.derivation.origin 1830 if origin is None: # pragma: no cover 1831 continue # should not happen... 1832 1833 if origin.sites.hasSpannerSite(): 1834 # environLocal.printDebug(['Stream.__deepcopy__', 'replacing component to', e]) 1835 # this will clear and replace the proper locations on 1836 # the SpannerStorage Stream 1837 newSpannerBundle.replaceSpannedElement(origin, e) 1838 1839 # need to remove the old SpannerStorage Stream from this element; 1840 # however, all we have here is the new Spanner and new elements 1841 # this must be done here, not when originally copying 1842 e.purgeOrphans(excludeStorageStreams=False) 1843 1844 def setElementOffset( 1845 self, 1846 element: base.Music21Object, 1847 offset: Union[int, float, Fraction, str], 1848 ): 1849 ''' 1850 Sets the Offset for an element that is already in a given stream. 1851 1852 Setup a note in two different streams at two different offsets: 1853 1854 >>> n = note.Note('B-4') 1855 >>> s = stream.Stream(id='Stream1') 1856 >>> s.insert(10, n) 1857 >>> n.offset 1858 10.0 1859 >>> n.activeSite.id 1860 'Stream1' 1861 1862 >>> s2 = stream.Stream(id='Stream2') 1863 >>> s2.insert(30, n) 1864 >>> n.activeSite.id 1865 'Stream2' 1866 1867 Now change the note's offset in Stream1: 1868 1869 >>> s.setElementOffset(n, 20.0) 1870 1871 This call has the effect of switching the `activeSite` of `n` to `s`. 1872 1873 >>> n.activeSite.id 1874 'Stream1' 1875 >>> n.offset 1876 20.0 1877 >>> n.getOffsetBySite(s) 1878 20.0 1879 1880 If the element is not in the Stream, raises a StreamException: 1881 1882 >>> n2 = note.Note('D') 1883 >>> s.setElementOffset(n2, 30.0) 1884 Traceback (most recent call last): 1885 music21.exceptions21.StreamException: Cannot set the offset for element 1886 <music21.note.Note D>, not in Stream <music21.stream.Stream Stream1>. 1887 1888 * Changed in v5.5 -- also sets .activeSite for the element 1889 1890 * In v6.7 -- also runs coreElementsChanged() 1891 1892 * In v7. -- addElement is removed; see 1893 :meth:`~music21.stream.core.StreamCoreMixin.coreSetElementOffset` 1894 ''' 1895 self.coreSetElementOffset(element, 1896 offset, 1897 ) 1898 # might change sorting, but not flatness. Maybe other things can be False too. 1899 self.coreElementsChanged(updateIsFlat=False) 1900 1901 def elementOffset(self, element, returnSpecial=False): 1902 ''' 1903 Return the offset as an opFrac (float or Fraction) from the offsetMap. 1904 highly optimized for speed. 1905 1906 >>> m = stream.Measure(number=1) 1907 >>> m.append(note.Note('C')) 1908 >>> d = note.Note('D') 1909 >>> m.append(d) 1910 >>> m.elementOffset(d) 1911 1.0 1912 1913 If returnSpecial is True then returns like OffsetSpecial.AT_END are allowed. 1914 1915 >>> b = bar.Barline() 1916 >>> m.storeAtEnd(b) 1917 >>> m.elementOffset(b) 1918 2.0 1919 >>> m.elementOffset(b, returnSpecial=True) 1920 <OffsetSpecial.AT_END> 1921 1922 Unlike element.getOffsetBySite(self), this method will NOT follow derivation chains 1923 and in fact will raise a sites.SitesException 1924 1925 >>> import copy 1926 >>> p = stream.Part(id='sPart') 1927 >>> p.insert(20, m) 1928 >>> m.getOffsetBySite(p) 1929 20.0 1930 >>> p.elementOffset(m) 1931 20.0 1932 1933 >>> mCopy = copy.deepcopy(m) 1934 >>> mCopy.number = 10 1935 >>> mCopy.derivation 1936 <Derivation of <music21.stream.Measure 10 offset=0.0> from 1937 <music21.stream.Measure 1 offset=20.0> via '__deepcopy__'> 1938 >>> mCopy.getOffsetBySite(p) 1939 20.0 1940 >>> p.elementOffset(mCopy) 1941 Traceback (most recent call last): 1942 music21.sites.SitesException: an entry for this object 0x... is not stored in 1943 stream <music21.stream.Part sPart> 1944 1945 Performance note: because it will not follow derivation chains, and does 1946 not need to unwrap a weakref, this method 1947 should usually be about 3x faster than element.getOffsetBySite(self) -- 1948 currently 600ns instead of 1.5 microseconds. 1949 ''' 1950 try: 1951 # 2.3 million times found in TestStream 1952 o = self._offsetDict[id(element)][0] 1953 # if returnedElement is not element: # stale reference... 1954 # o = None # 0 in TestStream -- not worth testing 1955 except KeyError: # 445k - 442,443 = 3k in TestStream 1956 for idElement in self._offsetDict: # slower search 1957 o, returnedElement = self._offsetDict[idElement] 1958 if element is returnedElement: 1959 # MSC 2021 -- it is possible this no longer ever happens, 1960 # currently uncovered in Coverage. 1961 break 1962 else: 1963 raise base.SitesException( 1964 f'an entry for this object 0x{id(element):x} is not stored in stream {self}') 1965 1966 # OffsetSpecial.__contains__() is more expensive, so try to fail fast 1967 if isinstance(o, str) and returnSpecial is False and o in OffsetSpecial: 1968 try: 1969 return getattr(self, o) 1970 except AttributeError: # pragma: no cover 1971 raise base.SitesException( 1972 'attempted to retrieve a bound offset with a string ' 1973 + f'attribute that is not supported: {o}') 1974 else: 1975 return o 1976 1977 def insert(self, 1978 offsetOrItemOrList, 1979 itemOrNone=None, 1980 *, 1981 ignoreSort=False, 1982 setActiveSite=True 1983 ): 1984 ''' 1985 Inserts an item(s) at the given offset(s). 1986 1987 If `ignoreSort` is True then the inserting does not 1988 change whether the Stream is sorted or not (much faster if you're 1989 going to be inserting dozens 1990 of items that don't change the sort status) 1991 1992 The `setActiveSite` parameter should nearly always be True; only for 1993 advanced Stream manipulation would you not change 1994 the activeSite after inserting an element. 1995 1996 Has three forms: in the two argument form, inserts an element at the given offset: 1997 1998 >>> st1 = stream.Stream() 1999 >>> st1.insert(32, note.Note('B-')) 2000 >>> st1.highestOffset 2001 32.0 2002 2003 In the single argument form with an object, inserts the element at its stored offset: 2004 2005 >>> n1 = note.Note('C#') 2006 >>> n1.offset = 30.0 2007 >>> st1 = stream.Stream() 2008 >>> st1.insert(n1) 2009 >>> st2 = stream.Stream() 2010 >>> st2.insert(40.0, n1) 2011 >>> n1.getOffsetBySite(st1) 2012 30.0 2013 2014 In single argument form with a list, the list should contain pairs that alternate 2015 offsets and items; the method then, obviously, inserts the items 2016 at the specified offsets: 2017 2018 >>> n1 = note.Note('G') 2019 >>> n2 = note.Note('F#') 2020 >>> st3 = stream.Stream() 2021 >>> st3.insert([1.0, n1, 2.0, n2]) 2022 >>> n1.getOffsetBySite(st3) 2023 1.0 2024 >>> n2.getOffsetBySite(st3) 2025 2.0 2026 >>> len(st3) 2027 2 2028 2029 Raises an error if offset is not a number 2030 2031 >>> stream.Stream().insert('l', note.Note('B')) 2032 Traceback (most recent call last): 2033 music21.exceptions21.StreamException: Offset 'l' must be a number. 2034 2035 ...or if the object is not a music21 object (or a list of them) 2036 2037 >>> stream.Stream().insert(3.3, 'hello') 2038 Traceback (most recent call last): 2039 music21.exceptions21.StreamException: to put a non Music21Object in a stream, 2040 create a music21.ElementWrapper for the item 2041 2042 The error message is slightly different in the one-element form: 2043 2044 >>> stream.Stream().insert('hello') 2045 Traceback (most recent call last): 2046 music21.exceptions21.StreamException: Cannot insert item 'hello' to 2047 stream -- is it a music21 object? 2048 ''' 2049 # environLocal.printDebug(['self', self, 'offsetOrItemOrList', 2050 # offsetOrItemOrList, 'itemOrNone', itemOrNone, 2051 # 'ignoreSort', ignoreSort, 'setActiveSite', setActiveSite]) 2052 # normal approach: provide offset and item 2053 if itemOrNone is not None: 2054 offset = offsetOrItemOrList 2055 item = itemOrNone 2056 elif itemOrNone is None and isinstance(offsetOrItemOrList, list): 2057 i = 0 2058 while i < len(offsetOrItemOrList): 2059 offset = offsetOrItemOrList[i] 2060 item = offsetOrItemOrList[i + 1] 2061 # recursively calling insert() here 2062 self.insert(offset, item, ignoreSort=ignoreSort) 2063 i += 2 2064 return 2065 # assume first arg is item, and that offset is local offset of object 2066 else: 2067 item = offsetOrItemOrList 2068 # offset = item.offset 2069 # this is equivalent to: 2070 try: 2071 activeSite = item.activeSite 2072 offset = item.getOffsetBySite(activeSite) 2073 except AttributeError: 2074 raise StreamException(f'Cannot insert item {item!r} to stream ' 2075 + '-- is it a music21 object?') 2076 2077 # if not common.isNum(offset): 2078 try: # using float conversion instead of isNum for performance 2079 offset = float(offset) 2080 except (ValueError, TypeError): 2081 raise StreamException(f'Offset {offset!r} must be a number.') 2082 2083 element = item 2084 2085 # checks if element is self, among other checks 2086 self.coreGuardBeforeAddElement(element) 2087 # main insert procedure here 2088 2089 storeSorted = self.coreInsert(offset, element, 2090 ignoreSort=ignoreSort, setActiveSite=setActiveSite) 2091 updateIsFlat = False 2092 if element.isStream: 2093 updateIsFlat = True 2094 self.coreElementsChanged(updateIsFlat=updateIsFlat) 2095 if ignoreSort is False: 2096 self.isSorted = storeSorted 2097 2098 def insertIntoNoteOrChord(self, offset, noteOrChord, chordsOnly=False): 2099 # noinspection PyShadowingNames 2100 ''' 2101 Insert a Note or Chord into an offset position in this Stream. 2102 If there is another Note or Chord in this position, 2103 create a new Note or Chord that combines the pitches of the 2104 inserted chord. If there is a Rest in this position, 2105 the Rest is replaced by the Note or Chord. The duration of the 2106 previously-found chord will remain the same in the new Chord. 2107 2108 >>> n1 = note.Note('D4') 2109 >>> n1.duration.quarterLength = 2.0 2110 >>> r1 = note.Rest() 2111 >>> r1.duration.quarterLength = 2.0 2112 >>> c1 = chord.Chord(['C4', 'E4']) 2113 >>> s = stream.Stream() 2114 >>> s.append(n1) 2115 >>> s.append(r1) 2116 >>> s.append(c1) 2117 >>> s.show('text') 2118 {0.0} <music21.note.Note D> 2119 {2.0} <music21.note.Rest half> 2120 {4.0} <music21.chord.Chord C4 E4> 2121 2122 Save the original Streams for later 2123 2124 >>> import copy 2125 >>> s2 = copy.deepcopy(s) 2126 >>> s3 = copy.deepcopy(s) 2127 >>> s4 = copy.deepcopy(s) 2128 2129 Notice that the duration of the inserted element is not taken into 2130 consideration and the original element is not broken up, 2131 as it would be in chordify(). But Chords and Notes are created... 2132 2133 >>> for i in [0.0, 2.0, 4.0]: 2134 ... s.insertIntoNoteOrChord(i, note.Note('F#4')) 2135 >>> s.show('text') 2136 {0.0} <music21.chord.Chord D4 F#4> 2137 {2.0} <music21.note.Note F#> 2138 {4.0} <music21.chord.Chord C4 E4 F#4> 2139 2140 if chordsOnly is set to True then no notes are returned, only chords, but 2141 untouched notes are left alone: 2142 2143 >>> s2.insert(5.0, note.Note('E##4')) 2144 >>> for i in [0.0, 2.0, 4.0]: 2145 ... s2.insertIntoNoteOrChord(i, note.Note('F#4'), chordsOnly=True) 2146 >>> s2.show('text') 2147 {0.0} <music21.chord.Chord D4 F#4> 2148 {2.0} <music21.chord.Chord F#4> 2149 {4.0} <music21.chord.Chord C4 E4 F#4> 2150 {5.0} <music21.note.Note E##> 2151 2152 A chord inserted on top of a note always changes the note into a chord: 2153 2154 >>> s2.insertIntoNoteOrChord(5.0, chord.Chord('F#4 G-4')) 2155 >>> s2.show('text') 2156 {0.0} <music21.chord.Chord D4 F#4> 2157 {2.0} <music21.chord.Chord F#4> 2158 {4.0} <music21.chord.Chord C4 E4 F#4> 2159 {5.0} <music21.chord.Chord E##4 F#4 G-4> 2160 2161 Chords can also be inserted into rests: 2162 2163 >>> s3.getElementsByOffset(2.0).first() 2164 <music21.note.Rest half> 2165 >>> s3.insertIntoNoteOrChord(2.0, chord.Chord('C4 E4 G#4')) 2166 >>> s3.show('text') 2167 {0.0} <music21.note.Note D> 2168 {2.0} <music21.chord.Chord C4 E4 G#4> 2169 {4.0} <music21.chord.Chord C4 E4> 2170 2171 Despite the variable name, a rest could be inserted into a noteOrChord. 2172 It does nothing to existing notes or chords, and just adds a new rest 2173 afterwards. 2174 2175 >>> s4.show('text', addEndTimes=True) 2176 {0.0 - 2.0} <music21.note.Note D> 2177 {2.0 - 4.0} <music21.note.Rest half> 2178 {4.0 - 5.0} <music21.chord.Chord C4 E4> 2179 2180 >>> for i in [0.0, 4.0, 6.0]: # skipping 2.0 for now 2181 ... r = note.Rest(type='quarter') 2182 ... s4.insertIntoNoteOrChord(i, r) 2183 >>> r2 = note.Rest(type='quarter') 2184 >>> s4.insertIntoNoteOrChord(2.0, r) 2185 >>> s4.show('text', addEndTimes=True) 2186 {0.0 - 2.0} <music21.note.Note D> 2187 {2.0 - 4.0} <music21.note.Rest half> 2188 {4.0 - 5.0} <music21.chord.Chord C4 E4> 2189 {6.0 - 7.0} <music21.note.Rest quarter> 2190 2191 Notice that (1) the original duration and not the new duration is used, unless 2192 there is no element at that place, and (2) if an element is put into a place where 2193 no existing element was found, then it will be found in the new Stream, but if it 2194 is placed on top of an existing element, the original element or a new copy will remain: 2195 2196 >>> r in s4 2197 True 2198 >>> r2 in s4 2199 False 2200 2201 If a Stream has more than one note, chord, or rest at that position, 2202 currently an error is raised. This may change later: 2203 2204 >>> s5 = stream.Stream() 2205 >>> s5.insert(0, note.Note('C##4')) 2206 >>> s5.insert(0, note.Note('E--4')) 2207 >>> s5.insertIntoNoteOrChord(0, note.Note('D4')) 2208 Traceback (most recent call last): 2209 music21.exceptions21.StreamException: more than one element found at the specified offset 2210 ''' 2211 # could use duration of Note to get end offset span 2212 targets = list( 2213 self.getElementsByOffset( 2214 offset, 2215 offset + noteOrChord.quarterLength, # set end to dur of supplied 2216 includeEndBoundary=False, 2217 mustFinishInSpan=False, 2218 mustBeginInSpan=True 2219 ).notesAndRests 2220 ) 2221 removeTarget = None 2222 # environLocal.printDebug(['insertIntoNoteOrChord', [e for e in targets]]) 2223 if len(targets) == 1: 2224 pitches = [] # avoid an undefined variable warning... 2225 components = [] # ditto 2226 2227 target = targets[0] # assume first 2228 removeTarget = target 2229 if isinstance(target, note.Rest): 2230 if isinstance(noteOrChord, note.Note): 2231 pitches = [noteOrChord.pitch] 2232 components = [noteOrChord] 2233 elif isinstance(noteOrChord, chord.Chord): 2234 pitches = list(noteOrChord.pitches) 2235 components = list(noteOrChord) 2236 if isinstance(target, note.Note): 2237 # if a note, make it into a chord 2238 if isinstance(noteOrChord, note.Note): 2239 pitches = [target.pitch, noteOrChord.pitch] 2240 components = [target, noteOrChord] 2241 elif isinstance(noteOrChord, chord.Chord): 2242 pitches = [target.pitch] + list(noteOrChord.pitches) 2243 components = [target] + list(noteOrChord) 2244 else: 2245 pitches = [target.pitch] 2246 components = [target] 2247 if isinstance(target, chord.Chord): 2248 # if a chord, make it into a chord 2249 if isinstance(noteOrChord, note.Note): 2250 pitches = list(target.pitches) + [noteOrChord.pitch] 2251 components = list(target) + [noteOrChord] 2252 elif isinstance(noteOrChord, chord.Chord): 2253 pitches = list(target.pitches) + list(noteOrChord.pitches) 2254 components = list(target) + list(noteOrChord) 2255 else: 2256 pitches = list(target.pitches) 2257 components = list(target) 2258 2259 if len(pitches) > 1 or chordsOnly is True: 2260 finalTarget = chord.Chord(pitches) 2261 elif len(pitches) == 1: 2262 finalTarget = note.Note(pitches[0]) 2263 else: 2264 finalTarget = note.Rest() 2265 2266 finalTarget.expressions = target.expressions 2267 finalTarget.articulations = target.articulations 2268 finalTarget.duration = target.duration 2269 # append lyrics list 2270 if hasattr(target, 'lyrics'): 2271 for ly in target.lyrics: 2272 if ly.text not in ('', None): 2273 finalTarget.addLyric(ly.text) 2274 # finalTarget.lyrics = target.lyrics 2275 if hasattr(finalTarget, 'stemDirection') and hasattr(target, 'stemDirection'): 2276 finalTarget.stemDirection = target.stemDirection 2277 if hasattr(finalTarget, 'noteheadFill') and hasattr(target, 'noteheadFill'): 2278 finalTarget.noteheadFill = target.noteheadFill 2279 2280 # fill component details 2281 if isinstance(finalTarget, chord.Chord): 2282 for i, n in enumerate(finalTarget): 2283 nPrevious = components[i] 2284 n.noteheadFill = nPrevious.noteheadFill 2285 2286 elif len(targets) > 1: 2287 raise StreamException('more than one element found at the specified offset') 2288 else: 2289 finalTarget = noteOrChord 2290 2291 if removeTarget is not None: 2292 self.remove(removeTarget) 2293 # insert normally, nothing to handle 2294 self.insert(offset, finalTarget, ignoreSort=False, setActiveSite=True) 2295 2296 def append(self, others): 2297 ''' 2298 Add a Music21Object (including another Stream) to the end of the current Stream. 2299 2300 If given a list, will append each element in order after the previous one. 2301 2302 The "end" of the stream is determined by the `highestTime` property 2303 (that is the latest "release" of an object, or directly after the last 2304 element ends). 2305 2306 Runs fast for multiple addition and will preserve isSorted if True 2307 2308 >>> a = stream.Stream() 2309 >>> notes = [] 2310 >>> for x in range(3): 2311 ... n = note.Note('G#') 2312 ... n.duration.quarterLength = 3 2313 ... notes.append(n) 2314 >>> a.append(notes[0]) 2315 >>> a.highestOffset, a.highestTime 2316 (0.0, 3.0) 2317 >>> a.append(notes[1]) 2318 >>> a.highestOffset, a.highestTime 2319 (3.0, 6.0) 2320 >>> a.append(notes[2]) 2321 >>> a.highestOffset, a.highestTime 2322 (6.0, 9.0) 2323 >>> notes2 = [] 2324 2325 Notes' naive offsets will 2326 change when they are added to a stream. 2327 2328 >>> for x in range(3): 2329 ... n = note.Note('A-') 2330 ... n.duration.quarterLength = 3 2331 ... n.offset = 0 2332 ... notes2.append(n) 2333 >>> a.append(notes2) # add em all again 2334 >>> a.highestOffset, a.highestTime 2335 (15.0, 18.0) 2336 >>> a.isSequence() 2337 True 2338 2339 Adding a note that already has an offset set does nothing different 2340 from above! That is, it is still added to the end of the Stream: 2341 2342 >>> n3 = note.Note('B-') 2343 >>> n3.offset = 1 2344 >>> n3.duration.quarterLength = 3 2345 >>> a.append(n3) 2346 >>> a.highestOffset, a.highestTime 2347 (18.0, 21.0) 2348 >>> n3.getOffsetBySite(a) 2349 18.0 2350 2351 Prior to v5.7 there was a bug where appending a `Clef` after a `KeySignature` 2352 or a `Measure` after a `KeySignature`, etc. would not cause sorting to be re-run. 2353 This bug is now fixed. 2354 2355 >>> s = stream.Stream() 2356 >>> s.append([meter.TimeSignature('4/4'), 2357 ... clef.TrebleClef()]) 2358 >>> s.elements[0] 2359 <music21.clef.TrebleClef> 2360 >>> s.show('text') 2361 {0.0} <music21.clef.TrebleClef> 2362 {0.0} <music21.meter.TimeSignature 4/4> 2363 2364 >>> s.append(metadata.Metadata(composer='Cage')) 2365 >>> s.show('text') 2366 {0.0} <music21.metadata.Metadata object at 0x11ca356a0> 2367 {0.0} <music21.clef.TrebleClef> 2368 {0.0} <music21.meter.TimeSignature 4/4> 2369 ''' 2370 # store and increment highest time for insert offset 2371 highestTime = self.highestTime 2372 if not common.isListLike(others): 2373 # back into a list for list processing if single 2374 others = [others] 2375 2376 clearIsSorted = False 2377 if self._elements: 2378 lastElement = self._elements[-1] 2379 else: 2380 lastElement = None 2381 2382 updateIsFlat = False 2383 for e in others: 2384 try: 2385 if e.isStream: # any on that is a Stream req update 2386 updateIsFlat = True 2387 except AttributeError: 2388 raise StreamException( 2389 f'The object you tried to add to the Stream, {e!r}, ' 2390 + 'is not a Music21Object. Use an ElementWrapper object ' 2391 + 'if this is what you intend') 2392 self.coreGuardBeforeAddElement(e) 2393 # add this Stream as a location for the new elements, with the 2394 # the offset set to the current highestTime 2395 self.coreSetElementOffset(e, highestTime, addElement=True) 2396 e.sites.add(self) 2397 # need to explicitly set the activeSite of the element 2398 self.coreSelfActiveSite(e) 2399 self._elements.append(e) 2400 2401 if e.duration.quarterLength != 0: 2402 # environLocal.printDebug(['incrementing highest time', 2403 # 'e.duration.quarterLength', 2404 # e.duration.quarterLength]) 2405 highestTime += e.duration.quarterLength 2406 if lastElement is not None and not lastElement.duration.quarterLength: 2407 if (e.priority < lastElement.priority 2408 or e.classSortOrder < lastElement.classSortOrder): 2409 clearIsSorted = True 2410 lastElement = e 2411 2412 # does not normally change sorted state 2413 if clearIsSorted: 2414 storeSorted = False 2415 else: 2416 storeSorted = self.isSorted 2417 2418 # we cannot keep the index cache here b/c we might 2419 self.coreElementsChanged(updateIsFlat=updateIsFlat) 2420 self.isSorted = storeSorted 2421 self._setHighestTime(opFrac(highestTime)) # call after to store in cache 2422 2423 def storeAtEnd(self, itemOrList, ignoreSort=False): 2424 ''' 2425 Inserts an item or items at the end of the Stream, 2426 stored in the special box (called _endElements). 2427 2428 This method is useful for putting things such as 2429 right bar lines or courtesy clefs that should always 2430 be at the end of a Stream no matter what else is appended 2431 to it. 2432 2433 As sorting is done only by priority and class, 2434 it cannot avoid setting isSorted to False. 2435 2436 >>> s = stream.Stream() 2437 >>> b = bar.Repeat() 2438 >>> s.storeAtEnd(b) 2439 >>> b in s 2440 True 2441 >>> s.elementOffset(b) 2442 0.0 2443 >>> s.elementOffset(b, returnSpecial=True) 2444 <OffsetSpecial.AT_END> 2445 2446 Only elements of zero duration can be stored. Otherwise a 2447 `StreamException` is raised. 2448 ''' 2449 if isinstance(itemOrList, list): 2450 for item in itemOrList: 2451 # recursively calling insert() here 2452 self.storeAtEnd(item, ignoreSort=ignoreSort) 2453 return 2454 else: 2455 item = itemOrList 2456 2457 element = item 2458 # checks if element is self, among other checks 2459 self.coreGuardBeforeAddElement(element) 2460 2461 # cannot support elements with Durations in the highest time list 2462 if element.duration.quarterLength != 0: 2463 raise StreamException('cannot insert an object with a non-zero ' 2464 + 'Duration into the highest time elements list') 2465 2466 self.coreStoreAtEnd(element) 2467 # Streams cannot reside in end elements, thus do not update is flat 2468 self.coreElementsChanged(updateIsFlat=False) 2469 2470 # -------------------------------------------------------------------------- 2471 # all the following call either insert() or append() 2472 2473 def insertAndShift(self, offsetOrItemOrList, itemOrNone=None): 2474 ''' 2475 Insert an item at a specified or native offset, 2476 and shift any elements found in the Stream to start at 2477 the end of the added elements. 2478 2479 This presently does not shift elements that have durations 2480 that extend into the lowest insert position. 2481 2482 >>> st1 = stream.Stream() 2483 >>> st1.insertAndShift(32, note.Note('B')) 2484 >>> st1.highestOffset 2485 32.0 2486 >>> st1.insertAndShift(32, note.Note('C')) 2487 >>> st1.highestOffset 2488 33.0 2489 >>> st1.show('text', addEndTimes=True) 2490 {32.0 - 33.0} <music21.note.Note C> 2491 {33.0 - 34.0} <music21.note.Note B> 2492 2493 Let's insert an item at the beginning, note that 2494 since the C and B are not affected, they do not shift. 2495 2496 >>> st1.insertAndShift(0, note.Note('D')) 2497 >>> st1.show('text', addEndTimes=True) 2498 {0.0 - 1.0} <music21.note.Note D> 2499 {32.0 - 33.0} <music21.note.Note C> 2500 {33.0 - 34.0} <music21.note.Note B> 2501 2502 But if we insert something again at the beginning of the stream, 2503 everything after the first shifted note begins shifting, so the 2504 C and the B shift even though there is a gap there. Normally 2505 there's no gaps in a stream, so this will not be a factor: 2506 2507 >>> st1.insertAndShift(0, note.Note('E')) 2508 >>> st1.show('text', addEndTimes=True) 2509 {0.0 - 1.0} <music21.note.Note E> 2510 {1.0 - 2.0} <music21.note.Note D> 2511 {33.0 - 34.0} <music21.note.Note C> 2512 {34.0 - 35.0} <music21.note.Note B> 2513 2514 In the single argument form with an object, inserts the element at its stored offset: 2515 2516 >>> n1 = note.Note('C#') 2517 >>> n1.offset = 30.0 2518 >>> n2 = note.Note('D#') 2519 >>> n2.offset = 30.0 2520 >>> st1 = stream.Stream() 2521 >>> st1.insertAndShift(n1) 2522 >>> st1.insertAndShift(n2) # will shift offset of n1 2523 >>> n1.getOffsetBySite(st1) 2524 31.0 2525 >>> n2.getOffsetBySite(st1) 2526 30.0 2527 >>> st1.show('text', addEndTimes=True) 2528 {30.0 - 31.0} <music21.note.Note D#> 2529 {31.0 - 32.0} <music21.note.Note C#> 2530 2531 >>> st2 = stream.Stream() 2532 >>> st2.insertAndShift(40.0, n1) 2533 >>> st2.insertAndShift(40.0, n2) 2534 >>> n1.getOffsetBySite(st2) 2535 41.0 2536 2537 In single argument form with a list, the list should contain pairs that alternate 2538 offsets and items; the method then, obviously, inserts the items 2539 at the specified offsets: 2540 2541 >>> n1 = note.Note('G-') 2542 >>> n2 = note.Note('F-') 2543 >>> st3 = stream.Stream() 2544 >>> st3.insertAndShift([1.0, n1, 2.0, n2]) 2545 >>> n1.getOffsetBySite(st3) 2546 1.0 2547 >>> n2.getOffsetBySite(st3) 2548 2.0 2549 >>> len(st3) 2550 2 2551 >>> st3.show('text', addEndTimes=True) 2552 {1.0 - 2.0} <music21.note.Note G-> 2553 {2.0 - 3.0} <music21.note.Note F-> 2554 2555 N.B. -- using this method on a list assumes that you'll be inserting 2556 contiguous objects; you can't shift things that are separated, as this 2557 following FAILED example shows. 2558 2559 >>> n1 = note.Note('G', type='half') 2560 >>> st4 = stream.Stream() 2561 >>> st4.repeatAppend(n1, 3) 2562 >>> st4.insertAndShift([2.0, note.Note('e'), 4.0, note.Note('f')]) 2563 >>> st4.show('text') 2564 {0.0} <music21.note.Note G> 2565 {2.0} <music21.note.Note E> 2566 {4.0} <music21.note.Note F> 2567 {5.0} <music21.note.Note G> 2568 {7.0} <music21.note.Note G> 2569 2570 As an FYI, there is no removeAndShift() function, so the opposite of 2571 insertAndShift(el) is remove(el, shiftOffsets=True). 2572 ''' 2573 # need to find the highest time after the insert 2574 if itemOrNone is not None: # we have an offset and an element 2575 insertObject = itemOrNone 2576 qL = insertObject.duration.quarterLength 2577 offset = offsetOrItemOrList 2578 lowestOffsetInsert = offset 2579 highestTimeInsert = offset + qL 2580 elif itemOrNone is None and isinstance(offsetOrItemOrList, list): 2581 # need to find which has the highest endtime (combined offset and dur) 2582 insertList = offsetOrItemOrList 2583 highestTimeInsert = 0.0 2584 lowestOffsetInsert = None 2585 i = 0 2586 while i < len(insertList): 2587 o = insertList[i] 2588 e = insertList[i + 1] 2589 qL = e.duration.quarterLength 2590 if o + qL > highestTimeInsert: 2591 highestTimeInsert = o + qL 2592 if lowestOffsetInsert is None or o < lowestOffsetInsert: 2593 lowestOffsetInsert = o 2594 i += 2 2595 else: # using native offset 2596 # if hasattr(offsetOrItemOrList, 'duration'): 2597 insertObject = offsetOrItemOrList 2598 qL = insertObject.duration.quarterLength 2599 # should this be getOffsetBySite(None)? 2600 highestTimeInsert = insertObject.offset + qL 2601 lowestOffsetInsert = insertObject.offset 2602 2603 # this shift is the additional time to move due to the duration 2604 # of the newly inserted elements 2605 2606 # environLocal.printDebug(['insertAndShift()', 2607 # 'adding one or more elements', 2608 # 'lowestOffsetInsert', lowestOffsetInsert, 2609 # 'highestTimeInsert', highestTimeInsert]) 2610 2611 # are not assuming that elements are ordered 2612 # use getElementAtOrAfter() in the future 2613 lowestElementToShift = None 2614 lowestGap = None 2615 for e in self._elements: 2616 o = self.elementOffset(e) 2617 # gap is distance from offset to insert point; tells if shift is 2618 # necessary 2619 gap = o - lowestOffsetInsert 2620 if gap < 0: # no shifting necessary 2621 continue 2622 # only process elements whose offsets are after the lowest insert 2623 if lowestGap is None or gap < lowestGap: 2624 lowestGap = gap 2625 lowestElementToShift = e 2626 2627 if lowestElementToShift is not None: 2628 lowestOffsetToShift = self.elementOffset(lowestElementToShift) 2629 shiftPos = highestTimeInsert - lowestOffsetToShift 2630 else: 2631 shiftPos = 0 2632 2633 if shiftPos <= 0: 2634 pass # no need to move any objects 2635 # See stream.tests.Test.testInsertAndShiftNoDuration 2636 # clef insertion at offset 3 which gives shiftPos < 0 2637 else: 2638 # need to move all the elements already in this stream 2639 for e in self._elements: 2640 o = self.elementOffset(e) 2641 # gap is distance from offset to insert point; tells if shift is 2642 # necessary 2643 gap = o - lowestOffsetInsert 2644 # only process elements whose offsets are after the lowest insert 2645 if gap >= 0.0: 2646 # environLocal.printDebug(['insertAndShift()', e, 'offset', o, 2647 # 'gap:', gap, 'shiftDur:', shiftDur, 2648 # 'shiftPos:', shiftPos, 'o+shiftDur', o+shiftDur, 2649 # 'o+shiftPos', o+shiftPos]) 2650 2651 # need original offset, shiftDur, plus the distance from the start 2652 self.coreSetElementOffset(e, o + shiftPos) 2653 # after shifting all the necessary elements, append new ones 2654 # these will not be in order 2655 self.coreElementsChanged() 2656 self.insert(offsetOrItemOrList, itemOrNone) 2657 2658 # -------------------------------------------------------------------------- 2659 # searching and replacing routines 2660 2661 def setDerivationMethod(self, derivationMethod, recurse=False): 2662 ''' 2663 Sets the .derivation.method for each element in the Stream 2664 if it has a .derivation object. 2665 2666 >>> import copy 2667 >>> s = converter.parse('tinyNotation: 2/4 c2 d e f') 2668 >>> s2 = copy.deepcopy(s) 2669 >>> s2.recurse().notes[-1].derivation 2670 <Derivation of <music21.note.Note F> from <music21.note.Note F> via '__deepcopy__'> 2671 >>> s2.setDerivationMethod('exampleCopy', recurse=True) 2672 >>> s2.recurse().notes[-1].derivation 2673 <Derivation of <music21.note.Note F> from <music21.note.Note F> via 'exampleCopy'> 2674 2675 Without recurse: 2676 2677 >>> s = converter.parse('tinyNotation: 2/4 c2 d e f') 2678 >>> s2 = copy.deepcopy(s) 2679 >>> s2.setDerivationMethod('exampleCopy') 2680 >>> s2.recurse().notes[-1].derivation 2681 <Derivation of <music21.note.Note F> from <music21.note.Note F> via '__deepcopy__'> 2682 ''' 2683 if recurse: 2684 sIter = self.recurse() 2685 else: 2686 sIter = self.iter() 2687 2688 for el in sIter: 2689 if el.derivation is not None: 2690 el.derivation.method = derivationMethod 2691 2692 def replace(self, 2693 target: base.Music21Object, 2694 replacement: base.Music21Object, 2695 *, 2696 recurse: bool = False, 2697 allDerived: bool = True) -> None: 2698 ''' 2699 Given a `target` object, replace it with 2700 the supplied `replacement` object. 2701 2702 Does nothing if target cannot be found. Raises StreamException if replacement 2703 is already in the stream. 2704 2705 If `allDerived` is True (as it is by default), all sites (stream) that 2706 this this stream derives from and also 2707 have a reference for the replacement will be similarly changed. 2708 This is useful for altering both a flat and nested representation. 2709 2710 >>> cSharp = note.Note('C#4') 2711 >>> s = stream.Stream() 2712 >>> s.insert(0, cSharp) 2713 >>> dFlat = note.Note('D-4') 2714 >>> s.replace(cSharp, dFlat) 2715 >>> s.show('t') 2716 {0.0} <music21.note.Note D-> 2717 2718 If allDerived is True then all streams that this stream comes from get changed 2719 (but not non-derived streams) 2720 2721 >>> otherStream = stream.Stream() 2722 >>> otherStream.insert(0, dFlat) 2723 >>> f = note.Note('F4') 2724 >>> sf = s.flatten() 2725 >>> sf is not s 2726 True 2727 >>> sf.replace(dFlat, f, allDerived=True) 2728 >>> sf[0] is f 2729 True 2730 >>> s[0] is f 2731 True 2732 >>> otherStream[0] is dFlat 2733 True 2734 2735 Note that it does not work the other way: if we made the replacement on `s` 2736 then `sf`, the flattened representation, would not be changed, since `s` 2737 does not derive from `sf` but vice-versa. 2738 2739 With `recurse=True`, a stream can replace an element that is 2740 further down in the hierarchy. First let's set up a 2741 nested score: 2742 2743 >>> s = stream.Score() 2744 >>> p = stream.Part(id='part1') 2745 >>> s.append(p) 2746 >>> m = stream.Measure() 2747 >>> p.append(m) 2748 >>> cSharp = note.Note('C#4') 2749 >>> m.append(cSharp) 2750 >>> s.show('text') 2751 {0.0} <music21.stream.Part part1> 2752 {0.0} <music21.stream.Measure 0 offset=0.0> 2753 {0.0} <music21.note.Note C#> 2754 2755 Now make a deep-nested replacement 2756 2757 >>> dFlat = note.Note('D-4') 2758 >>> s.replace(cSharp, dFlat, recurse=True) 2759 >>> s.show('text') 2760 {0.0} <music21.stream.Part part1> 2761 {0.0} <music21.stream.Measure 0 offset=0.0> 2762 {0.0} <music21.note.Note D-> 2763 2764 Changed by v.5: 2765 2766 allTargetSites RENAMED to allDerived -- only searches in derivation chain. 2767 2768 Changed in v5.3 -- firstMatchOnly removed -- impossible to have element 2769 in stream twice. recurse and shiftOffsets changed to keywordOnly arguments 2770 2771 Changed in v6 -- recurse works 2772 2773 Changed in v7 -- raises StreamException if replacement is already in the stream. 2774 ''' 2775 def replaceDerived(startSite=self): 2776 if not allDerived: 2777 return 2778 for derivedSite in startSite.derivation.chain(): 2779 for subsite in derivedSite.recurse(streamsOnly=True, includeSelf=True): 2780 if subsite in target.sites: 2781 subsite.replace(target, 2782 replacement, 2783 recurse=recurse, 2784 allDerived=False) 2785 2786 try: 2787 i = self.index(replacement) 2788 except StreamException: 2789 # good. now continue. 2790 pass 2791 else: 2792 raise StreamException(f'{replacement} already in {self}') 2793 2794 try: 2795 i = self.index(target) 2796 except StreamException: 2797 if recurse: 2798 container = self.containerInHierarchy(target, setActiveSite=False) 2799 if container is not None: 2800 container.replace(target, replacement, allDerived=allDerived) 2801 replaceDerived() 2802 if container is not None: 2803 replaceDerived(startSite=container) 2804 return # do nothing if no match 2805 2806 eLen = len(self._elements) 2807 if i < eLen: 2808 target = self._elements[i] # target may have been obj id; re-classing 2809 self._elements[i] = replacement 2810 # place the replacement at the old objects offset for this site 2811 self.coreSetElementOffset(replacement, self.elementOffset(target), addElement=True) 2812 replacement.sites.add(self) 2813 else: 2814 # target may have been obj id; reassign 2815 target = self._endElements[i - eLen] 2816 self._endElements[i - eLen] = replacement 2817 2818 self.coreSetElementOffset(replacement, OffsetSpecial.AT_END, addElement=True) 2819 replacement.sites.add(self) 2820 2821 target.sites.remove(self) 2822 target.activeSite = None 2823 if id(target) in self._offsetDict: 2824 del(self._offsetDict[id(target)]) 2825 2826 updateIsFlat = False 2827 if replacement.isStream: 2828 updateIsFlat = True 2829 # elements have changed: sort order may change b/c have diff classes 2830 self.coreElementsChanged(updateIsFlat=updateIsFlat) 2831 2832 replaceDerived() 2833 2834 def splitAtDurations(self, *, recurse=False) -> base._SplitTuple: 2835 ''' 2836 Overrides base method :meth:`~music21.base.Music21Object.splitAtDurations` 2837 so that once each element in the stream having a complex duration is split 2838 into similar, shorter elements representing each duration component, 2839 the original element is actually replaced in the stream where it was found 2840 with those new elements. 2841 2842 Returns a 1-tuple containing itself, for consistency with the superclass method. 2843 2844 >>> s = stream.Stream() 2845 >>> s.insert(note.Note(quarterLength=5.0)) 2846 >>> post = s.splitAtDurations() 2847 >>> post 2848 (<music21.stream.Stream 0x10955ceb0>,) 2849 >>> [n.duration for n in s] 2850 [<music21.duration.Duration 4.0>, <music21.duration.Duration 1.0>] 2851 2852 Unless `recurse=True`, notes in substreams will not be found. 2853 2854 >>> s2 = stream.Score() 2855 >>> p = stream.Part([note.Note(quarterLength=5)]) 2856 >>> s2.append(p) 2857 >>> s2.splitAtDurations() 2858 (<music21.stream.Score 0x10d12f100>,) 2859 >>> [n.duration for n in s2.recurse().notes] 2860 [<music21.duration.Duration 5.0>] 2861 >>> s2.splitAtDurations(recurse=True) 2862 (<music21.stream.Score 0x10d12f100>,) 2863 >>> [n.duration for n in s2.recurse().notes] 2864 [<music21.duration.Duration 4.0>, <music21.duration.Duration 1.0>] 2865 2866 `recurse=True` should not be necessary to find elements in streams 2867 without substreams, such as a loose Voice: 2868 2869 >>> v = stream.Voice([note.Note(quarterLength=5.5)], id=1) 2870 >>> v.splitAtDurations() 2871 (<music21.stream.Voice 1>,) 2872 >>> [n.duration for n in v.notes] 2873 [<music21.duration.Duration 4.0>, <music21.duration.Duration 1.5>] 2874 2875 But a Voice in a Measure (most common) will not be found without `recurse`: 2876 2877 >>> m = stream.Measure() 2878 >>> v2 = stream.Voice([note.Note(quarterLength=5.25)]) 2879 >>> m.insert(v2) 2880 >>> m.splitAtDurations() 2881 (<music21.stream.Measure 0 offset=0.0>,) 2882 >>> [n.duration for n in m.recurse().notes] 2883 [<music21.duration.Duration 5.25>] 2884 2885 For any spanner containing the element being removed, the first or last of the 2886 replacing components replaces the removed element 2887 (according to whether it was first or last in the spanner.) 2888 2889 >>> s3 = stream.Stream() 2890 >>> n1 = note.Note(quarterLength=5) 2891 >>> n2 = note.Note(quarterLength=5) 2892 >>> s3.append([n1, n2]) 2893 >>> s3.insert(0, spanner.Slur([n1, n2])) 2894 >>> post = s3.splitAtDurations() 2895 >>> s3.spanners.first().getFirst() is n1 2896 False 2897 >>> s3.spanners.first().getFirst().duration 2898 <music21.duration.Duration 4.0> 2899 >>> s3.spanners.first().getLast().duration 2900 <music21.duration.Duration 1.0> 2901 2902 Does not act on rests where `.fullMeasure` is True or 'always', 2903 nor when `.fullMeasure` is 'auto' and the duration equals the `.barDuration`. 2904 This is because full measure rests are usually represented 2905 as a single whole rest regardless of their duration. 2906 2907 >>> r = note.Rest(quarterLength=5.0) 2908 >>> r.fullMeasure = 'auto' 2909 >>> v = stream.Voice(r) 2910 >>> m = stream.Measure(v) 2911 >>> result = m.splitAtDurations(recurse=True) 2912 >>> list(result[0][note.Rest]) 2913 [<music21.note.Rest 5ql>] 2914 2915 Here is a rest that doesn't fill the measure: 2916 2917 >>> m.insert(0, meter.TimeSignature('6/4')) 2918 >>> result = m.splitAtDurations(recurse=True) 2919 >>> list(result[0][note.Rest]) 2920 [<music21.note.Rest whole>, <music21.note.Rest quarter>] 2921 2922 But by calling it a full-measure rest, we won't try to split it: 2923 2924 >>> r2 = note.Rest(quarterLength=5.0) 2925 >>> r2.fullMeasure = True 2926 >>> m2 = stream.Measure(r2) 2927 >>> m2.insert(0, meter.TimeSignature('6/4')) 2928 >>> result = m2.splitAtDurations() 2929 >>> list(result[0][note.Rest]) 2930 [<music21.note.Rest 5ql>] 2931 ''' 2932 2933 def processContainer(container: Stream): 2934 for complexObj in container.getElementsNotOfClass(['Stream', 'Variant', 'Spanner']): 2935 if complexObj.duration.type != 'complex': 2936 continue 2937 if isinstance(complexObj, note.Rest) and complexObj.fullMeasure in (True, 'always'): 2938 continue 2939 if isinstance(complexObj, note.Rest) and complexObj.fullMeasure == 'auto': 2940 if container.isMeasure and (complexObj.duration == container.barDuration): 2941 continue 2942 elif ('Voice' in container.classes 2943 and container.activeSite 2944 and container.activeSite.isMeasure 2945 and complexObj.duration == container.activeSite.barDuration 2946 ): 2947 continue 2948 2949 insertPoint = complexObj.offset 2950 objList = complexObj.splitAtDurations() 2951 2952 container.replace(complexObj, objList[0]) 2953 insertPoint += objList[0].quarterLength 2954 2955 for subsequent in objList[1:]: 2956 container.insert(insertPoint, subsequent) 2957 insertPoint += subsequent.quarterLength 2958 2959 # Replace elements in spanners 2960 for sp in complexObj.getSpannerSites(): 2961 if sp.getFirst() is complexObj: 2962 sp.replaceSpannedElement(complexObj, objList[0]) 2963 if sp.getLast() is complexObj: 2964 sp.replaceSpannedElement(complexObj, objList[-1]) 2965 2966 container.streamStatus.beams = False 2967 2968 # Handle "loose" objects in self (usually just Measure or Voice) 2969 processContainer(self) 2970 # Handle inner streams 2971 if recurse: 2972 for innerStream in self.recurse( 2973 includeSelf=False, streamsOnly=True, restoreActiveSites=True): 2974 processContainer(innerStream) 2975 2976 return base._SplitTuple((self,)) 2977 2978 def splitAtQuarterLength(self, 2979 quarterLength, 2980 *, 2981 retainOrigin=True, 2982 addTies=True, 2983 displayTiedAccidentals=False, 2984 searchContext=True): 2985 ''' 2986 This method overrides the method on Music21Object to provide 2987 similar functionality for Streams. 2988 2989 Most arguments are passed to Music21Object.splitAtQuarterLength. 2990 2991 Changed in v7. -- all but quarterLength are keyword only 2992 ''' 2993 quarterLength = opFrac(quarterLength) 2994 if retainOrigin: 2995 sLeft = self 2996 else: 2997 sLeft = self.coreCopyAsDerivation('splitAtQuarterLength') 2998 # create empty container for right-hand side 2999 sRight = self.__class__() 3000 3001 # if this is a Measure or Part, transfer clefs, ts, and key 3002 if sLeft.isMeasure: 3003 timeSignatures = sLeft.getTimeSignatures( 3004 searchContext=searchContext, 3005 returnDefault=False, 3006 ) 3007 if timeSignatures: 3008 sRight.keySignature = copy.deepcopy(timeSignatures[0]) 3009 if searchContext: 3010 keySignatures = sLeft.getContextByClass(key.KeySignature) 3011 if keySignatures is not None: 3012 keySignatures = [keySignatures] 3013 else: 3014 keySignatures = sLeft.getElementsByClass(key.KeySignature) 3015 if keySignatures: 3016 sRight.keySignature = copy.deepcopy(keySignatures[0]) 3017 endClef = sLeft.getContextByClass('Clef') 3018 if endClef is not None: 3019 sRight.clef = copy.deepcopy(endClef) 3020 3021 if quarterLength > sLeft.highestTime: # nothing to do 3022 return sLeft, sRight 3023 3024 # use quarterLength as start time 3025 targets = sLeft.getElementsByOffset( 3026 quarterLength, 3027 sLeft.highestTime, 3028 includeEndBoundary=True, 3029 mustFinishInSpan=False, 3030 includeElementsThatEndAtStart=False, 3031 mustBeginInSpan=False 3032 ) 3033 3034 targetSplit = [] 3035 targetMove = [] 3036 # find all those that need to split v. those that need to be moved 3037 for t in targets: 3038 # if target starts before the boundary, it needs to be split 3039 if sLeft.elementOffset(t) < quarterLength: 3040 targetSplit.append(t) 3041 else: 3042 targetMove.append(t) 3043 3044 # environLocal.printDebug(['split', targetSplit, 'move', targetMove]) 3045 3046 for t in targetSplit: 3047 # must retain original, as a deepcopy, if necessary, has 3048 # already been made 3049 3050 # the split point needs to be relative to this element's start 3051 qlSplit = quarterLength - sLeft.elementOffset(t) 3052 unused_eLeft, eRight = t.splitAtQuarterLength( 3053 qlSplit, 3054 retainOrigin=True, 3055 addTies=addTies, 3056 displayTiedAccidentals=displayTiedAccidentals) 3057 # do not need to insert eLeft, as already positioned and 3058 # altered in-place above 3059 # it is assumed that anything cut will start at zero 3060 sRight.insert(0, eRight) 3061 3062 for t in targetMove: 3063 sRight.insert(t.getOffsetBySite(sLeft) - quarterLength, t) 3064 sLeft.remove(t) 3065 3066 return sLeft, sRight 3067 3068 # -------------------------------------------------------------------------- 3069 def recurseRepr(self, 3070 *, 3071 prefixSpaces=0, 3072 addBreaks=True, 3073 addIndent=True, 3074 addEndTimes=False, 3075 useMixedNumerals=False): 3076 ''' 3077 Used by .show('text') to display a stream's contents with offsets. 3078 3079 >>> s1 = stream.Stream() 3080 >>> s2 = stream.Stream() 3081 >>> s3 = stream.Stream() 3082 >>> n1 = note.Note() 3083 >>> s3.append(n1) 3084 >>> s2.append(s3) 3085 >>> s1.append(s2) 3086 >>> post = s1.recurseRepr(addBreaks=False, addIndent=False) 3087 >>> post 3088 '{0.0} <music21.stream.Stream ...> / {0.0} <...> / {0.0} <music21.note.Note C>' 3089 3090 Made public in v7. Always calls on self. 3091 ''' 3092 def singleElement(in_element, 3093 in_indent, 3094 ) -> str: 3095 offGet = in_element.getOffsetBySite(self) 3096 if useMixedNumerals: 3097 off = common.mixedNumeral(offGet) 3098 else: 3099 off = common.strTrimFloat(offGet) 3100 if addEndTimes is False: 3101 return in_indent + '{' + off + '} ' + repr(in_element) 3102 else: 3103 ql = offGet + in_element.duration.quarterLength 3104 if useMixedNumerals: 3105 qlStr = common.mixedNumeral(ql) 3106 else: 3107 qlStr = common.strTrimFloat(ql) 3108 return in_indent + '{' + off + ' - ' + qlStr + '} ' + repr(in_element) 3109 3110 msg = [] 3111 insertSpaces = 4 3112 for element in self: 3113 if addIndent: 3114 indent = ' ' * prefixSpaces 3115 else: 3116 indent = '' 3117 3118 # if isinstance(element, Stream): 3119 if element.isStream: 3120 msg.append(singleElement(element, indent)) 3121 msg.append( 3122 element.recurseRepr(prefixSpaces=prefixSpaces + insertSpaces, 3123 addBreaks=addBreaks, 3124 addIndent=addIndent, 3125 addEndTimes=addEndTimes, 3126 useMixedNumerals=useMixedNumerals) 3127 ) 3128 else: 3129 msg.append(singleElement(element, indent)) 3130 if addBreaks: 3131 msg = '\n'.join(msg) 3132 else: # use slashes with spaces 3133 msg = ' / '.join(msg) 3134 return msg 3135 3136 def _reprText(self, *, addEndTimes=False, useMixedNumerals=False): 3137 ''' 3138 Return a text representation. This methods can be overridden by 3139 subclasses to provide alternative text representations. 3140 3141 This is used by .show('text') 3142 ''' 3143 return self.recurseRepr(addEndTimes=addEndTimes, 3144 useMixedNumerals=useMixedNumerals) 3145 3146 def _reprTextLine(self, *, addEndTimes=False, useMixedNumerals=False): 3147 ''' 3148 Return a text representation without line breaks. 3149 This methods can be overridden by subclasses to 3150 provide alternative text representations. 3151 ''' 3152 return self.recurseRepr(addEndTimes=addEndTimes, 3153 useMixedNumerals=useMixedNumerals, 3154 addBreaks=False, 3155 addIndent=False) 3156 3157 # -------------------------------------------------------------------------- 3158 # display methods; in the same manner as show() and write() 3159 3160 def plot(self, *args, **keywords): 3161 ''' 3162 Given a method and keyword configuration arguments, create and display a plot. 3163 3164 Note: plot() requires the Python package matplotlib to be installed. 3165 3166 For details on arguments this function takes, see 3167 :ref:`User's Guide, Chapter 22: Graphing <usersGuide_22_graphing>`. 3168 3169 >>> s = corpus.parse('demos/two-parts.xml') #_DOCS_HIDE 3170 >>> thePlot = s.plot('pianoroll', doneAction=None) #_DOCS_HIDE 3171 >>> #_DOCS_SHOW s = corpus.parse('bach/bwv57.8') 3172 >>> #_DOCS_SHOW thePlot = s.plot('pianoroll') 3173 3174 .. image:: images/HorizontalBarPitchSpaceOffset.* 3175 :width: 600 3176 3177 ''' 3178 # import is here to avoid import of matplotlib problems 3179 from music21 import graph 3180 # first ordered arg can be method type 3181 return graph.plotStream(self, *args, **keywords) 3182 3183 def analyze(self, *args, **keywords): 3184 ''' 3185 Runs a particular analytical method on the contents of the 3186 stream to find its ambitus (range) or key. 3187 3188 * ambitus -- runs :class:`~music21.analysis.discrete.Ambitus` 3189 * key -- runs :class:`~music21.analysis.discrete.KrumhanslSchmuckler` 3190 3191 Some of these methods can take additional arguments. For details on 3192 these arguments, see 3193 :func:`~music21.analysis.discrete.analyzeStream`. 3194 3195 Example: 3196 3197 3198 >>> s = corpus.parse('bach/bwv66.6') 3199 >>> s.analyze('ambitus') 3200 <music21.interval.Interval m21> 3201 >>> s.analyze('key') 3202 <music21.key.Key of f# minor> 3203 3204 Example: music21 allows you to automatically run an 3205 analysis to get the key of a piece or excerpt not 3206 based on the key signature but instead on the 3207 frequency with which some notes are used as opposed 3208 to others (first described by Carol Krumhansl). For 3209 instance, a piece with mostly Cs and Gs, some Fs, 3210 and Ds, but fewer G#s, C#s, etc. is more likely to 3211 be in the key of C major than in D-flat major 3212 (or A minor, etc.). You can easily get this analysis 3213 from a stream by running: 3214 3215 >>> myStream = corpus.parse('luca/gloria') 3216 >>> analyzedKey = myStream.analyze('key') 3217 >>> analyzedKey 3218 <music21.key.Key of F major> 3219 3220 analyzedKey is a :class:`~music21.key.Key` 3221 object with a few extra parameters. 3222 correlationCoefficient shows how well this key fits the 3223 profile of a piece in that key: 3224 3225 >>> analyzedKey.correlationCoefficient 3226 0.86715... 3227 3228 `alternateInterpretations` is a list of the other 3229 possible interpretations sorted from most likely to least: 3230 3231 >>> analyzedKey.alternateInterpretations 3232 [<music21.key.Key of d minor>, 3233 <music21.key.Key of C major>, 3234 <music21.key.Key of g minor>, 3235 ...] 3236 3237 Each of these can be examined in turn to see its correlation coefficient: 3238 3239 >>> analyzedKey.alternateInterpretations[1].correlationCoefficient 3240 0.788528... 3241 >>> analyzedKey.alternateInterpretations[22].correlationCoefficient 3242 -0.86728... 3243 ''' 3244 3245 from music21.analysis import discrete 3246 # pass this stream to the analysis procedure 3247 return discrete.analyzeStream(self, *args, **keywords) 3248 3249 # -------------------------------------------------------------------------- 3250 # methods that act on individual elements without requiring 3251 # coreElementsChanged to fire 3252 def addGroupForElements(self, group, classFilter=None, *, recurse=False): 3253 ''' 3254 Add the group to the groups attribute of all elements. 3255 if `classFilter` is set then only those elements whose objects 3256 belong to a certain class (or for Streams which are themselves of 3257 a certain class) are set. 3258 3259 >>> a = stream.Stream() 3260 >>> a.repeatAppend(note.Note('A-'), 30) 3261 >>> a.repeatAppend(note.Rest(), 30) 3262 >>> a.addGroupForElements('flute') 3263 >>> a[0].groups 3264 ['flute'] 3265 >>> a.addGroupForElements('quietTime', note.Rest) 3266 >>> a[0].groups 3267 ['flute'] 3268 >>> a[50].groups 3269 ['flute', 'quietTime'] 3270 >>> a[1].groups.append('quietTime') # set one note to it 3271 >>> a[1].step = 'B' 3272 >>> b = a.getElementsByGroup('quietTime') 3273 >>> len(b) 3274 31 3275 >>> c = b.getElementsByClass(note.Note) 3276 >>> len(c) 3277 1 3278 >>> c[0].name 3279 'B-' 3280 3281 If recurse is True then all sub-elements will get the group: 3282 3283 >>> s = converter.parse('tinyNotation: 4/4 c4 d e f g a b- b') 3284 >>> s.addGroupForElements('scaleNote', 'Note') 3285 >>> s.recurse().notes[3].groups 3286 [] 3287 >>> s.addGroupForElements('scaleNote', 'Note', recurse=True) 3288 >>> s.recurse().notes[3].groups 3289 ['scaleNote'] 3290 3291 No group will be added more than once: 3292 3293 >>> s.addGroupForElements('scaleNote', 'Note', recurse=True) 3294 >>> s.recurse().notes[3].groups 3295 ['scaleNote'] 3296 3297 Added in v6.7.1 -- recurse 3298 ''' 3299 sIterator = self.iter() if not recurse else self.recurse() 3300 if classFilter is not None: 3301 sIterator = sIterator.addFilter(filters.ClassFilter(classFilter)) 3302 for el in sIterator: 3303 if group not in el.groups: 3304 el.groups.append(group) 3305 3306 # -------------------------------------------------------------------------- 3307 # getElementsByX(self): anything that returns a collection of Elements 3308 # formerly always returned a Stream; turning to Iterators in September 2015 3309 3310 def getElementsByClass(self, classFilterList) -> iterator.StreamIterator: 3311 ''' 3312 Return a StreamIterator that will iterate over Elements that match one 3313 or more classes in the `classFilterList`. A single class 3314 can also used for the `classFilterList` parameter instead of a List. 3315 3316 >>> a = stream.Score() 3317 >>> a.repeatInsert(note.Rest(), list(range(10))) 3318 >>> for x in range(4): 3319 ... n = note.Note('G#') 3320 ... n.offset = x * 3 3321 ... a.insert(n) 3322 >>> found = a.getElementsByClass(note.Note) 3323 >>> found 3324 <music21.stream.iterator.StreamIterator for Score:0x104f2f400 @:0> 3325 3326 >>> len(found) 3327 4 3328 >>> found[0].pitch.accidental.name 3329 'sharp' 3330 3331 >>> foundStream = found.stream() 3332 >>> isinstance(foundStream, stream.Score) 3333 True 3334 3335 3336 Notice that we do not find elements that are in 3337 sub-streams of the main Stream. We'll add 15 more rests 3338 in a sub-stream and they won't be found: 3339 3340 >>> b = stream.Stream() 3341 >>> b.repeatInsert(note.Rest(), list(range(15))) 3342 >>> a.insert(b) 3343 >>> found = a.getElementsByClass(note.Rest) 3344 >>> len(found) 3345 10 3346 3347 To find them either (1) use `.flatten()` to get at everything: 3348 3349 >>> found = a.flatten().getElementsByClass(note.Rest) 3350 >>> len(found) 3351 25 3352 3353 Or, (2) recurse over the main stream and call .getElementsByClass 3354 on each one. Notice that the first subStream is actually the outermost 3355 Stream: 3356 3357 >>> totalFound = 0 3358 >>> for subStream in a.recurse(streamsOnly=True, includeSelf=True): 3359 ... found = subStream.getElementsByClass(note.Rest) 3360 ... totalFound += len(found) 3361 >>> totalFound 3362 25 3363 3364 The class name of the Stream created is the same as the original: 3365 3366 >>> found = a.getElementsByClass(note.Note).stream() 3367 >>> found.__class__.__name__ 3368 'Score' 3369 3370 ...except if `returnStreamSubClass` is False, which makes the method 3371 return a generic Stream: 3372 3373 >>> found = a.getElementsByClass(note.Rest).stream(returnStreamSubClass=False) 3374 >>> found.__class__.__name__ 3375 'Stream' 3376 3377 3378 Make a list from a StreamIterator: 3379 3380 >>> foundList = list(a.recurse().getElementsByClass(note.Rest)) 3381 >>> len(foundList) 3382 25 3383 ''' 3384 return self.iter().getElementsByClass(classFilterList, returnClone=False) 3385 3386 def getElementsNotOfClass(self, classFilterList) -> iterator.StreamIterator: 3387 ''' 3388 Return a list of all Elements that do not 3389 match the one or more classes in the `classFilterList`. 3390 3391 In lieu of a list, a single class can be used as the `classFilterList` parameter. 3392 3393 >>> a = stream.Stream() 3394 >>> a.repeatInsert(note.Rest(), list(range(10))) 3395 >>> for x in range(4): 3396 ... n = note.Note('G#') 3397 ... n.offset = x * 3 3398 ... a.insert(n) 3399 >>> found = a.getElementsNotOfClass(note.Note) 3400 >>> len(found) 3401 10 3402 3403 >>> b = stream.Stream() 3404 >>> b.repeatInsert(note.Rest(), list(range(15))) 3405 >>> a.insert(b) 3406 3407 Here, it gets elements from within a stream 3408 this probably should not do this, as it is one layer lower 3409 3410 >>> found = a.flatten().getElementsNotOfClass(note.Rest) 3411 >>> len(found) 3412 4 3413 >>> found = a.flatten().getElementsNotOfClass(note.Note) 3414 >>> len(found) 3415 25 3416 ''' 3417 return self.iter().getElementsNotOfClass(classFilterList, returnClone=False) 3418 3419 def getElementsByGroup(self, groupFilterList) -> iterator.StreamIterator: 3420 ''' 3421 >>> n1 = note.Note('C') 3422 >>> n1.groups.append('trombone') 3423 >>> n2 = note.Note('D') 3424 >>> n2.groups.append('trombone') 3425 >>> n2.groups.append('tuba') 3426 >>> n3 = note.Note('E') 3427 >>> n3.groups.append('tuba') 3428 >>> s1 = stream.Stream() 3429 >>> s1.append(n1) 3430 >>> s1.append(n2) 3431 >>> s1.append(n3) 3432 >>> tboneSubStream = s1.getElementsByGroup('trombone') 3433 >>> for thisNote in tboneSubStream: 3434 ... print(thisNote.name) 3435 C 3436 D 3437 >>> tubaSubStream = s1.getElementsByGroup('tuba') 3438 >>> for thisNote in tubaSubStream: 3439 ... print(thisNote.name) 3440 D 3441 E 3442 3443 OMIT_FROM_DOCS 3444 # TODO: group comparisons are not YET case insensitive. 3445 ''' 3446 return self.iter().getElementsByGroup(groupFilterList, returnClone=False) 3447 3448 def getElementById(self, elementId) -> Optional[base.Music21Object]: 3449 ''' 3450 Returns the first encountered element for a given id. Return None 3451 if no match. Note: this uses the id attribute stored on elements, 3452 which may not be the same as id(e). 3453 3454 >>> a = stream.Stream() 3455 >>> ew = note.Note() 3456 >>> a.insert(0, ew) 3457 >>> a[0].id = 'green' 3458 >>> None == a.getElementById(3) 3459 True 3460 >>> a.getElementById('green').id 3461 'green' 3462 >>> a.getElementById('Green').id # case does not matter 3463 'green' 3464 3465 Getting an element by getElementById changes its activeSite 3466 3467 >>> b = stream.Stream() 3468 >>> b.append(ew) 3469 >>> ew.activeSite is b 3470 True 3471 >>> ew2 = a.getElementById('green') 3472 >>> ew2 is ew 3473 True 3474 >>> ew2.activeSite is a 3475 True 3476 >>> ew.activeSite is a 3477 True 3478 3479 Changed in v7. -- remove classFilter. 3480 ''' 3481 sIterator = self.iter().addFilter(filters.IdFilter(elementId)) 3482 for e in sIterator: 3483 return e 3484 return None 3485 3486 def getElementsByOffset( 3487 self, 3488 offsetStart, 3489 offsetEnd=None, 3490 *, 3491 includeEndBoundary=True, 3492 mustFinishInSpan=False, 3493 mustBeginInSpan=True, 3494 includeElementsThatEndAtStart=True, 3495 classList=None 3496 ) -> iterator.StreamIterator: 3497 ''' 3498 Returns a StreamIterator containing all Music21Objects that 3499 are found at a certain offset or within a certain 3500 offset time range (given the `offsetStart` and (optional) `offsetEnd` values). 3501 3502 There are several attributes that govern how this range is 3503 determined: 3504 3505 3506 If `mustFinishInSpan` is True then an event that begins 3507 between offsetStart and offsetEnd but which ends after offsetEnd 3508 will not be included. The default is False. 3509 3510 3511 For instance, a half note at offset 2.0 will be found in 3512 getElementsByOffset(1.5, 2.5) or getElementsByOffset(1.5, 2.5, 3513 mustFinishInSpan=False) but not by getElementsByOffset(1.5, 2.5, 3514 mustFinishInSpan=True). 3515 3516 The `includeEndBoundary` option determines if an element 3517 begun just at the offsetEnd should be included. For instance, 3518 the half note at offset 2.0 above would be found by 3519 getElementsByOffset(0, 2.0) or by getElementsByOffset(0, 2.0, 3520 includeEndBoundary=True) but not by getElementsByOffset(0, 2.0, 3521 includeEndBoundary=False). 3522 3523 Setting includeEndBoundary to False at the same time as 3524 mustFinishInSpan is set to True is probably NOT what you want to do 3525 unless you want to find things like clefs at the end of the region 3526 to display as courtesy clefs. 3527 3528 The `mustBeginInSpan` option determines whether notes or other 3529 objects that do not begin in the region but are still sounding 3530 at the beginning of the region are excluded. The default is 3531 True -- that is, these notes will not be included. 3532 For instance the half note at offset 2.0 from above would not be found by 3533 getElementsByOffset(3.0, 3.5) or getElementsByOffset(3.0, 3.5, 3534 mustBeginInSpan=True) but it would be found by 3535 getElementsByOffset(3.0, 3.5, mustBeginInSpan=False) 3536 3537 Setting includeElementsThatEndAtStart to False is useful for zeroLength 3538 searches that set mustBeginInSpan == False to not catch notes that were 3539 playing before the search but that end just before the end of the search type. 3540 This setting is *ignored* for zero-length searches. 3541 See the code for allPlayingWhileSounding for a demonstration. 3542 3543 This chart, and the examples below, demonstrate the various 3544 features of getElementsByOffset. It is one of the most complex 3545 methods of music21 but also one of the most powerful, so it 3546 is worth learning at least the basics. 3547 3548 .. image:: images/getElementsByOffset.* 3549 :width: 600 3550 3551 3552 >>> st1 = stream.Stream() 3553 >>> n0 = note.Note('C') 3554 >>> n0.duration.type = 'half' 3555 >>> n0.offset = 0 3556 >>> st1.insert(n0) 3557 >>> n2 = note.Note('D') 3558 >>> n2.duration.type = 'half' 3559 >>> n2.offset = 2 3560 >>> st1.insert(n2) 3561 >>> out1 = st1.getElementsByOffset(2) 3562 >>> len(out1) 3563 1 3564 >>> out1[0].step 3565 'D' 3566 3567 >>> out2 = st1.getElementsByOffset(1, 3) 3568 >>> len(out2) 3569 1 3570 >>> out2[0].step 3571 'D' 3572 >>> out3 = st1.getElementsByOffset(1, 3, mustFinishInSpan=True) 3573 >>> len(out3) 3574 0 3575 >>> out4 = st1.getElementsByOffset(1, 2) 3576 >>> len(out4) 3577 1 3578 >>> out4[0].step 3579 'D' 3580 >>> out5 = st1.getElementsByOffset(1, 2, includeEndBoundary=False) 3581 >>> len(out5) 3582 0 3583 >>> out6 = st1.getElementsByOffset(1, 2, includeEndBoundary=False, mustBeginInSpan=False) 3584 >>> len(out6) 3585 1 3586 >>> out6[0].step 3587 'C' 3588 >>> out7 = st1.getElementsByOffset(1, 3, mustBeginInSpan=False) 3589 >>> len(out7) 3590 2 3591 >>> [el.step for el in out7] 3592 ['C', 'D'] 3593 3594 3595 Note, that elements that end at the start offset are included if mustBeginInSpan is False 3596 3597 >>> out8 = st1.getElementsByOffset(2, 4, mustBeginInSpan=False) 3598 >>> len(out8) 3599 2 3600 >>> [el.step for el in out8] 3601 ['C', 'D'] 3602 3603 To change this behavior set includeElementsThatEndAtStart=False 3604 3605 >>> out9 = st1.getElementsByOffset(2, 4, 3606 ... mustBeginInSpan=False, includeElementsThatEndAtStart=False) 3607 >>> len(out9) 3608 1 3609 >>> [el.step for el in out9] 3610 ['D'] 3611 3612 3613 Note how zeroLengthSearches implicitly set includeElementsThatEndAtStart=False. 3614 These two are the same: 3615 3616 >>> out1 = st1.getElementsByOffset(2, mustBeginInSpan=False) 3617 >>> out2 = st1.getElementsByOffset(2, 2, mustBeginInSpan=False) 3618 >>> len(out1) == len(out2) == 1 3619 True 3620 >>> out1[0] is out2[0] is n2 3621 True 3622 3623 But this is different: 3624 3625 >>> out3 = st1.getElementsByOffset(2, 2.1, mustBeginInSpan=False) 3626 >>> len(out3) 3627 2 3628 >>> out3[0] is n0 3629 True 3630 3631 Explicitly setting includeElementsThatEndAtStart=False does not get the 3632 first note: 3633 3634 >>> out4 = st1.getElementsByOffset(2, 2.1, mustBeginInSpan=False, 3635 ... includeElementsThatEndAtStart=False) 3636 >>> len(out4) 3637 1 3638 >>> out4[0] is n2 3639 True 3640 3641 3642 3643 Testing multiple zero-length elements with mustBeginInSpan: 3644 3645 >>> tc = clef.TrebleClef() 3646 >>> ts = meter.TimeSignature('4/4') 3647 >>> ks = key.KeySignature(2) 3648 >>> s = stream.Stream() 3649 >>> s.insert(0.0, tc) 3650 >>> s.insert(0.0, ts) 3651 >>> s.insert(0.0, ks) 3652 >>> len(s.getElementsByOffset(0.0, mustBeginInSpan=True)) 3653 3 3654 >>> len(s.getElementsByOffset(0.0, mustBeginInSpan=False)) 3655 3 3656 3657 OMIT_FROM_DOCS 3658 3659 >>> a = stream.Stream() 3660 >>> n = note.Note('G') 3661 >>> n.quarterLength = 0.5 3662 >>> a.repeatInsert(n, list(range(8))) 3663 >>> b = stream.Stream() 3664 >>> b.repeatInsert(a, [0, 3, 6]) 3665 >>> c = b.getElementsByOffset(2, 6.9) 3666 >>> len(c) 3667 2 3668 >>> c = b.flatten().getElementsByOffset(2, 6.9) 3669 >>> len(c) 3670 10 3671 3672 3673 Same test as above, but with floats 3674 3675 >>> out1 = st1.getElementsByOffset(2.0) 3676 >>> len(out1) 3677 1 3678 >>> out1[0].step 3679 'D' 3680 >>> out2 = st1.getElementsByOffset(1.0, 3.0) 3681 >>> len(out2) 3682 1 3683 >>> out2[0].step 3684 'D' 3685 >>> out3 = st1.getElementsByOffset(1.0, 3.0, mustFinishInSpan=True) 3686 >>> len(out3) 3687 0 3688 >>> out3b = st1.getElementsByOffset(0.0, 3.001, mustFinishInSpan=True) 3689 >>> len(out3b) 3690 1 3691 >>> out3b[0].step 3692 'C' 3693 >>> out3b = st1.getElementsByOffset(1.0, 3.001, 3694 ... mustFinishInSpan=True, mustBeginInSpan=False) 3695 >>> len(out3b) 3696 1 3697 >>> out3b[0].step 3698 'C' 3699 3700 3701 >>> out4 = st1.getElementsByOffset(1.0, 2.0) 3702 >>> len(out4) 3703 1 3704 >>> out4[0].step 3705 'D' 3706 >>> out5 = st1.getElementsByOffset(1.0, 2.0, includeEndBoundary=False) 3707 >>> len(out5) 3708 0 3709 >>> out6 = st1.getElementsByOffset(1.0, 2.0, 3710 ... includeEndBoundary=False, mustBeginInSpan=False) 3711 >>> len(out6) 3712 1 3713 >>> out6[0].step 3714 'C' 3715 >>> out7 = st1.getElementsByOffset(1.0, 3.0, mustBeginInSpan=False) 3716 >>> len(out7) 3717 2 3718 >>> [el.step for el in out7] 3719 ['C', 'D'] 3720 3721 Changed in v5.5: all arguments changing behavior are keyword only. 3722 ''' 3723 sIterator = self.iter().getElementsByOffset( 3724 offsetStart=offsetStart, 3725 offsetEnd=offsetEnd, 3726 includeEndBoundary=includeEndBoundary, 3727 mustFinishInSpan=mustFinishInSpan, 3728 mustBeginInSpan=mustBeginInSpan, 3729 includeElementsThatEndAtStart=includeElementsThatEndAtStart) 3730 if classList is not None: 3731 sIterator = sIterator.getElementsByClass(classList) 3732 return sIterator 3733 3734 def getElementAtOrBefore(self, offset, classList=None) -> Optional[base.Music21Object]: 3735 # noinspection PyShadowingNames 3736 ''' 3737 Given an offset, find the element at this offset, 3738 or with the offset less than and nearest to. 3739 3740 Return one element or None if no elements are at or preceded by this 3741 offset. 3742 3743 If the `classList` parameter is used, it should be a 3744 list of class names or strings, and only objects that 3745 are instances of 3746 these classes or subclasses of these classes will be returned. 3747 3748 >>> stream1 = stream.Stream() 3749 >>> x = note.Note('D4') 3750 >>> x.id = 'x' 3751 >>> y = note.Note('E4') 3752 >>> y.id = 'y' 3753 >>> z = note.Rest() 3754 >>> z.id = 'z' 3755 3756 >>> stream1.insert(20, x) 3757 >>> stream1.insert(10, y) 3758 >>> stream1.insert( 0, z) 3759 3760 >>> b = stream1.getElementAtOrBefore(21) 3761 >>> b.offset, b.id 3762 (20.0, 'x') 3763 3764 >>> b = stream1.getElementAtOrBefore(19) 3765 >>> b.offset, b.id 3766 (10.0, 'y') 3767 3768 >>> b = stream1.getElementAtOrBefore(0) 3769 >>> b.offset, b.id 3770 (0.0, 'z') 3771 >>> b = stream1.getElementAtOrBefore(0.1) 3772 >>> b.offset, b.id 3773 (0.0, 'z') 3774 3775 3776 You can give a list of acceptable classes to return, and non-matching 3777 elements will be ignored 3778 3779 >>> c = stream1.getElementAtOrBefore(100, [clef.TrebleClef, note.Rest]) 3780 >>> c.offset, c.id 3781 (0.0, 'z') 3782 3783 Getting an object via getElementAtOrBefore sets the activeSite 3784 for that object to the Stream, and thus sets its offset 3785 3786 >>> stream2 = stream.Stream() 3787 >>> stream2.insert(100.5, x) 3788 >>> x.offset 3789 100.5 3790 >>> d = stream1.getElementAtOrBefore(20) 3791 >>> d is x 3792 True 3793 >>> x.activeSite is stream1 3794 True 3795 >>> x.offset 3796 20.0 3797 3798 3799 If no element is before the offset, returns None 3800 3801 >>> s = stream.Stream() 3802 >>> s.insert(10, note.Note('E')) 3803 >>> print(s.getElementAtOrBefore(9)) 3804 None 3805 3806 The sort order of returned items is the reverse 3807 of the normal sort order, so that, for instance, 3808 if there's a clef and a note at offset 20, 3809 getting the object before offset 21 will give 3810 you the note, and not the clef, since clefs 3811 sort before notes: 3812 3813 >>> clef1 = clef.BassClef() 3814 >>> stream1.insert(20, clef1) 3815 >>> e = stream1.getElementAtOrBefore(21) 3816 >>> e 3817 <music21.note.Note D> 3818 ''' 3819 # NOTE: this is a performance critical method 3820 3821 # TODO: switch to trees 3822 candidates = [] 3823 offset = opFrac(offset) 3824 nearestTrailSpan = offset # start with max time 3825 3826 sIterator = self.iter() 3827 if classList: 3828 sIterator = sIterator.getElementsByClass(classList) 3829 3830 # need both _elements and _endElements 3831 for e in sIterator: 3832 span = opFrac(offset - self.elementOffset(e)) 3833 # environLocal.printDebug(['e span check', span, 'offset', offset, 3834 # 'e.offset', e.offset, 'self.elementOffset(e)', self.elementOffset(e), 'e', e]) 3835 if span < 0: 3836 continue 3837 elif span == 0: 3838 candidates.append((span, e)) 3839 nearestTrailSpan = span 3840 else: 3841 # do this comparison because may be out of order 3842 if span <= nearestTrailSpan: 3843 candidates.append((span, e)) 3844 nearestTrailSpan = span 3845 # environLocal.printDebug(['getElementAtOrBefore(), e candidates', candidates]) 3846 if candidates: 3847 candidates.sort(key=lambda x: (-1 * x[0], x[1].sortTuple())) 3848 # TODO: this sort has side effects -- see ICMC2011 -- sorting clef vs. note, etc. 3849 self.coreSelfActiveSite(candidates[-1][1]) 3850 return candidates[-1][1] 3851 else: 3852 return None 3853 3854 def getElementBeforeOffset(self, offset, classList=None) -> Optional[base.Music21Object]: 3855 ''' 3856 Get element before (and not at) a provided offset. 3857 3858 If the `classList` parameter is used, it should be a 3859 list of class names or strings, and only objects that 3860 are instances of 3861 these classes or subclasses of these classes will be returned. 3862 3863 >>> stream1 = stream.Stream() 3864 >>> x = note.Note('D4') 3865 >>> x.id = 'x' 3866 >>> y = note.Note('E4') 3867 >>> y.id = 'y' 3868 >>> z = note.Rest() 3869 >>> z.id = 'z' 3870 >>> stream1.insert(20, x) 3871 >>> stream1.insert(10, y) 3872 >>> stream1.insert( 0, z) 3873 3874 >>> b = stream1.getElementBeforeOffset(21) 3875 >>> b.offset, b.id 3876 (20.0, 'x') 3877 >>> b = stream1.getElementBeforeOffset(20) 3878 >>> b.offset, b.id 3879 (10.0, 'y') 3880 3881 >>> b = stream1.getElementBeforeOffset(10) 3882 >>> b.offset, b.id 3883 (0.0, 'z') 3884 3885 >>> b = stream1.getElementBeforeOffset(0) 3886 >>> b is None 3887 True 3888 >>> b = stream1.getElementBeforeOffset(0.1) 3889 >>> b.offset, b.id 3890 (0.0, 'z') 3891 3892 >>> w = note.Note('F4') 3893 >>> w.id = 'w' 3894 >>> stream1.insert( 0, w) 3895 3896 This should get w because it was inserted last. 3897 3898 >>> b = stream1.getElementBeforeOffset(0.1) 3899 >>> b.offset, b.id 3900 (0.0, 'w') 3901 3902 But if we give it a lower priority than z then z will appear first. 3903 3904 >>> w.priority = z.priority - 1 3905 >>> b = stream1.getElementBeforeOffset(0.1) 3906 >>> b.offset, b.id 3907 (0.0, 'z') 3908 ''' 3909 # NOTE: this is a performance critical method 3910 candidates = [] 3911 offset = opFrac(offset) 3912 nearestTrailSpan = offset # start with max time 3913 3914 sIterator = self.iter() 3915 if classList: 3916 sIterator = sIterator.getElementsByClass(classList) 3917 3918 for e in sIterator: 3919 span = opFrac(offset - self.elementOffset(e)) 3920 # environLocal.printDebug(['e span check', span, 'offset', offset, 3921 # 'e.offset', e.offset, 'self.elementOffset(e)', self.elementOffset(e), 'e', e]) 3922 # by forcing <= here, we are sure to get offsets not at zero 3923 if span <= 0: # the e is after this offset 3924 continue 3925 else: # do this comparison because may be out of order 3926 if span <= nearestTrailSpan: 3927 candidates.append((span, e)) 3928 nearestTrailSpan = span 3929 # environLocal.printDebug(['getElementBeforeOffset(), e candidates', candidates]) 3930 if candidates: 3931 candidates.sort(key=lambda x: (-1 * x[0], x[1].sortTuple())) 3932 self.coreSelfActiveSite(candidates[-1][1]) 3933 return candidates[-1][1] 3934 else: 3935 return None 3936 3937 # def getElementAfterOffset(self, offset, classList=None): 3938 # '''Get element after a provided offset 3939 # 3940 # TODO: write this 3941 # ''' 3942 # raise Exception('not yet implemented') 3943 # 3944 # 3945 # def getElementBeforeElement(self, element, classList=None): 3946 # '''given an element, get the element before 3947 # 3948 # TODO: write this 3949 # ''' 3950 # raise Exception('not yet implemented') 3951 3952 def getElementAfterElement(self, element, classList=None): 3953 ''' 3954 Given an element, get the next element. If classList is specified, 3955 check to make sure that the element is an instance of the class list 3956 3957 >>> st1 = stream.Stream() 3958 >>> n1 = note.Note('C4') 3959 >>> n2 = note.Note('D4') 3960 >>> r3 = note.Rest() 3961 >>> st1.append([n1, n2, r3]) 3962 >>> t2 = st1.getElementAfterElement(n1) 3963 >>> t2 is n2 3964 True 3965 >>> t3 = st1.getElementAfterElement(t2) 3966 >>> t3 is r3 3967 True 3968 >>> t4 = st1.getElementAfterElement(t3) 3969 >>> t4 3970 3971 >>> t5 = st1.getElementAfterElement(n1, [note.Rest]) 3972 >>> t5 3973 <music21.note.Rest quarter> 3974 >>> t5 is r3 3975 True 3976 >>> t6 = st1.getElementAfterElement(n1, [note.Rest, note.Note]) 3977 >>> t6 is n2 3978 True 3979 3980 >>> t7 = st1.getElementAfterElement(r3) 3981 >>> t7 is None 3982 True 3983 3984 3985 If the element is not in the stream, it will raise a StreamException: 3986 3987 >>> st1.getElementAfterElement(note.Note('C#')) 3988 Traceback (most recent call last): 3989 music21.exceptions21.StreamException: 3990 cannot find object (<music21.note.Note C#>) in Stream 3991 3992 ''' 3993 if classList is not None: 3994 classSet = set(classList) 3995 else: 3996 classSet = None 3997 3998 # index() ultimately does an autoSort check, so no check here or 3999 # sorting is necessary 4000 # also, this will raise a StreamException if element is not in the Stream 4001 elPos = self.index(element) 4002 4003 # store once as a property call concatenates 4004 elements = self.elements 4005 if classList is None: 4006 if elPos == len(elements) - 1: 4007 return None 4008 else: 4009 e = elements[elPos + 1] 4010 self.coreSelfActiveSite(e) 4011 return e 4012 else: 4013 for i in range(elPos + 1, len(elements)): 4014 if classList is None or classSet.intersection(elements[i].classSet): 4015 e = elements[i] 4016 self.coreSelfActiveSite(e) 4017 return e 4018 4019 # ---------------------------------------------------- 4020 # end .getElement filters 4021 4022 # ------------------------------------------------------------------------- 4023 # routines for obtaining specific types of elements from a Stream 4024 # _getNotes and _getPitches are found with the interval routines 4025 4026 def measures(self, 4027 numberStart, 4028 numberEnd, 4029 collect=('Clef', 'TimeSignature', 'Instrument', 'KeySignature'), 4030 gatherSpanners=GatherSpanners.ALL, 4031 indicesNotNumbers=False): 4032 ''' 4033 Get a region of Measures based on a start and end Measure number 4034 where the boundary numbers are both included. 4035 4036 That is, a request for measures 4 through 10 will return 7 Measures, numbers 4 through 10. 4037 4038 Additionally, any number of associated classes can be gathered from the context 4039 and put into the measure. By default we collect the Clef, TimeSignature, KeySignature, 4040 and Instrument so that there is enough context to perform. (See getContextByClass() 4041 and .previous() for definitions of the context) 4042 4043 While all elements in the source are the original elements in the extracted region, 4044 new Measure objects are created and returned. 4045 4046 >>> bachIn = corpus.parse('bach/bwv66.6') 4047 >>> bachExcerpt = bachIn.parts[0].measures(1, 3) 4048 >>> len(bachExcerpt.getElementsByClass('Measure')) 4049 3 4050 4051 Because bwv66.6 has a pickup measure, and we requested to start at measure 1, 4052 this is NOT true: 4053 4054 >>> firstExcerptMeasure = bachExcerpt.getElementsByClass('Measure').first() 4055 >>> firstBachMeasure = bachIn.parts[0].getElementsByClass('Measure').first() 4056 >>> firstExcerptMeasure is firstBachMeasure 4057 False 4058 >>> firstBachMeasure.number 4059 0 4060 >>> firstExcerptMeasure.number 4061 1 4062 4063 4064 To get all measures from the beginning, go ahead and always request measure 0 to x, 4065 there will be no error if there is not a pickup measure. 4066 4067 >>> bachExcerpt = bachIn.parts[0].measures(0, 3) 4068 >>> excerptNote = bachExcerpt.getElementsByClass('Measure').first().notes.first() 4069 >>> originalNote = bachIn.parts[0].recurse().notes[0] 4070 >>> excerptNote is originalNote 4071 True 4072 4073 if `indicesNotNumbers` is True, then it ignores defined measureNumbers and 4074 uses 0-indexed measure objects and half-open range. For instance, if you have a piece 4075 that goes "m1, m2, m3, m4, ..." (like a standard piece without pickups, then 4076 `.measures(1, 3, indicesNotNumbers=True)` would return measures 2 and 3, because 4077 it is interpreted as the slice from object with index 1, which is measure 2 (m1 has 4078 index 0) up to but NOT including the object with index 3, which is measure 4. 4079 IndicesNotNumbers is like a Python-slice. 4080 4081 >>> bachExcerpt2 = bachIn.parts[0].measures(0, 2, indicesNotNumbers=True) 4082 >>> for m in bachExcerpt2.getElementsByClass('Measure'): 4083 ... print(m) 4084 ... print(m.number) 4085 <music21.stream.Measure 0 offset=0.0> 4086 0 4087 <music21.stream.Measure 1 offset=1.0> 4088 1 4089 4090 4091 If `numberEnd=None` then it is interpreted as the last measure of the stream: 4092 4093 >>> bachExcerpt3 = bachIn.parts[0].measures(7, None) 4094 >>> for m in bachExcerpt3.getElementsByClass('Measure'): 4095 ... print(m) 4096 <music21.stream.Measure 7 offset=0.0> 4097 <music21.stream.Measure 8 offset=4.0> 4098 <music21.stream.Measure 9 offset=8.0> 4099 4100 Note that the offsets in the new stream are shifted so that the first measure 4101 in the excerpt begins at 0.0 4102 4103 The measure elements are the same objects as the original: 4104 4105 >>> lastExcerptMeasure = bachExcerpt3.getElementsByClass('Measure').last() 4106 >>> lastOriginalMeasure = bachIn.parts[0].getElementsByClass('Measure').last() 4107 >>> lastExcerptMeasure is lastOriginalMeasure 4108 True 4109 4110 At the beginning of the Stream returned, before the measures will be some additional 4111 objects so that the context is properly preserved: 4112 4113 >>> for thing in bachExcerpt3: 4114 ... print(thing) 4115 P1: Soprano: Instrument 1 4116 <music21.clef.TrebleClef> 4117 f# minor 4118 <music21.meter.TimeSignature 4/4> 4119 <music21.stream.Measure 7 offset=0.0> 4120 <music21.stream.Measure 8 offset=4.0> 4121 <music21.stream.Measure 9 offset=8.0> 4122 4123 Collecting gets the most recent element in the context of the stream: 4124 4125 >>> bachIn.parts[0].insert(10, key.Key('D-')) 4126 >>> bachExcerpt4 = bachIn.parts[0].measures(7, None) 4127 >>> for thing in bachExcerpt4: 4128 ... print(thing) 4129 P1: Soprano: Instrument 1 4130 <music21.clef.TrebleClef> 4131 D- major 4132 ... 4133 4134 4135 What is collected is determined by the "collect" iterable. To collect nothing 4136 send an empty list: 4137 4138 >>> bachExcerpt5 = bachIn.parts[0].measures(8, None, collect=[]) 4139 >>> for thing in bachExcerpt5: 4140 ... print(thing) 4141 <music21.stream.Measure 8 offset=0.0> 4142 <music21.stream.Measure 9 offset=4.0> 4143 4144 4145 If a stream has measure suffixes, then Streams having that suffix or no suffix 4146 are returned. 4147 4148 >>> p = stream.Part() 4149 >>> mSuffix3 = stream.Measure(number=3) 4150 >>> mSuffix4 = stream.Measure(number=4) 4151 >>> mSuffix4a = stream.Measure(number=4) 4152 >>> mSuffix4a.numberSuffix = 'a' 4153 >>> mSuffix4b = stream.Measure(number=4) 4154 >>> mSuffix4b.numberSuffix = 'b' 4155 >>> mSuffix5 = stream.Measure(number=5) 4156 >>> mSuffix5a = stream.Measure(number=5) 4157 >>> mSuffix5a.numberSuffix = 'a' 4158 >>> mSuffix6 = stream.Measure(number=6) 4159 >>> p.append([mSuffix3, mSuffix4, mSuffix4a, mSuffix4b, mSuffix5, mSuffix5a, mSuffix6]) 4160 >>> suffixExcerpt = p.measures('4b', 6) 4161 >>> suffixExcerpt.show('text') 4162 {0.0} <music21.stream.Measure 4 offset=0.0> 4163 <BLANKLINE> 4164 {0.0} <music21.stream.Measure 4b offset=0.0> 4165 <BLANKLINE> 4166 {0.0} <music21.stream.Measure 5 offset=0.0> 4167 <BLANKLINE> 4168 {0.0} <music21.stream.Measure 5a offset=0.0> 4169 <BLANKLINE> 4170 {0.0} <music21.stream.Measure 6 offset=0.0> 4171 <BLANKLINE> 4172 >>> suffixExcerpt2 = p.measures(3, '4a') 4173 >>> suffixExcerpt2.show('text') 4174 {0.0} <music21.stream.Measure 3 offset=0.0> 4175 <BLANKLINE> 4176 {0.0} <music21.stream.Measure 4 offset=0.0> 4177 <BLANKLINE> 4178 {0.0} <music21.stream.Measure 4a offset=0.0> 4179 <BLANKLINE> 4180 4181 4182 Changed in v.7 -- If `gatherSpanners` is True or GatherSpanners.ALL (default), 4183 then just the spanners pertaining to the requested measure region 4184 are provided, rather than the entire bundle from the source. 4185 4186 >>> from music21.common.enums import GatherSpanners 4187 >>> beachIn = corpus.parse('beach') 4188 >>> beachExcerpt = beachIn.measures(3, 4, gatherSpanners=GatherSpanners.ALL) 4189 >>> len(beachExcerpt.spannerBundle) 4190 8 4191 >>> len(beachIn.spannerBundle) 4192 93 4193 4194 Changed in v.7 -- does not create measures automatically. 4195 4196 OMIT_FROM_DOCS 4197 4198 Ensure that layout.StaffGroup objects are present: 4199 4200 >>> for sp in beachExcerpt.spannerBundle.getByClass('StaffGroup'): 4201 ... print(sp) 4202 <music21.layout.StaffGroup <music21.stream.PartStaff P5-Staff1><... P5-Staff2>> 4203 <music21.layout.StaffGroup <music21.stream.Part Soprano I><...Alto II>> 4204 ''' 4205 4206 def hasMeasureNumberInformation(measureIterator): 4207 ''' 4208 Many people create streams where every number is zero. 4209 This will check for that as quickly as possible. 4210 ''' 4211 uniqueMeasureNumbers = set() 4212 for m in measureIterator: 4213 try: 4214 mNumber = int(m.number) 4215 except ValueError: # pragma: no cover 4216 # should never happen. 4217 raise StreamException(f'found problematic measure for numbering: {m}') 4218 uniqueMeasureNumbers.add(mNumber) 4219 if len(uniqueMeasureNumbers) > 1: 4220 break 4221 if len(uniqueMeasureNumbers) > 1: 4222 return True 4223 elif len(uniqueMeasureNumbers.union({0})) > 1: 4224 return True # is there a number other than zero? (or any number at all 4225 else: 4226 return False 4227 4228 returnObj = self.cloneEmpty(derivationMethod='measures') 4229 srcObj = self 4230 4231 mStreamIter = self.getElementsByClass('Measure') 4232 4233 # FIND THE CORRECT ORIGINAL MEASURE OBJECTS 4234 # for indicesNotNumbers, this is simple... 4235 if indicesNotNumbers: 4236 matches = mStreamIter[numberStart:numberEnd] 4237 else: 4238 hasUniqueMeasureNumbers = hasMeasureNumberInformation(mStreamIter) 4239 4240 # unused... 4241 # originalNumberStart = numberStart 4242 # originalNumberEnd = numberEnd 4243 startSuffix = None 4244 endSuffix = None 4245 if isinstance(numberStart, str): 4246 numberStart, startSuffixTemp = common.getNumFromStr(numberStart) 4247 if startSuffixTemp: 4248 startSuffix = startSuffixTemp 4249 numberStart = int(numberStart) 4250 4251 if isinstance(numberEnd, str): 4252 numberEnd, endSuffixTemp = common.getNumFromStr(numberEnd) 4253 if endSuffixTemp: 4254 endSuffix = endSuffixTemp 4255 numberEnd = int(numberEnd) 4256 4257 if numberEnd is not None: 4258 matchingMeasureNumbers = set(range(numberStart, numberEnd + 1)) 4259 4260 if hasUniqueMeasureNumbers: 4261 matches = [m for m in mStreamIter if m.number in matchingMeasureNumbers] 4262 else: 4263 matches = [m for i, m in enumerate(mStreamIter) 4264 if i + 1 in matchingMeasureNumbers] 4265 else: 4266 if hasUniqueMeasureNumbers: 4267 matches = [m for m in mStreamIter if m.number >= numberStart] 4268 else: 4269 matches = [m for i, m in enumerate(mStreamIter) 4270 if i + 1 >= numberStart] 4271 4272 if startSuffix is not None: 4273 oldMatches = matches 4274 matches = [] 4275 for m in oldMatches: 4276 if m.number != numberStart: 4277 matches.append(m) 4278 elif not m.numberSuffix: 4279 matches.append(m) 4280 elif m.numberSuffix >= startSuffix: 4281 matches.append(m) 4282 4283 if endSuffix is not None: 4284 oldMatches = matches 4285 matches = [] 4286 for m in oldMatches: 4287 if m.number != numberEnd: 4288 matches.append(m) 4289 elif not m.numberSuffix: 4290 matches.append(m) 4291 elif m.numberSuffix <= endSuffix: 4292 matches.append(m) 4293 4294 if not matches: 4295 startMeasure = None 4296 startOffset = 0 # does not matter; could be any number... 4297 else: 4298 startMeasure = matches[0] 4299 startOffset = startMeasure.getOffsetBySite(srcObj) 4300 4301 for className in collect: 4302 # first, see if it is in this Measure 4303 if (startMeasure is None 4304 or startMeasure.recurse().getElementsByClass(className).getElementsByOffset(0)): 4305 continue 4306 4307 # placing missing objects in outer container, not Measure 4308 found = startMeasure.getContextByClass(className) 4309 if found is not None: 4310 if startMeasure is not None: 4311 found.priority = startMeasure.priority - 1 4312 # TODO: This should not change global priority on found, but 4313 # instead priority, like offset, should be a per-site attribute 4314 returnObj.coreInsert(0, found) 4315 4316 for m in matches: 4317 mOffset = m.getOffsetBySite(srcObj) - startOffset 4318 returnObj.coreInsert(mOffset, m) 4319 4320 # used coreInsert 4321 returnObj.coreElementsChanged() 4322 4323 if gatherSpanners: # True, GatherSpanners.ALL, or GatherSpanners.COMPLETE_ONLY 4324 requireAllPresent = (gatherSpanners is GatherSpanners.COMPLETE_ONLY) 4325 returnObj.coreGatherMissingSpanners( 4326 requireAllPresent=requireAllPresent, 4327 constrainingSpannerBundle=self.spannerBundle, 4328 ) 4329 4330 # environLocal.printDebug(['len(returnObj.flatten())', len(returnObj.flatten())]) 4331 return returnObj 4332 4333 def measure(self, 4334 measureNumber, 4335 collect=('Clef', 'TimeSignature', 'Instrument', 'KeySignature'), 4336 indicesNotNumbers=False) -> Optional['music21.stream.Measure']: 4337 ''' 4338 Given a measure number, return a single 4339 :class:`~music21.stream.Measure` object if the Measure number exists, otherwise return None. 4340 4341 This method is distinguished from :meth:`~music21.stream.Stream.measures` 4342 in that this method returns a single Measure object, not a Stream containing 4343 one or more Measure objects. 4344 4345 4346 >>> a = corpus.parse('bach/bwv324.xml') 4347 >>> a.parts[0].measure(3) 4348 <music21.stream.Measure 3 offset=8.0> 4349 4350 See :meth:`~music21.stream.Stream.measures` for an explanation of collect and 4351 indicesNotNumbers 4352 4353 To get the last measure of a piece, use -1 as a measureNumber -- this will turn 4354 on indicesNotNumbers if it is off: 4355 4356 >>> a.parts[0].measure(-1) 4357 <music21.stream.Measure 9 offset=38.0> 4358 4359 Getting a non-existent measure will return None: 4360 4361 >>> print(a.parts[0].measure(99)) 4362 None 4363 4364 OMIT_FROM_DOCS 4365 4366 >>> sf = a.parts[0].flatten() 4367 >>> sf.measure(2) is None 4368 True 4369 ''' 4370 if not isinstance(measureNumber, str) and measureNumber < 0: 4371 indicesNotNumbers = True 4372 4373 startMeasureNumber = measureNumber 4374 endMeasureNumber = measureNumber 4375 if indicesNotNumbers: 4376 endMeasureNumber += 1 4377 if startMeasureNumber == -1: 4378 endMeasureNumber = None 4379 4380 # we must be able to obtain a measure from this (not a flat) 4381 # representation (e.g., this is a Stream or Part, not a Score) 4382 if self.getElementsByClass('Measure'): 4383 # environLocal.printDebug(['got measures from getElementsByClass']) 4384 s = self.measures(startMeasureNumber, 4385 endMeasureNumber, 4386 collect=collect, 4387 indicesNotNumbers=indicesNotNumbers) 4388 measureIter = s.getElementsByClass('Measure') 4389 m: Optional[Measure] 4390 # noinspection PyTypeChecker 4391 m = measureIter.first() 4392 if m is None: # not 'if not m' because m might be an empty measure. 4393 return None 4394 else: 4395 self.coreSelfActiveSite(m) 4396 # ^^ this sets its offset to something meaningful... 4397 return m 4398 else: 4399 # environLocal.printDebug(['got not measures from getElementsByClass']) 4400 return None 4401 4402 def template(self, 4403 *, 4404 fillWithRests=True, 4405 removeClasses=None, 4406 retainVoices=True, 4407 ): 4408 ''' 4409 Return a new Stream based on this one, but without the notes and other elements 4410 but keeping instruments, clefs, keys, etc. 4411 4412 Classes to remove are specified in `removeClasses`. 4413 4414 If this Stream contains measures, return a new Stream 4415 with new Measures populated with the same characteristics of those found in this Stream. 4416 4417 >>> b = corpus.parse('bwv66.6') 4418 >>> sopr = b.parts[0] 4419 >>> soprEmpty = sopr.template() 4420 >>> soprEmpty.show('text') 4421 {0.0} <music21.instrument.Instrument 'P1: Soprano: Instrument 1'> 4422 {0.0} <music21.stream.Measure 0 offset=0.0> 4423 {0.0} <music21.clef.TrebleClef> 4424 {0.0} <music21.key.Key of f# minor> 4425 {0.0} <music21.meter.TimeSignature 4/4> 4426 {0.0} <music21.note.Rest quarter> 4427 {1.0} <music21.stream.Measure 1 offset=1.0> 4428 {0.0} <music21.note.Rest whole> 4429 {5.0} <music21.stream.Measure 2 offset=5.0> 4430 {0.0} <music21.note.Rest whole> 4431 {9.0} <music21.stream.Measure 3 offset=9.0> 4432 {0.0} <music21.layout.SystemLayout> 4433 {0.0} <music21.note.Rest whole> 4434 {13.0} <music21.stream.Measure 4 offset=13.0> 4435 ... 4436 4437 4438 Really make empty with `fillWithRests=False` 4439 4440 >>> alto = b.parts[1] 4441 >>> altoEmpty = alto.template(fillWithRests=False) 4442 >>> altoEmpty.show('text') 4443 {0.0} <music21.instrument.Instrument 'P2: Alto: Instrument 2'> 4444 {0.0} <music21.stream.Measure 0 offset=0.0> 4445 {0.0} <music21.clef.TrebleClef> 4446 {0.0} <music21.key.Key of f# minor> 4447 {0.0} <music21.meter.TimeSignature 4/4> 4448 {1.0} <music21.stream.Measure 1 offset=1.0> 4449 <BLANKLINE> 4450 {5.0} <music21.stream.Measure 2 offset=5.0> 4451 <BLANKLINE> 4452 {9.0} <music21.stream.Measure 3 offset=9.0> 4453 {0.0} <music21.layout.SystemLayout> 4454 ... 4455 4456 4457 `removeClasses` can be a list or set of classes to remove. By default it is 4458 ['GeneralNote', 'Dynamic', 'Expression'] 4459 4460 >>> tenor = b.parts[2] 4461 >>> tenorNoClefsSignatures = tenor.template(fillWithRests=False, 4462 ... removeClasses=['Clef', 'KeySignature', 'TimeSignature', 'Instrument']) 4463 >>> tenorNoClefsSignatures.show('text') 4464 {0.0} <music21.stream.Measure 0 offset=0.0> 4465 {0.0} <music21.note.Note A> 4466 {0.5} <music21.note.Note B> 4467 {1.0} <music21.stream.Measure 1 offset=1.0> 4468 {0.0} <music21.note.Note C#> 4469 {1.0} <music21.note.Note B> 4470 {2.0} <music21.note.Note A> 4471 {3.0} <music21.note.Note B> 4472 {5.0} <music21.stream.Measure 2 offset=5.0> 4473 ... 4474 4475 Setting removeClasses to True removes everything that is not a Stream: 4476 4477 >>> bass = b.parts[3] 4478 >>> bassEmpty = bass.template(fillWithRests=False, removeClasses=True) 4479 >>> bassEmpty.show('text') 4480 {0.0} <music21.stream.Measure 0 offset=0.0> 4481 <BLANKLINE> 4482 {1.0} <music21.stream.Measure 1 offset=1.0> 4483 <BLANKLINE> 4484 {5.0} <music21.stream.Measure 2 offset=5.0> 4485 <BLANKLINE> 4486 {9.0} <music21.stream.Measure 3 offset=9.0> 4487 <BLANKLINE> 4488 {13.0} <music21.stream.Measure 4 offset=13.0> 4489 <BLANKLINE> 4490 ... 4491 4492 On the whole score: 4493 4494 >>> b.template().show('text') 4495 {0.0} <music21.metadata.Metadata object at 0x106151940> 4496 {0.0} <music21.stream.Part Soprano> 4497 {0.0} <music21.instrument.Instrument 'P1: Soprano: Instrument 1'> 4498 {0.0} <music21.stream.Measure 0 offset=0.0> 4499 {0.0} <music21.clef.TrebleClef> 4500 {0.0} <music21.key.Key of f# minor> 4501 {0.0} <music21.meter.TimeSignature 4/4> 4502 {0.0} <music21.note.Rest quarter> 4503 {1.0} <music21.stream.Measure 1 offset=1.0> 4504 {0.0} <music21.note.Rest whole> 4505 ... 4506 {33.0} <music21.stream.Measure 9 offset=33.0> 4507 {0.0} <music21.note.Rest dotted-half> 4508 {3.0} <music21.bar.Barline type=final> 4509 {0.0} <music21.stream.Part Alto> 4510 {0.0} <music21.instrument.Instrument 'P2: Alto: Instrument 2'> 4511 {0.0} <music21.stream.Measure 0 offset=0.0> 4512 {0.0} <music21.clef.TrebleClef> 4513 {0.0} <music21.key.Key of f# minor> 4514 {0.0} <music21.meter.TimeSignature 4/4> 4515 {0.0} <music21.note.Rest quarter> 4516 {1.0} <music21.stream.Measure 1 offset=1.0> 4517 {0.0} <music21.note.Rest whole> 4518 ... 4519 {33.0} <music21.stream.Measure 9 offset=33.0> 4520 {0.0} <music21.note.Rest dotted-half> 4521 {3.0} <music21.bar.Barline type=final> 4522 {0.0} <music21.layout.StaffGroup ...> 4523 4524 4525 If `retainVoices` is False (default True) then Voice streams are treated 4526 differently from all other Streams and are removed. All elements in the 4527 voice are removed even if they do not match the `classList`: 4528 4529 >>> p = stream.Part(id='part0') 4530 >>> m1 = stream.Measure(number=1) 4531 >>> v1 = stream.Voice(id='voice1') 4532 >>> v1.insert(0, note.Note('E', quarterLength=4.0)) 4533 >>> v2 = stream.Voice(id='voice2') 4534 >>> v2.insert(0, note.Note('G', quarterLength=2.0)) 4535 >>> m1.insert(0, v1) 4536 >>> m1.insert(0, v2) 4537 >>> m2 = stream.Measure(number=2) 4538 >>> m2.insert(0, note.Note('D', quarterLength=4.0)) 4539 >>> p.append([m1, m2]) 4540 >>> pt = p.template(retainVoices=False) 4541 >>> pt.show('text') 4542 {0.0} <music21.stream.Measure 1 offset=0.0> 4543 {0.0} <music21.note.Rest whole> 4544 {4.0} <music21.stream.Measure 2 offset=4.0> 4545 {0.0} <music21.note.Rest whole> 4546 >>> pt[0][0].quarterLength 4547 4.0 4548 4549 Developer note -- if you just want a copy of a Score with new 4550 Part and Measure objects, but you don't care that the notes, etc. 4551 inside are the same objects as the original (i.e., you do not 4552 plan to manipulate them, or you want the manipulations to 4553 return to the original objects), using .template() is several 4554 times faster than a deepcopy of the stream (about 4x faster 4555 on bwv66.6) 4556 4557 Changed in v7. -- all arguments are keyword only. 4558 ''' 4559 out = self.cloneEmpty(derivationMethod='template') 4560 if removeClasses is None: 4561 removeClasses = {'GeneralNote', 'Dynamic', 'Expression'} 4562 elif common.isIterable(removeClasses): 4563 removeClasses = set(removeClasses) 4564 4565 restInfo = {'offset': None, 'endTime': None} 4566 4567 def optionalAddRest(): 4568 # six.PY3 nonlocal currentRest would remove the need for restInfo struct 4569 if not fillWithRests: 4570 return 4571 if restInfo['offset'] is None: 4572 return 4573 4574 restQL = restInfo['endTime'] - restInfo['offset'] 4575 restObj = note.Rest(quarterLength=restQL) 4576 out.coreInsert(restInfo['offset'], restObj) 4577 restInfo['offset'] = None 4578 restInfo['endTime'] = None 4579 4580 for el in self: 4581 elOffset = self.elementOffset(el, returnSpecial=True) 4582 if el.isStream and (retainVoices or ('Voice' not in el.classes)): 4583 optionalAddRest() 4584 outEl = el.template(fillWithRests=fillWithRests, 4585 removeClasses=removeClasses, 4586 retainVoices=retainVoices) 4587 if elOffset != OffsetSpecial.AT_END: 4588 out.coreInsert(elOffset, outEl) 4589 else: # pragma: no cover 4590 # should not have streams stored at end. 4591 out.coreStoreAtEnd(outEl) 4592 4593 elif (removeClasses is True 4594 or el.classSet.intersection(removeClasses) 4595 or (not retainVoices and 'Voice' in el.classes)): 4596 # remove this element 4597 if fillWithRests and el.duration.quarterLength: 4598 endTime = elOffset + el.duration.quarterLength 4599 if restInfo['offset'] is None: 4600 restInfo['offset'] = elOffset 4601 restInfo['endTime'] = endTime 4602 elif endTime > restInfo['endTime']: 4603 restInfo['endTime'] = endTime 4604 else: 4605 optionalAddRest() 4606 elNew = copy.deepcopy(el) 4607 if elOffset != OffsetSpecial.AT_END: 4608 out.coreInsert(elOffset, elNew) 4609 else: 4610 out.coreStoreAtEnd(elNew) 4611 4612 # Replace old measures in spanners with new measures 4613 # Example: out is a Part, out.spannerBundle has RepeatBrackets spanning measures 4614 # TODO: when dropping support for Py3.9 add strict=True 4615 for oldM, newM in zip( 4616 self.getElementsByClass('Measure'), 4617 out.getElementsByClass('Measure') 4618 ): 4619 out.spannerBundle.replaceSpannedElement(oldM, newM) 4620 4621 optionalAddRest() 4622 out.coreElementsChanged() 4623 4624 return out 4625 4626 def measureOffsetMap(self, classFilterList=None): 4627 ''' 4628 If this Stream contains Measures, returns an OrderedDict 4629 whose keys are the offsets of the start of each measure 4630 and whose values are a list of references 4631 to the :class:`~music21.stream.Measure` objects that start 4632 at that offset. 4633 4634 Even in normal music there may be more than 4635 one Measure starting at each offset because each 4636 :class:`~music21.stream.Part` might define its own Measure. 4637 However, you are unlikely to encounter such things unless you 4638 run Score.flatten(retainContainers=True). 4639 4640 The offsets are always measured relative to the 4641 calling Stream (self). 4642 4643 You can specify a `classFilterList` argument as a list of classes 4644 to find instead of Measures. But the default will of course 4645 find Measure objects. 4646 4647 Example 1: This Bach chorale is in 4/4 without a pickup, so 4648 as expected, measures are found every 4 offsets, until the 4649 weird recitation in m. 7 which in our edition lasts 10 beats 4650 and thus causes a gap in measureOffsetMap from 24.0 to 34.0. 4651 4652 .. image:: images/streamMeasureOffsetMapBWV324.* 4653 :width: 572 4654 4655 4656 >>> chorale = corpus.parse('bach/bwv324.xml') 4657 >>> alto = chorale.parts['alto'] 4658 >>> altoMeasures = alto.measureOffsetMap() 4659 >>> altoMeasures 4660 OrderedDict([(0.0, [<music21.stream.Measure 1 offset=0.0>]), 4661 (4.0, [<music21.stream.Measure 2 offset=4.0>]), 4662 (8.0, [<music21.stream.Measure 3 offset=8.0>]), 4663 ... 4664 (38.0, [<music21.stream.Measure 9 offset=38.0>])]) 4665 >>> list(altoMeasures.keys()) 4666 [0.0, 4.0, 8.0, 12.0, 16.0, 20.0, 24.0, 34.0, 38.0] 4667 4668 altoMeasures is a dictionary of the measures 4669 that are found in the alto part, so we can get 4670 the measure beginning on offset 4.0 (measure 2) 4671 and display it (though it's the only measure 4672 found at offset 4.0, there might be others as 4673 in example 2, so we need to call altoMeasures[4.0][0] 4674 to get this measure.): 4675 4676 >>> altoMeasures[4.0] 4677 [<music21.stream.Measure 2 offset=4.0>] 4678 >>> altoMeasures[4.0][0].show('text') 4679 {0.0} <music21.note.Note D> 4680 {1.0} <music21.note.Note D#> 4681 {2.0} <music21.note.Note E> 4682 {3.0} <music21.note.Note F#> 4683 4684 Example 2: How to get all the measures from all parts (not the 4685 most efficient way, but it works!): 4686 4687 >>> mom = chorale.measureOffsetMap() 4688 >>> mom 4689 OrderedDict([(0.0, [<music21.stream.Measure 1 offset=0.0>, 4690 <music21.stream.Measure 1 offset=0.0>, 4691 <music21.stream.Measure 1 offset=0.0>, 4692 <music21.stream.Measure 1 offset=0.0>]), 4693 (4.0, [<music21.stream.Measure 2 offset=4.0>, 4694 ...])]) 4695 >>> for measure_obj in mom[8.0]: 4696 ... print(measure_obj, measure_obj.getContextByClass('Part').id) 4697 <music21.stream.Measure 3 offset=8.0> Soprano 4698 <music21.stream.Measure 3 offset=8.0> Alto 4699 <music21.stream.Measure 3 offset=8.0> Tenor 4700 <music21.stream.Measure 3 offset=8.0> Bass 4701 4702 OMIT_FROM_DOCS 4703 4704 see important examples in testMeasureOffsetMap() and 4705 testMeasureOffsetMapPostTie() 4706 ''' 4707 if classFilterList is None: 4708 classFilterList = [Measure] 4709 elif not isinstance(classFilterList, (list, tuple)): 4710 classFilterList = [classFilterList] 4711 4712 # environLocal.printDebug(['calling measure offsetMap()']) 4713 4714 # environLocal.printDebug([classFilterList]) 4715 offsetMap = {} 4716 # first, try to get measures 4717 # this works best of this is a Part or Score 4718 if Measure in classFilterList or 'Measure' in classFilterList: 4719 for m in self.getElementsByClass('Measure'): 4720 offset = self.elementOffset(m) 4721 if offset not in offsetMap: 4722 offsetMap[offset] = [] 4723 # there may be more than one measure at the same offset 4724 offsetMap[offset].append(m) 4725 4726 # try other classes 4727 for className in classFilterList: 4728 if className in [Measure or 'Measure']: # do not redo 4729 continue 4730 for e in self.getElementsByClass(className): 4731 # environLocal.printDebug(['calling measure offsetMap(); e:', e]) 4732 # NOTE: if this is done on Notes, this can take an extremely 4733 # long time to process 4734 # 'reverse' here is a reverse sort, where oldest objects are returned 4735 # first 4736 m = e.getContextByClass('Measure') # , sortByCreationTime='reverse') 4737 if m is None: # pragma: no cover 4738 # hard to think of a time this would happen...But... 4739 continue 4740 # assuming that the offset returns the proper offset context 4741 # this is, the current offset may not be the stream that 4742 # contains this Measure; its current activeSite 4743 offset = m.offset 4744 if offset not in offsetMap: 4745 offsetMap[offset] = [] 4746 if m not in offsetMap[offset]: 4747 offsetMap[offset].append(m) 4748 4749 orderedOffsetMap = collections.OrderedDict(sorted(offsetMap.items(), key=lambda o: o[0])) 4750 return orderedOffsetMap 4751 4752 def _getFinalBarline(self): 4753 # if we have part-like streams, process each part 4754 if self.hasPartLikeStreams(): 4755 post = [] 4756 for p in self.getElementsByClass('Stream'): 4757 post.append(p._getFinalBarline()) 4758 return post # a list of barlines 4759 # core routines for a single Stream 4760 else: 4761 if self.hasMeasures(): 4762 return self.getElementsByClass('Measure').last().rightBarline 4763 elif hasattr(self, 'rightBarline'): 4764 return self.rightBarline 4765 else: 4766 return None 4767 4768 def _setFinalBarline(self, value): 4769 # if we have part-like streams, process each part 4770 if self.hasPartLikeStreams(): 4771 if not common.isListLike(value): 4772 value = [value] 4773 for i, p in enumerate(self.getElementsByClass('Stream')): 4774 # set final barline w/ mod iteration of value list 4775 bl = value[i % len(value)] 4776 # environLocal.printDebug(['enumerating measures', i, p, 'setting barline', bl]) 4777 p._setFinalBarline(bl) 4778 return 4779 4780 # core routines for a single Stream 4781 if self.hasMeasures(): 4782 self.getElementsByClass('Measure').last().rightBarline = value 4783 elif hasattr(self, 'rightBarline'): 4784 self.rightBarline = value # pylint: disable=attribute-defined-outside-init 4785 # do nothing for other streams 4786 4787 finalBarline = property(_getFinalBarline, _setFinalBarline, doc=''' 4788 Get or set the final barline of this Stream's Measures, 4789 if and only if there are Measures defined as elements in this Stream. 4790 This method will not create Measures if none exist. 4791 4792 >>> p = stream.Part() 4793 >>> m1 = stream.Measure() 4794 >>> m1.rightBarline = bar.Barline('double') 4795 >>> p.append(m1) 4796 >>> p.finalBarline 4797 <music21.bar.Barline type=double> 4798 >>> m2 = stream.Measure() 4799 >>> m2.rightBarline = bar.Barline('final') 4800 >>> p.append(m2) 4801 >>> p.finalBarline 4802 <music21.bar.Barline type=final> 4803 4804 This property also works on Scores that contain one or more Parts. 4805 In that case a list of barlines can be used to set the final barline. 4806 4807 >>> s = corpus.parse('bwv66.6') 4808 >>> s.finalBarline 4809 [<music21.bar.Barline type=final>, 4810 <music21.bar.Barline type=final>, 4811 <music21.bar.Barline type=final>, 4812 <music21.bar.Barline type=final>] 4813 4814 >>> s.finalBarline = 'none' 4815 >>> s.finalBarline 4816 [<music21.bar.Barline type=none>, 4817 <music21.bar.Barline type=none>, 4818 <music21.bar.Barline type=none>, 4819 <music21.bar.Barline type=none>] 4820 4821 4822 Getting or setting a final barline on a Measure (or another Stream 4823 with a rightBarline attribute) is the same as getting or setting the rightBarline. 4824 4825 >>> m = stream.Measure() 4826 >>> m.finalBarline is None 4827 True 4828 >>> m.finalBarline = 'final' 4829 >>> m.finalBarline 4830 <music21.bar.Barline type=final> 4831 >>> m.rightBarline 4832 <music21.bar.Barline type=final> 4833 4834 Getting on a generic Stream, Voice, or Opus always returns a barline of None, 4835 and setting on a generic Stream, Voice, or Opus always returns None: 4836 4837 >>> s = stream.Stream() 4838 >>> s.finalBarline is None 4839 True 4840 >>> s.finalBarline = 'final' 4841 >>> s.finalBarline is None 4842 True 4843 4844 Changed in v6.3 -- does not raise an exception if queried or set on a measure-less stream. 4845 Previously raised a StreamException 4846 ''') 4847 4848 @property 4849 def voices(self): 4850 ''' 4851 Return all :class:`~music21.stream.Voices` objects 4852 in an iterator 4853 4854 >>> s = stream.Stream() 4855 >>> s.insert(0, stream.Voice()) 4856 >>> s.insert(0, stream.Voice()) 4857 >>> len(s.voices) 4858 2 4859 ''' 4860 return self.getElementsByClass('Voice') 4861 4862 @property 4863 def spanners(self): 4864 ''' 4865 Return all :class:`~music21.spanner.Spanner` objects 4866 (things such as Slurs, long trills, or anything that 4867 connects many objects) 4868 in an Iterator 4869 4870 >>> s = stream.Stream() 4871 >>> s.insert(0, spanner.Slur()) 4872 >>> s.insert(0, spanner.Slur()) 4873 >>> len(s.spanners) 4874 2 4875 ''' 4876 return self.getElementsByClass('Spanner') 4877 4878 # -------------------------------------------------------------------------- 4879 # handling transposition values and status 4880 4881 @property 4882 def atSoundingPitch(self) -> Union[bool, str]: 4883 ''' 4884 Get or set the atSoundingPitch status, that is whether the 4885 score is at concert pitch or may have transposing instruments 4886 that will not sound as notated. 4887 4888 Valid values are True, False, and 'unknown'. 4889 4890 Note that setting "atSoundingPitch" does not actually transpose the notes. See 4891 `toSoundingPitch()` for that information. 4892 4893 >>> s = stream.Stream() 4894 >>> s.atSoundingPitch = True 4895 >>> s.atSoundingPitch = False 4896 >>> s.atSoundingPitch = 'unknown' 4897 >>> s.atSoundingPitch 4898 'unknown' 4899 >>> s.atSoundingPitch = 'junk' 4900 Traceback (most recent call last): 4901 music21.exceptions21.StreamException: not a valid at sounding pitch value: junk 4902 ''' 4903 return self._atSoundingPitch 4904 4905 @atSoundingPitch.setter 4906 def atSoundingPitch(self, value: Union[bool, str]): 4907 if value in [True, False, 'unknown']: 4908 self._atSoundingPitch = value 4909 else: 4910 raise StreamException(f'not a valid at sounding pitch value: {value}') 4911 4912 def _transposeByInstrument(self, 4913 reverse=False, 4914 inPlace=False, 4915 transposeKeySignature=True): 4916 ''' 4917 Transpose the Stream according to each instrument's transposition. 4918 4919 If reverse is False, the transposition will happen in the direction 4920 opposite of what is specified by the Instrument. for instance, 4921 for changing a concert score to a transposed score or for 4922 extracting parts. 4923 4924 TODO: Fill out -- expose publicly? 4925 ''' 4926 if not inPlace: # make a copy 4927 returnObj = copy.deepcopy(self) 4928 else: 4929 returnObj = self 4930 4931 instrument_stream = returnObj.getInstruments(recurse=True) 4932 instrument_map: Dict['music21.instrument.Instrument', Union[float, Fraction]] = {} 4933 for inst in instrument_stream: 4934 # keep track of original durations of each instrument 4935 instrument_map[inst] = inst.duration.quarterLength 4936 # this loses the expression of duration, but should be fine for instruments. 4937 4938 instrument_stream.duration = returnObj.duration 4939 # inPlace=False here because we are only doing calculations 4940 # toWrittenPitch() shouldn't be inserting extra instruments 4941 instrument_stream = instrument_stream.extendDuration('Instrument', inPlace=False) 4942 4943 # store class filter list for transposition 4944 if transposeKeySignature: 4945 classFilterList = ('Note', 'Chord', 'KeySignature') 4946 else: 4947 classFilterList = ('Note', 'Chord') 4948 4949 for inst in instrument_stream: 4950 if inst.transposition is None: 4951 continue 4952 start = inst.offset 4953 end = start + inst.quarterLength 4954 focus = returnObj.flatten().getElementsByOffset( 4955 start, 4956 end, 4957 includeEndBoundary=False, 4958 mustFinishInSpan=False, 4959 mustBeginInSpan=True).stream() 4960 trans = inst.transposition 4961 if reverse: 4962 trans = trans.reverse() 4963 focus.transpose(trans, inPlace=True, 4964 classFilterList=classFilterList) 4965 4966 # restore original durations 4967 for inst, original_ql in instrument_map.items(): 4968 inst.duration.quarterLength = original_ql 4969 4970 return returnObj 4971 4972 def _treatAtSoundingPitch(self) -> Union[bool, str]: 4973 ''' 4974 `atSoundingPitch` might be True, False, or 'unknown'. Given that 4975 setting the property does not automatically synchronize the corresponding 4976 property on contained or containing streams, any time a method relying on the 4977 value of `atSoundingPitch` such as :meth:`toSoundingPitch` visits a stream, 4978 it will need to resolve 'unknown' values or even possibly conflicting values. 4979 4980 If this stream's `.atSoundingPitch` is 'unknown', this helper method searches 4981 this stream's sites until a True or False 4982 value for `.atSoundingPitch` is found, since it is possible a user only manipulated 4983 the value on the top-level stream. 4984 4985 Then, contained streams are searched to verify that they do not contain 4986 conflicting values (i.e. .atSoundingPitch = True when the container has 4987 .atSoundingPitch = False). Conflicting values are resolved by converting 4988 the inner streams to written or sounding pitch as necessary to match this 4989 stream's value. 4990 ''' 4991 at_sounding = self.atSoundingPitch 4992 if self.atSoundingPitch == 'unknown': 4993 for site in self.sites: 4994 if site.isStream and site.atSoundingPitch != 'unknown': 4995 at_sounding = site.atSoundingPitch 4996 break 4997 else: 4998 raise StreamException('atSoundingPitch is unknown: cannot transpose') 4999 5000 for substream in self.recurse(streamsOnly=True, includeSelf=False): 5001 if substream.atSoundingPitch == 'unknown': 5002 continue 5003 if substream.atSoundingPitch is False and at_sounding is True: 5004 substream.toSoundingPitch(inPlace=True) 5005 elif substream.atSoundingPitch is True and at_sounding is False: 5006 substream.toWrittenPitch(inPlace=True) 5007 5008 return at_sounding 5009 5010 def toSoundingPitch(self, *, inPlace=False): 5011 # noinspection PyShadowingNames 5012 ''' 5013 If not at sounding pitch, transpose all Pitch 5014 elements to sounding pitch. The atSoundingPitch property 5015 is used to determine if transposition is necessary. 5016 5017 Affected by the presence of Instruments and by Ottava spanners 5018 5019 v2.0.10 changes -- inPlace is False; v. 5 returns None if inPlace=True 5020 5021 >>> sc = stream.Score() 5022 >>> p = stream.Part(id='barisax') 5023 >>> p.append(instrument.BaritoneSaxophone()) 5024 >>> m = stream.Measure(number=1) 5025 >>> m.append(note.Note('A4')) 5026 >>> p.append(m) 5027 >>> sc.append(p) 5028 >>> sc.atSoundingPitch = False 5029 5030 >>> scSounding = sc.toSoundingPitch() 5031 >>> scSounding.show('text') 5032 {0.0} <music21.stream.Part barisax> 5033 {0.0} <music21.instrument.BaritoneSaxophone 'Baritone Saxophone'> 5034 {0.0} <music21.stream.Measure 1 offset=0.0> 5035 {0.0} <music21.note.Note C> 5036 5037 >>> scSounding.atSoundingPitch 5038 True 5039 >>> scSounding.parts[0].atSoundingPitch 5040 True 5041 >>> scSounding.recurse().notes[0].nameWithOctave 5042 'C3' 5043 5044 If 'atSoundingPitch' is unknown for this Stream and all of its parent Streams 5045 then will raise a StreamException: 5046 5047 >>> s = stream.Score() 5048 >>> p = stream.Part(id='partEmpty') 5049 >>> s.append(p) 5050 >>> p.toSoundingPitch() 5051 Traceback (most recent call last): 5052 music21.exceptions21.StreamException: atSoundingPitch is unknown: cannot transpose 5053 >>> s.atSoundingPitch = False 5054 >>> sp = p.toSoundingPitch() 5055 >>> sp 5056 <music21.stream.Part partEmpty> 5057 >>> sp.derivation.origin is p 5058 True 5059 ''' 5060 if not inPlace: # make a copy 5061 returnObj = self.coreCopyAsDerivation('toSoundingPitch') 5062 else: 5063 returnObj = self 5064 5065 if returnObj.hasPartLikeStreams() or 'Opus' in returnObj.classSet: 5066 for partLike in returnObj.getElementsByClass('Stream'): 5067 # call on each part 5068 partLike.toSoundingPitch(inPlace=True) 5069 returnObj.atSoundingPitch = True 5070 return returnObj if not inPlace else None 5071 5072 at_sounding = returnObj._treatAtSoundingPitch() 5073 5074 if at_sounding is False: 5075 # transposition defined on instrument goes from written to sounding 5076 returnObj._transposeByInstrument(reverse=False, inPlace=True) 5077 for container in returnObj.recurse(streamsOnly=True, includeSelf=True): 5078 container.atSoundingPitch = True 5079 5080 for ottava in returnObj.recurse().getElementsByClass('Ottava'): 5081 ottava.performTransposition() 5082 5083 if not inPlace: 5084 return returnObj # the Stream or None 5085 5086 def toWrittenPitch(self, *, inPlace=False): 5087 ''' 5088 If not at written pitch, transpose all Pitch elements to 5089 written pitch. The atSoundingPitch property is used to 5090 determine if transposition is necessary. 5091 5092 music21 v.3 changes -- inPlace=False, v. 5 -- returns None if inPlace=True 5093 5094 >>> sc = stream.Score() 5095 >>> p = stream.Part(id='baritoneSax') 5096 >>> p.append(instrument.BaritoneSaxophone()) 5097 >>> m = stream.Measure(number=1) 5098 >>> m.append(note.Note('C3')) 5099 >>> p.append(m) 5100 >>> sc.append(p) 5101 >>> sc.atSoundingPitch = True 5102 >>> scWritten = sc.toWrittenPitch() 5103 >>> scWritten.show('text') 5104 {0.0} <music21.stream.Part baritoneSax> 5105 {0.0} <music21.instrument.BaritoneSaxophone 'Baritone Saxophone'> 5106 {0.0} <music21.stream.Measure 1 offset=0.0> 5107 {0.0} <music21.note.Note A> 5108 >>> scWritten.atSoundingPitch 5109 False 5110 >>> scWritten.parts[0].atSoundingPitch 5111 False 5112 >>> scWritten.recurse().notes[0].nameWithOctave 5113 'A4' 5114 ''' 5115 if not inPlace: # make a copy 5116 returnObj = self.coreCopyAsDerivation('toWrittenPitch') 5117 else: 5118 returnObj = self 5119 5120 if returnObj.hasPartLikeStreams() or 'Opus' in returnObj.classes: 5121 for partLike in returnObj.getElementsByClass('Stream'): 5122 # call on each part 5123 partLike.toWrittenPitch(inPlace=True) 5124 returnObj.atSoundingPitch = False 5125 return returnObj if not inPlace else None 5126 5127 at_sounding = returnObj._treatAtSoundingPitch() 5128 5129 if at_sounding is True: 5130 # need to reverse to go to written 5131 returnObj._transposeByInstrument(reverse=True, inPlace=True) 5132 for container in returnObj.recurse(streamsOnly=True, includeSelf=True): 5133 container.atSoundingPitch = False 5134 5135 for ottava in returnObj.recurse().getElementsByClass('Ottava'): 5136 ottava.undoTransposition() 5137 5138 if not inPlace: 5139 return returnObj 5140 5141 # -------------------------------------------------------------------------- 5142 def getTimeSignatures(self, *, 5143 searchContext=True, 5144 returnDefault=True, 5145 sortByCreationTime=True): 5146 ''' 5147 Collect all :class:`~music21.meter.TimeSignature` objects in this stream. 5148 If no TimeSignature objects are defined, get a default (4/4 or whatever 5149 is defined in the defaults.py file). 5150 5151 >>> a = stream.Stream() 5152 >>> b = meter.TimeSignature('3/4') 5153 >>> a.insert(b) 5154 >>> a.repeatInsert(note.Note('C#'), list(range(10))) 5155 >>> c = a.getTimeSignatures() 5156 >>> len(c) == 1 5157 True 5158 ''' 5159 # even if this is a Measure, the TimeSignature in the Stream will be 5160 # found 5161 # post = self.getElementsByClass(meter.TimeSignature) 5162 post = self.getElementsByClass('TimeSignature').stream() 5163 5164 # search activeSite Streams through contexts 5165 if not post and searchContext: 5166 # returns a single value 5167 post = self.cloneEmpty(derivationMethod='getTimeSignatures') 5168 5169 # sort by time to search the most recent objects 5170 obj = self.getContextByClass('TimeSignature', sortByCreationTime=sortByCreationTime) 5171 # obj = self.previous('TimeSignature') 5172 # environLocal.printDebug(['getTimeSignatures(): searching contexts: results', obj]) 5173 if obj is not None: 5174 post.append(obj) 5175 5176 # if there is no timeSignature, we will look at any stream at offset 0: 5177 if not post: 5178 streamsAtStart = self.getElementsByOffset(0.0).getElementsByClass('Stream') 5179 for s in streamsAtStart: 5180 tss = s.getElementsByOffset(0.0).getElementsByClass('TimeSignature') 5181 for ts in tss: 5182 post.append(ts) 5183 5184 # get a default and/or place default at zero if nothing at zero 5185 if returnDefault: 5186 if not post or post[0].offset > 0: 5187 ts = meter.TimeSignature('%s/%s' % (defaults.meterNumerator, 5188 defaults.meterDenominatorBeatType)) 5189 post.insert(0, ts) 5190 # environLocal.printDebug(['getTimeSignatures(): final result:', post[0]]) 5191 return post 5192 5193 def getInstruments(self, 5194 *, 5195 searchActiveSite=True, 5196 returnDefault=True, 5197 recurse=False) -> 'music21.stream.Stream': 5198 ''' 5199 Search this stream or activeSite streams for 5200 :class:`~music21.instrument.Instrument` objects, and return a new stream 5201 containing them. Otherwise, return a Stream containing a single default `Instrument`. 5202 5203 >>> p = stream.Part() 5204 >>> m = stream.Measure([note.Note()]) 5205 >>> p.insert(0, m) 5206 >>> instrumentStream = p.getInstruments(returnDefault=True) 5207 >>> defaultInst = instrumentStream.first() 5208 >>> defaultInst 5209 <music21.instrument.Instrument ': '> 5210 5211 Insert the default instrument into the part: 5212 5213 >>> p.insert(0, defaultInst) 5214 5215 Searching the measure will find it only if the measure's active site is searched: 5216 5217 >>> search1 = p.measure(1).getInstruments(searchActiveSite=False, returnDefault=False) 5218 >>> search1.first() is None 5219 True 5220 >>> search2 = p.measure(1).getInstruments(searchActiveSite=True, returnDefault=False) 5221 >>> search2.first() is defaultInst 5222 True 5223 ''' 5224 instObj = None 5225 if recurse: 5226 sIter = self.recurse() 5227 else: 5228 sIter = self.iter() 5229 5230 post = sIter.getElementsByClass('Instrument').stream() 5231 if post: 5232 # environLocal.printDebug(['found local instrument:', post[0]]) 5233 instObj = post.first() 5234 else: 5235 if searchActiveSite: 5236 # if isinstance(self.activeSite, Stream) and self.activeSite != self: 5237 if (self.activeSite is not None 5238 and self.activeSite.isStream 5239 and self.activeSite is not self): 5240 # environLocal.printDebug(['searching activeSite Stream', 5241 # self, self.activeSite]) 5242 instObj = self.activeSite.getInstrument( 5243 searchActiveSite=searchActiveSite, 5244 returnDefault=False) 5245 if instObj is not None: 5246 post.insert(0, instObj) 5247 5248 # if still not defined, get default 5249 if returnDefault and instObj is None: 5250 from music21.instrument import Instrument 5251 instObj = Instrument() 5252 # instObj.partId = defaults.partId # give a default id 5253 # TODO: should this be changed to None? MSC 2015-12 5254 instObj.partName = defaults.partName # give a default id 5255 post.insert(0, instObj) 5256 5257 # returns a Stream 5258 return post 5259 5260 def getInstrument(self, 5261 *, 5262 searchActiveSite=True, 5263 returnDefault=True, 5264 recurse=False) -> Optional['music21.instrument.Instrument']: 5265 ''' 5266 Return the first Instrument found in this Stream, or None. 5267 5268 >>> s = stream.Score() 5269 >>> p1 = stream.Part() 5270 >>> p1.insert(instrument.Violin()) 5271 >>> m1p1 = stream.Measure() 5272 >>> m1p1.append(note.Note('g')) 5273 >>> p1.append(m1p1) 5274 5275 >>> p2 = stream.Part() 5276 >>> p2.insert(instrument.Viola()) 5277 >>> m1p2 = stream.Measure() 5278 >>> m1p2.append(note.Note('f#')) 5279 >>> p2.append(m1p2) 5280 5281 >>> s.insert(0, p1) 5282 >>> s.insert(0, p2) 5283 >>> p1.getInstrument(returnDefault=False).instrumentName 5284 'Violin' 5285 >>> p2.getInstrument(returnDefault=False).instrumentName 5286 'Viola' 5287 5288 Changed in v.7 -- added `recurse` (default False) 5289 ''' 5290 post = self.getInstruments(searchActiveSite=searchActiveSite, 5291 returnDefault=returnDefault, 5292 recurse=recurse) 5293 return post.first() 5294 5295 @common.deprecated('v7', 'v8', 'use getElementsByClass() or getContextByClass() or bestClef()') 5296 def getClefs(self, searchActiveSite=False, searchContext=True, 5297 returnDefault=True): # pragma: no cover 5298 ''' 5299 DEPRECATED in v7. 5300 5301 Collect all :class:`~music21.clef.Clef` objects in 5302 this Stream in a list. Optionally search the 5303 activeSite Stream and/or contexts. 5304 5305 If no Clef objects are defined, get a default 5306 using :meth:`~music21.clef.bestClef` 5307 5308 5309 >>> a = stream.Stream() 5310 >>> b = clef.AltoClef() 5311 >>> a.insert(0, b) 5312 >>> a.repeatInsert(note.Note('C#'), list(range(10))) 5313 >>> #_DOCS_SHOW c = a.getClefs() 5314 >>> #_DOCS_SHOW len(c) == 1 5315 True 5316 ''' 5317 # TODO: activeSite searching is not yet implemented 5318 # this may not be useful unless a stream is flat 5319 post = list(self.getElementsByClass('Clef')) 5320 5321 # environLocal.printDebug(['getClefs(); count of local', len(post), post]) 5322 if not post and searchActiveSite and self.activeSite is not None: 5323 # environLocal.printDebug(['getClefs(): search activeSite']) 5324 post = list(self.activeSite.getElementsByClass('Clef')) 5325 5326 if not post and searchContext: 5327 # returns a single element match 5328 # post = self.__class__() 5329 obj = self.getContextByClass('Clef') 5330 if obj is not None: 5331 post.append(obj) 5332 5333 # get a default and/or place default at zero if nothing at zero 5334 if returnDefault and (not post or post[0].offset > 0): 5335 # environLocal.printDebug(['getClefs(): using bestClef()']) 5336 post.insert(0, clef.bestClef(self)) 5337 return post 5338 5339 @common.deprecated('v7', 'v8', 'use getElementsByClass() or getContextByClass()') 5340 def getKeySignatures(self, searchActiveSite=True, searchContext=True): # pragma: no cover 5341 ''' 5342 Collect all :class:`~music21.key.KeySignature` objects in this 5343 Stream in a new Stream. Optionally search the activeSite 5344 stream and/or contexts. 5345 5346 If no KeySignature objects are defined, returns an empty Stream 5347 5348 DEPRECATED in v7. 5349 5350 >>> a = stream.Stream() 5351 >>> b = key.KeySignature(3) 5352 >>> a.insert(0, b) 5353 >>> a.repeatInsert(note.Note('C#'), list(range(10))) 5354 >>> #_DOCS_SHOW c = a.getKeySignatures() 5355 >>> #_DOCS_SHOW len(c) == 1 5356 True 5357 ''' 5358 # TODO: activeSite searching is not yet implemented 5359 # this may not be useful unless a stream is flat 5360 post = self.getElementsByClass('KeySignature') 5361 if not post and searchContext: 5362 # returns a single value 5363 post = self.cloneEmpty(derivationMethod='getKeySignatures') 5364 obj = self.getContextByClass(key.KeySignature) 5365 if obj is not None: 5366 post.append(obj) 5367 5368 # do nothing if empty 5369 if not post or post[0].offset > 0: 5370 pass 5371 return post 5372 5373 def invertDiatonic(self, inversionNote=note.Note('C4'), *, inPlace=False): 5374 ''' 5375 inverts a stream diatonically around the given note (by default, middle C) 5376 5377 For pieces where the key signature 5378 does not change throughout the piece it is MUCH faster than 5379 for pieces where the key signature changes. 5380 5381 Here in this test, we put Ciconia's Quod Jactatur (a single voice 5382 piece that should have a canon solution: see trecento.quodJactatur) 5383 into 3 flats (instead of its original 1 flat) in measure 1, but 5384 into 5 sharps in measure 2 and then invert around F4, creating 5385 a new piece. 5386 5387 Changed in v. 5 -- inPlace is False by default. 5388 5389 5390 >>> qj = corpus.parse('ciconia/quod_jactatur').parts[0].measures(1, 2) 5391 >>> qj.id = 'measureExcerpt' 5392 5393 >>> qj.show('text') 5394 {0.0} <music21.instrument.Piano 'P1: MusicXML Part: Grand Piano'> 5395 {0.0} <music21.stream.Measure 1 offset=0.0> 5396 {0.0} <music21.layout.SystemLayout> 5397 {0.0} <music21.clef.Treble8vbClef> 5398 {0.0} <music21.key.Key of F major> 5399 {0.0} <music21.meter.TimeSignature 2/4> 5400 {0.0} <music21.note.Note C> 5401 {1.5} <music21.note.Note D> 5402 {2.0} <music21.stream.Measure 2 offset=2.0> 5403 {0.0} <music21.note.Note E> 5404 {0.5} <music21.note.Note D> 5405 {1.0} <music21.note.Note C> 5406 {1.5} <music21.note.Note D> 5407 5408 >>> qjFlat = qj.flatten() 5409 >>> k1 = qjFlat.getElementsByClass(key.KeySignature).first() 5410 >>> k3flats = key.KeySignature(-3) 5411 >>> qjFlat.replace(k1, k3flats, allDerived=True) 5412 >>> qj.getElementsByClass(stream.Measure)[1].insert(0, key.KeySignature(5)) 5413 5414 >>> qj2 = qj.invertDiatonic(note.Note('F4'), inPlace=False) 5415 >>> qj2.show('text') 5416 {0.0} <music21.instrument.Piano 'P1: MusicXML Part: Grand Piano'> 5417 {0.0} <music21.stream.Measure 1 offset=0.0> 5418 {0.0} <music21.layout.SystemLayout> 5419 {0.0} <music21.clef.Treble8vbClef> 5420 {0.0} <music21.key.KeySignature of 3 flats> 5421 {0.0} <music21.meter.TimeSignature 2/4> 5422 {0.0} <music21.note.Note B-> 5423 {1.5} <music21.note.Note A-> 5424 {2.0} <music21.stream.Measure 2 offset=2.0> 5425 {0.0} <music21.key.KeySignature of 5 sharps> 5426 {0.0} <music21.note.Note G#> 5427 {0.5} <music21.note.Note A#> 5428 {1.0} <music21.note.Note B> 5429 {1.5} <music21.note.Note A#> 5430 ''' 5431 if inPlace: 5432 returnStream = self 5433 else: 5434 returnStream = self.coreCopyAsDerivation('invertDiatonic') 5435 5436 keySigSearch = returnStream.recurse().getElementsByClass(key.KeySignature) 5437 5438 quickSearch = True 5439 if not keySigSearch: 5440 ourKey = key.Key('C') 5441 elif len(keySigSearch) == 1: 5442 ourKey = keySigSearch[0] 5443 else: 5444 ourKey = None # for might be undefined warning 5445 quickSearch = False 5446 5447 inversionDNN = inversionNote.pitch.diatonicNoteNum 5448 for n in returnStream.recurse().getElementsByClass('NotRest'): 5449 n.pitch.diatonicNoteNum = (2 * inversionDNN) - n.pitch.diatonicNoteNum 5450 if quickSearch: # use previously found 5451 n.pitch.accidental = ourKey.accidentalByStep(n.pitch.step) 5452 else: # use context search 5453 ksActive = n.getContextByClass(key.KeySignature) 5454 n.pitch.accidental = ksActive.accidentalByStep(n.pitch.step) 5455 5456 if n.pitch.accidental is not None: 5457 n.pitch.accidental.displayStatus = None 5458 5459 if not inPlace: 5460 return returnStream 5461 5462 # ------------------------------------------------------------------------- 5463 # offset manipulation 5464 5465 def shiftElements(self, offset, startOffset=None, endOffset=None, classFilterList=None): 5466 ''' 5467 Add the given offset value to every offset of 5468 the objects found in the Stream. Objects that are 5469 specifically placed at the end of the Stream via 5470 .storeAtEnd() (such as right barlines) are 5471 not affected. 5472 5473 If startOffset is given then all elements before 5474 that offset will be shifted. If endOffset is given 5475 then all elements at or after this offset will be 5476 shifted 5477 5478 >>> a = stream.Stream() 5479 >>> a.repeatInsert(note.Note('C'), list(range(10))) 5480 >>> a.shiftElements(30) 5481 >>> a.lowestOffset 5482 30.0 5483 >>> a.shiftElements(-10) 5484 >>> a.lowestOffset 5485 20.0 5486 5487 Use shiftElements to move elements after a change in 5488 duration: 5489 5490 >>> st2 = stream.Stream() 5491 >>> st2.insert(0, note.Note('D4', type='whole')) 5492 >>> st2.repeatInsert(note.Note('C4'), list(range(4, 8))) 5493 >>> st2.show('text') 5494 {0.0} <music21.note.Note D> 5495 {4.0} <music21.note.Note C> 5496 {5.0} <music21.note.Note C> 5497 {6.0} <music21.note.Note C> 5498 {7.0} <music21.note.Note C> 5499 5500 Now make the first note a dotted whole note and shift the rest by two quarters... 5501 5502 >>> firstNote = st2[0] 5503 >>> firstNoteOldQL = firstNote.quarterLength 5504 >>> firstNote.duration.dots = 1 5505 >>> firstNoteNewQL = firstNote.quarterLength 5506 >>> shiftAmount = firstNoteNewQL - firstNoteOldQL 5507 >>> shiftAmount 5508 2.0 5509 5510 >>> st2.shiftElements(shiftAmount, startOffset=4.0) 5511 >>> st2.show('text') 5512 {0.0} <music21.note.Note D> 5513 {6.0} <music21.note.Note C> 5514 {7.0} <music21.note.Note C> 5515 {8.0} <music21.note.Note C> 5516 {9.0} <music21.note.Note C> 5517 5518 A class filter list may be given. It must be an iterable. 5519 5520 >>> st2.insert(7.5, key.Key('F')) 5521 >>> st2.shiftElements(2/3, startOffset=6.0, endOffset=8.0, 5522 ... classFilterList=[note.Note]) 5523 >>> st2.show('text') 5524 {0.0} <music21.note.Note D> 5525 {6.6667} <music21.note.Note C> 5526 {7.5} <music21.key.Key of F major> 5527 {7.6667} <music21.note.Note C> 5528 {8.0} <music21.note.Note C> 5529 {9.0} <music21.note.Note C> 5530 ''' 5531 # only want _elements, do not want _endElements 5532 for e in self._elements: 5533 5534 if startOffset is not None and self.elementOffset(e) < startOffset: 5535 continue 5536 if endOffset is not None and self.elementOffset(e) >= endOffset: 5537 continue 5538 if classFilterList is not None and e.classSet.isdisjoint(classFilterList): 5539 continue 5540 5541 self.coreSetElementOffset(e, opFrac(self.elementOffset(e) + offset)) 5542 5543 self.coreElementsChanged() 5544 5545 def transferOffsetToElements(self): 5546 ''' 5547 Transfer the offset of this stream to all 5548 internal elements; then set 5549 the offset of this stream to zero. 5550 5551 >>> a = stream.Stream() 5552 >>> a.repeatInsert(note.Note('C'), list(range(10))) 5553 >>> a.offset = 30 5554 >>> a.transferOffsetToElements() 5555 >>> a.lowestOffset 5556 30.0 5557 >>> a.offset 5558 0.0 5559 >>> a.offset = 20 5560 >>> a.transferOffsetToElements() 5561 >>> a.lowestOffset 5562 50.0 5563 ''' 5564 self.shiftElements(self.offset) 5565 self.offset = 0.0 5566 5567 # ------------------------------------------------------------------------- 5568 # utilities for creating large numbers of elements 5569 5570 def repeatAppend(self, item, numberOfTimes): 5571 ''' 5572 Given an object and a number, run append that many times on 5573 a deepcopy of the object. 5574 numberOfTimes should of course be a positive integer. 5575 5576 >>> a = stream.Stream() 5577 >>> n = note.Note('D--') 5578 >>> n.duration.type = 'whole' 5579 >>> a.repeatAppend(n, 10) 5580 >>> a.show('text') 5581 {0.0} <music21.note.Note D--> 5582 {4.0} <music21.note.Note D--> 5583 {8.0} <music21.note.Note D--> 5584 {12.0} <music21.note.Note D--> 5585 ... 5586 {36.0} <music21.note.Note D--> 5587 5588 >>> a.duration.quarterLength 5589 40.0 5590 >>> a[9].offset 5591 36.0 5592 ''' 5593 try: 5594 unused = item.isStream 5595 element = item 5596 # if not isinstance(item, music21.Music21Object): 5597 except AttributeError: 5598 # element = music21.ElementWrapper(item) 5599 raise StreamException('to put a non Music21Object in a stream, ' 5600 + 'create a music21.ElementWrapper for the item') 5601 # # if not an element, embed 5602 # if not isinstance(item, music21.Music21Object): 5603 # element = music21.ElementWrapper(item) 5604 # else: 5605 # element = item 5606 5607 for unused_i in range(numberOfTimes): 5608 self.append(copy.deepcopy(element)) 5609 5610 def repeatInsert(self, item, offsets): 5611 ''' 5612 Given an object, create a deep copy of each object at 5613 each positions specified by 5614 the offset list: 5615 5616 >>> a = stream.Stream() 5617 >>> n = note.Note('G-') 5618 >>> n.quarterLength = 1 5619 5620 >>> a.repeatInsert(n, [0, 2, 3, 4, 4.5, 5, 6, 7, 8, 9, 10, 11, 12]) 5621 >>> len(a) 5622 13 5623 >>> a[10].offset 5624 10.0 5625 ''' 5626 if not common.isIterable(offsets): 5627 raise StreamException(f'must provide an iterable of offsets, not {offsets}') 5628 5629 try: 5630 unused = item.isStream 5631 element = item 5632 # if not isinstance(item, music21.Music21Object): 5633 except AttributeError: 5634 # if not an element, embed 5635 # element = music21.ElementWrapper(item) 5636 raise StreamException('to put a non Music21Object in a stream, ' 5637 + 'create a music21.ElementWrapper for the item') 5638 # if not isinstance(item, music21.Music21Object): 5639 # # if not an element, embed 5640 # element = music21.ElementWrapper(item) 5641 # else: 5642 # element = item 5643 5644 for offset in offsets: 5645 elementCopy = copy.deepcopy(element) 5646 self.coreInsert(offset, elementCopy) 5647 self.coreElementsChanged() 5648 5649 def extractContext(self, searchElement, before=4.0, after=4.0, 5650 maxBefore=None, maxAfter=None): 5651 ''' 5652 Extracts elements around the given element within (before) 5653 quarter notes and (after) quarter notes (default 4), and 5654 returns a new Stream. 5655 5656 >>> qn = note.Note(type='quarter') 5657 >>> qtrStream = stream.Stream() 5658 >>> qtrStream.repeatInsert(qn, [0, 1, 2, 3, 4, 5]) 5659 >>> hn = note.Note(type='half') 5660 >>> hn.name = 'B-' 5661 >>> qtrStream.append(hn) 5662 >>> qtrStream.repeatInsert(qn, [8, 9, 10, 11]) 5663 >>> hnStream = qtrStream.extractContext(hn, 1.0, 1.0) 5664 >>> hnStream.show('text') 5665 {5.0} <music21.note.Note C> 5666 {6.0} <music21.note.Note B-> 5667 {8.0} <music21.note.Note C> 5668 5669 Changed in v7. -- forceOutputClass removed. 5670 5671 OMIT_FROM_DOCS 5672 TODO: maxBefore -- maximum number of elements to return before; etc. 5673 TODO: use .template to get new Stream 5674 5675 NOTE: RENAME: this probably should be renamed, as we use Context in a special way. 5676 Perhaps better is extractNeighbors? 5677 5678 ''' 5679 display = self.cloneEmpty('extractContext') 5680 5681 found = None 5682 foundOffset = 0 5683 foundEnd = 0 5684 elements = self.elements 5685 for i in range(len(elements)): 5686 b = elements[i] 5687 if b.id == searchElement.id: 5688 found = i 5689 foundOffset = self.elementOffset(elements[i]) 5690 foundEnd = foundOffset + elements[i].duration.quarterLength 5691 elif b is searchElement: 5692 found = i 5693 foundOffset = self.elementOffset(elements[i]) 5694 foundEnd = foundOffset + elements[i].duration.quarterLength 5695 if found is None: 5696 raise StreamException('Could not find the element in the stream') 5697 5698 # handle _elements and _endElements independently 5699 for e in self._elements: 5700 o = self.elementOffset(e) 5701 if foundOffset - before <= o < foundEnd + after: 5702 display.coreInsert(o, e) 5703 5704 for e in self._endElements: 5705 o = self.elementOffset(e) 5706 if foundOffset - before <= o < foundEnd + after: 5707 display.coreStoreAtEnd(e) 5708 display.coreElementsChanged() 5709 return display 5710 5711 # -------------------------------------------------------------------------- 5712 # transformations of self that return a new Stream 5713 5714 def _uniqueOffsetsAndEndTimes(self, offsetsOnly=False, endTimesOnly=False): 5715 ''' 5716 Get a list of all offsets and endtimes 5717 of notes and rests in this stream. 5718 5719 Helper method for makeChords and Chordify 5720 run on .flatten().notesAndRests 5721 5722 5723 >>> s = stream.Score() 5724 >>> p1 = stream.Part() 5725 >>> p1.insert(4, note.Note('C#')) 5726 >>> p1.insert(5.3, note.Rest()) 5727 >>> p2 = stream.Part() 5728 >>> p2.insert(2.12, note.Note('D-', type='half')) 5729 >>> p2.insert(5.5, note.Rest()) 5730 >>> s.insert(0, p1) 5731 >>> s.insert(0, p2) 5732 5733 We will get a mix of float and fractions.Fraction() objects 5734 5735 >>> [str(o) for o in s.flatten()._uniqueOffsetsAndEndTimes()] 5736 ['53/25', '4.0', '103/25', '5.0', '53/10', '5.5', '63/10', '6.5'] 5737 5738 Limit what is returned: 5739 5740 >>> [str(o) for o in s.flatten()._uniqueOffsetsAndEndTimes(offsetsOnly=True)] 5741 ['53/25', '4.0', '53/10', '5.5'] 5742 >>> [str(o) for o in s.flatten()._uniqueOffsetsAndEndTimes(endTimesOnly=True)] 5743 ['103/25', '5.0', '63/10', '6.5'] 5744 5745 And this is useless... :-) 5746 5747 >>> s.flatten()._uniqueOffsetsAndEndTimes(offsetsOnly=True, endTimesOnly=True) 5748 [] 5749 5750 ''' 5751 offsetDictValues = self._offsetDict.values() 5752 if endTimesOnly: 5753 offsets = set() 5754 else: 5755 offsets = {opFrac(v[0]) for v in offsetDictValues} 5756 5757 if offsetsOnly: 5758 endTimes = set() 5759 else: 5760 endTimes = {opFrac(v[0] + v[1].duration.quarterLength) 5761 for v in offsetDictValues} 5762 return sorted(offsets.union(endTimes)) 5763 5764 @common.deprecated('v7', 'v8', 'use chordify() instead') 5765 def makeChords(self, 5766 minimumWindowSize=0.125, 5767 includePostWindow=True, 5768 removeRedundantPitches=True, 5769 useExactOffsets=False, 5770 gatherArticulations=True, 5771 gatherExpressions=True, 5772 inPlace=False, 5773 transferGroupsToPitches=False, 5774 makeRests=True): # pragma: no cover 5775 ''' 5776 DEPRECATED in v7. Use Chordify instead! 5777 5778 Gathers simultaneously sounding :class:`~music21.note.Note` objects 5779 into :class:`~music21.chord.Chord` objects, each of which 5780 contains all the pitches sounding together. 5781 5782 If useExactOffsets is True (default=False), then do an exact 5783 makeChords using the offsets in the piece. 5784 If this parameter is set, then minimumWindowSize is ignored. 5785 5786 This first example puts a part with three quarter notes (C4, D4, E4) 5787 together with a part consisting of a half note (C#5) and a 5788 quarter note (E#5) to make two Chords, the first containing the 5789 three :class:`~music21.pitch.Pitch` objects sounding at the 5790 beginning, the second consisting of the two Pitches sounding 5791 on offset 2.0 (beat 3): 5792 5793 >>> p1 = stream.Part() 5794 >>> p1.append([note.Note('C4', type='quarter'), 5795 ... note.Note('D4', type='quarter'), 5796 ... note.Note('E4', type='quarter'), 5797 ... note.Note('B2', type='quarter')]) 5798 >>> p2 = stream.Part() 5799 >>> p2.append([note.Note('C#5', type='half'), 5800 ... note.Note('E#5', type='quarter'), 5801 ... chord.Chord(['E4', 'G5', 'C#7'])]) 5802 >>> sc1 = stream.Score() 5803 >>> sc1.insert(0, p1) 5804 >>> sc1.insert(0, p2) 5805 >>> #_DOCS_SHOW scChords = sc1.flatten().makeChords() 5806 >>> #_DOCS_SHOW scChords.show('text') 5807 {0.0} <music21.chord.Chord C4 C#5 D4> 5808 {2.0} <music21.chord.Chord E4 E#5> 5809 {3.0} <music21.chord.Chord B2 E4 G5 C#7> 5810 5811 The gathering of elements, starting from offset 0.0, uses 5812 the `minimumWindowSize`, in quarter lengths, to collect 5813 all Notes that start between 0.0 and the minimum window 5814 size (this permits overlaps within a minimum tolerance). 5815 5816 After collection, the maximum duration of collected elements 5817 is found; this duration is then used to set the new 5818 starting offset. A possible gap then results between the end 5819 of the window and offset specified by the maximum duration; 5820 these additional notes are gathered in a second pass if 5821 `includePostWindow` is True. 5822 5823 The new start offset is shifted to the larger of either 5824 the minimum window or the maximum duration found in the 5825 collected group. The process is repeated until all offsets 5826 are covered. 5827 5828 Each collection of Notes is formed into a Chord. The Chord 5829 is given the longest duration of all constituents, and is 5830 inserted at the start offset of the window from which it 5831 was gathered. 5832 5833 Chords can gather both articulations and expressions from found Notes 5834 using `gatherArticulations` and `gatherExpressions`. 5835 5836 If `transferGroupsToPitches` is True, and group defined on the source 5837 elements Groups object will be transferred to the Pitch 5838 objects contained in the resulting Chord. 5839 5840 The resulting Stream, if not in-place, can also gather 5841 additional objects by placing class names in the `collect` list. 5842 By default, TimeSignature and KeySignature objects are collected. 5843 ''' 5844 if not inPlace: # make a copy 5845 # since we do not return Scores, this probably should always be 5846 # a Stream 5847 # returnObj = Stream() 5848 # returnObj = self.__class__() # for output 5849 returnObj = self.coreCopyAsDerivation('makeChords') 5850 else: 5851 returnObj = self 5852 5853 def dealWithSubNotes(chordLength, noteList): 5854 # environLocal.printDebug(['creating chord from noteList', 5855 # noteList, 'inPlace', inPlace]) 5856 c = chord.Chord() 5857 c.duration.quarterLength = chordLength 5858 # these are references, not copies, for now 5859 tempComponents = [] 5860 for n in noteList: 5861 if n.isChord: 5862 cSub = list(n) 5863 else: 5864 cSub = [n] 5865 5866 if transferGroupsToPitches and n.groups: 5867 for comp in cSub: 5868 for g in n.groups: 5869 comp.pitch.groups.append(g) 5870 5871 for comp in cSub: 5872 tempComponents.append(comp) 5873 5874 c.pitches = [comp.pitch for comp in tempComponents] 5875 for comp in tempComponents: 5876 if comp.tie is not None: 5877 c.setTie(comp.tie.type, comp.pitch) 5878 5879 if gatherArticulations: 5880 for n in noteList: 5881 c.articulations += n.articulations 5882 if gatherExpressions: 5883 for n in noteList: 5884 c.expressions += n.expressions 5885 # always remove all the previous elements 5886 for n in noteList: 5887 returnObj.remove(n) 5888 # remove all rests found in source 5889 for r in list(returnObj.getElementsByClass('Rest')): 5890 returnObj.remove(r) 5891 5892 if removeRedundantPitches: 5893 removedPitches = c.removeRedundantPitches(inPlace=True) 5894 5895 if transferGroupsToPitches: 5896 for rem_p, cn in itertools.product(removedPitches, c): 5897 if cn.pitch.nameWithOctave == rem_p.nameWithOctave: 5898 # print(cn.pitch, rem_p) 5899 # print(cn.pitch.groups, rem_p.groups) 5900 cn.pitch.groups.extend(rem_p.groups) 5901 return c 5902 # environLocal.printDebug(['makeChords():', 5903 # 'transferGroupsToPitches', transferGroupsToPitches]) 5904 5905 if returnObj.hasMeasures(): 5906 # call on component measures 5907 for m in returnObj.getElementsByClass('Measure'): 5908 # offset values are not relative to measure; need to 5909 # shift by each measure's offset 5910 m.makeChords( 5911 minimumWindowSize=minimumWindowSize, 5912 includePostWindow=includePostWindow, 5913 removeRedundantPitches=removeRedundantPitches, 5914 gatherArticulations=gatherArticulations, 5915 gatherExpressions=gatherExpressions, 5916 transferGroupsToPitches=transferGroupsToPitches, 5917 inPlace=True, 5918 makeRests=makeRests 5919 ) 5920 return returnObj # exit 5921 5922 if returnObj.hasPartLikeStreams(): 5923 # must get Streams, not Parts here 5924 for p in returnObj.getElementsByClass('Stream'): 5925 p.makeChords( 5926 minimumWindowSize=minimumWindowSize, 5927 includePostWindow=includePostWindow, 5928 removeRedundantPitches=removeRedundantPitches, 5929 gatherArticulations=gatherArticulations, 5930 gatherExpressions=gatherExpressions, 5931 transferGroupsToPitches=transferGroupsToPitches, 5932 inPlace=True, 5933 makeRests=makeRests 5934 ) 5935 return returnObj # exit 5936 5937 # TODO: gather lyrics as an option 5938 # define classes that are gathered; assume they have pitches 5939 # matchClasses = ['Note', 'Chord', 'Rest'] 5940 matchClasses = ['Note', 'Chord'] 5941 o = 0.0 # start at zero 5942 oTerminate = returnObj.highestOffset 5943 5944 # get temporary boundaries for making rests 5945 preHighestTime = returnObj.highestTime 5946 preLowestOffset = returnObj.lowestOffset 5947 # environLocal.printDebug(['got preLowest, preHighest', preLowestOffset, preHighestTime]) 5948 if useExactOffsets is False: 5949 while True: # TODO: Remove while True always... 5950 # get all notes within the start and the min window size 5951 oStart = o 5952 oEnd = oStart + minimumWindowSize 5953 subNotes = list(returnObj.getElementsByOffset( 5954 oStart, 5955 oEnd, 5956 includeEndBoundary=False, 5957 mustFinishInSpan=False, 5958 mustBeginInSpan=True 5959 ).getElementsByClass(matchClasses)) # get once for speed 5960 # environLocal.printDebug(['subNotes', subNotes]) 5961 qlMax: Optional[float] = None 5962 # get the max duration found from within the window 5963 if subNotes: 5964 # get largest duration, use for duration of Chord, next span 5965 qlMax = max([n.quarterLength for n in subNotes]) 5966 5967 # if the max duration found in the window is greater than the min 5968 # window size, it is possible that there are notes that will not 5969 # be gathered; those starting at the end of this window but before 5970 # the max found duration (as that will become the start of the next 5971 # window 5972 # so: if ql > min window, gather notes between 5973 # oStart + minimumWindowSize and oStart + qlMax 5974 if (includePostWindow and qlMax is not None 5975 and qlMax > minimumWindowSize): 5976 subAdd = list(returnObj.getElementsByOffset( 5977 oStart + minimumWindowSize, 5978 oStart + qlMax, 5979 includeEndBoundary=False, 5980 mustFinishInSpan=False, 5981 mustBeginInSpan=True 5982 ).getElementsByClass(matchClasses)) 5983 # concatenate any additional notes found 5984 subNotes += subAdd 5985 5986 # make subNotes into a chord 5987 if subNotes: 5988 cOut = dealWithSubNotes(qlMax, subNotes) 5989 # insert chord at start location 5990 returnObj.coreInsert(o, cOut) 5991 # environLocal.printDebug(['len of returnObj', len(returnObj)]) 5992 # shift offset to qlMax or minimumWindowSize 5993 if qlMax is not None and qlMax >= minimumWindowSize: 5994 # update start offset to what was old boundary 5995 # note: this assumes that the start of the longest duration 5996 # was at oStart; it could have been between oStart and oEnd 5997 o += qlMax 5998 else: 5999 o += minimumWindowSize 6000 # end While loop conditions 6001 if o > oTerminate: 6002 break 6003 else: # useExactOffsets is True: 6004 onAndOffOffsets = self.flatten().notesAndRests.stream()._uniqueOffsetsAndEndTimes() 6005 # environLocal.printDebug(['makeChords: useExactOffsets=True; 6006 # onAndOffOffsets:', onAndOffOffsets]) 6007 6008 for i in range(len(onAndOffOffsets) - 1): 6009 # get all notes within the start and the min window size 6010 oStart = onAndOffOffsets[i] 6011 oEnd = onAndOffOffsets[i + 1] 6012 subNotes = list(returnObj.getElementsByOffset( 6013 oStart, 6014 oEnd, 6015 includeEndBoundary=False, 6016 mustFinishInSpan=False, 6017 mustBeginInSpan=True 6018 ).getElementsByClass(matchClasses)) 6019 # environLocal.printDebug(['subNotes', subNotes]) 6020 # subNotes.show('t') 6021 6022 # make subNotes into a chord 6023 if subNotes: 6024 cOut = dealWithSubNotes(oEnd - oStart, subNotes) 6025 # insert chord at start location 6026 returnObj.coreInsert(oStart, cOut) 6027 6028 # makeRests to fill any gaps produced by stripping 6029 # environLocal.printDebug(['pre makeRests show()']) 6030 returnObj.coreElementsChanged() 6031 if makeRests: 6032 returnObj.makeRests( 6033 refStreamOrTimeRange=(preLowestOffset, preHighestTime), 6034 fillGaps=True, inPlace=True) 6035 return returnObj 6036 6037 def chordify( 6038 self, 6039 *, 6040 addTies=True, 6041 addPartIdAsGroup=False, 6042 removeRedundantPitches=True, 6043 toSoundingPitch=True, 6044 copyPitches=True, 6045 ): 6046 # noinspection PyShadowingNames 6047 ''' 6048 Create a chordal reduction of polyphonic music, where each 6049 change to a new pitch results in a new chord. If a Score or 6050 Part of Measures is provided, a Stream of Measures will be 6051 returned. If a flat Stream of notes, or a Score of such 6052 Streams is provided, no Measures will be returned. 6053 6054 If using chordify with chord symbols, ensure that the chord symbols 6055 have durations (by default the duration of a chord symbol object is 0, unlike 6056 a chord object). If harmony objects are not provided a duration, they 6057 will not be included in the chordified output pitches but may appear as chord symbol 6058 in notation on the score. To realize the chord symbol durations on a score, call 6059 :meth:`music21.harmony.realizeChordSymbolDurations` and pass in the score. 6060 6061 This functionality works by splitting all Durations in 6062 all parts, or if multi-part by all unique offsets. All 6063 simultaneous durations are then gathered into single chords. 6064 6065 If `addPartIdAsGroup` is True, all elements found in the 6066 Stream will have their source Part id added to the 6067 element's pitches' Group. These groups names are useful 6068 for partially "de-chordifying" the output. If the element chordifies to 6069 a :class:`~music21.chord.Chord` object, then the group will be found in each 6070 :class:`~music21.pitch.Pitch` element's .groups in Chord.pitches. If the 6071 element chordifies to a single :class:`~music21.note.Note` then .pitch.groups 6072 will hold the group name. 6073 6074 The `addTies` parameter currently does not work for pitches in Chords. 6075 6076 If `toSoundingPitch` is True, all parts that define one or 6077 more transpositions will be transposed to sounding pitch before chordification. 6078 True by default. 6079 6080 >>> s = stream.Score() 6081 >>> p1 = stream.Part() 6082 >>> p1.id = 'part1' 6083 >>> p1.insert(4, note.Note('C#4')) 6084 >>> p1.insert(5.3, note.Rest()) 6085 >>> p2 = stream.Part() 6086 >>> p2.id = 'part2' 6087 >>> p2.insert(2.12, note.Note('D-4', type='half')) 6088 >>> p2.insert(5.5, note.Rest()) 6089 >>> s.insert(0, p1) 6090 >>> s.insert(0, p2) 6091 >>> s.show('text', addEndTimes=True) 6092 {0.0 - 6.3} <music21.stream.Part part1> 6093 {4.0 - 5.0} <music21.note.Note C#> 6094 {5.3 - 6.3} <music21.note.Rest quarter> 6095 {0.0 - 6.5} <music21.stream.Part part2> 6096 {2.12 - 4.12} <music21.note.Note D-> 6097 {5.5 - 6.5} <music21.note.Rest quarter> 6098 6099 >>> cc = s.chordify() 6100 6101 >>> cc[3] 6102 <music21.chord.Chord C#4> 6103 >>> cc[3].duration.quarterLength 6104 Fraction(22, 25) 6105 6106 >>> cc.show('text', addEndTimes=True) 6107 {0.0 - 2.12} <music21.note.Rest 53/25ql> 6108 {2.12 - 4.0} <music21.chord.Chord D-4> 6109 {4.0 - 4.12} <music21.chord.Chord C#4 D-4> 6110 {4.12 - 5.0} <music21.chord.Chord C#4> 6111 {5.0 - 6.5} <music21.note.Rest dotted-quarter> 6112 6113 Here's how addPartIdAsGroup works: 6114 6115 >>> cc2 = s.chordify(addPartIdAsGroup=True) 6116 >>> cSharpDFlatChord = cc2[2] 6117 >>> for p in cSharpDFlatChord.pitches: 6118 ... (str(p), p.groups) 6119 ('C#4', ['part1']) 6120 ('D-4', ['part2']) 6121 6122 >>> s = stream.Stream() 6123 >>> p1 = stream.Part() 6124 >>> p1.insert(0, harmony.ChordSymbol('Cm', quarterLength=4.0)) 6125 >>> p1.insert(2, note.Note('C2')) 6126 >>> p1.insert(4, harmony.ChordSymbol('D', quarterLength=4.0)) 6127 >>> p1.insert(7, note.Note('A2')) 6128 >>> s.insert(0, p1) 6129 >>> s.chordify().show('text') 6130 {0.0} <music21.chord.Chord C3 E-3 G3> 6131 {2.0} <music21.chord.Chord C2 C3 E-3 G3> 6132 {3.0} <music21.chord.Chord C3 E-3 G3> 6133 {4.0} <music21.chord.Chord D3 F#3 A3> 6134 {7.0} <music21.chord.Chord A2 D3 F#3 A3> 6135 6136 Note that :class:`~music21.harmony.ChordSymbol` objects can also be chordified: 6137 6138 >>> s = stream.Stream() 6139 >>> p2 = stream.Part() 6140 >>> p1 = stream.Part() 6141 >>> p2.insert(0, harmony.ChordSymbol('Cm', quarterLength=4.0)) 6142 >>> p1.insert(2, note.Note('C2')) 6143 >>> p2.insert(4, harmony.ChordSymbol('D', quarterLength=4.0)) 6144 >>> p1.insert(7, note.Note('A2')) 6145 >>> s.insert(0, p1) 6146 >>> s.insert(0, p2) 6147 >>> s.chordify().show('text') 6148 {0.0} <music21.chord.Chord C3 E-3 G3> 6149 {2.0} <music21.chord.Chord C2 C3 E-3 G3> 6150 {3.0} <music21.chord.Chord C3 E-3 G3> 6151 {4.0} <music21.chord.Chord D3 F#3 A3> 6152 {7.0} <music21.chord.Chord A2 D3 F#3 A3> 6153 6154 If addPartIdAsGroup is True, and there are redundant pitches, 6155 ensure that the merged pitch has both groups 6156 6157 >>> s = stream.Score() 6158 >>> p0 = stream.Part(id='p0') 6159 >>> p0.insert(0, note.Note('C4')) 6160 >>> p1 = stream.Part(id='p1') 6161 >>> p1.insert(0, note.Note('C4')) 6162 >>> s.insert(0, p0) 6163 >>> s.insert(0, p1) 6164 >>> s1 = s.chordify(addPartIdAsGroup=True) 6165 >>> c = s1.recurse().notes[0] 6166 >>> c 6167 <music21.chord.Chord C4> 6168 >>> c.pitches[0].groups 6169 ['p0', 'p1'] 6170 6171 With copyPitches = False, then the original pitches are retained, which 6172 together with removeRedundantPitches=False can be a powerful tool for 6173 working back to the original score: 6174 6175 >>> n00 = note.Note('C4') 6176 >>> n01 = note.Note('E4') 6177 >>> n10 = note.Note('E4', type='half') 6178 >>> p0 = stream.Part(id='p0') 6179 >>> p1 = stream.Part(id='p1') 6180 >>> p0.append([n00, n01]) 6181 >>> p1.append(n10) 6182 >>> s = stream.Score() 6183 >>> s.insert(0, p0) 6184 >>> s.insert(0, p1) 6185 >>> ss = s.chordify(removeRedundantPitches=False, copyPitches=False, addPartIdAsGroup=True) 6186 >>> ss.show('text') 6187 {0.0} <music21.chord.Chord C4 E4> 6188 {1.0} <music21.chord.Chord E4 E4> 6189 6190 >>> c1 = ss.recurse().notes[1] 6191 >>> for p in c1.pitches: 6192 ... if 'p0' in p.groups: 6193 ... p.step = 'G' # make a complete triad 6194 >>> n01 6195 <music21.note.Note G> 6196 6197 Changes in v.5: 6198 6199 Runs a little faster for small scores and run a TON faster for big scores 6200 running in O(n) time not O(n^2) 6201 6202 no longer supported: displayTiedAccidentals=False, 6203 6204 Changes in v.6.3: 6205 6206 Added copyPitches 6207 6208 OMIT_FROM_DOCS 6209 6210 Test that chordifying works on a single stream. 6211 6212 >>> f2 = stream.Score() 6213 >>> f2.insert(0, metadata.Metadata()) 6214 >>> f2.insert(0, note.Note('C4')) 6215 >>> f2.insert(0, note.Note('D#4')) 6216 >>> c = f2.chordify() 6217 >>> cn = c.notes 6218 >>> cn[0].pitches 6219 (<music21.pitch.Pitch C4>, <music21.pitch.Pitch D#4>) 6220 ''' 6221 def chordifyOneMeasure(templateInner, streamToChordify): 6222 ''' 6223 streamToChordify is either a Measure or a Score=MeasureSlice 6224 ''' 6225 timespanTree = streamToChordify.asTimespans(classList=('GeneralNote',)) 6226 allTimePoints = timespanTree.allTimePoints() 6227 if 0 not in allTimePoints: 6228 allTimePoints = (0,) + allTimePoints 6229 6230 for offset, endTime in zip(allTimePoints, allTimePoints[1:]): 6231 if isclose(offset, endTime, abs_tol=1e-7): 6232 continue 6233 vert = timespanTree.getVerticalityAt(offset) 6234 quarterLength = endTime - offset 6235 if quarterLength < 0: # pragma: no cover 6236 environLocal.warn( 6237 'Something is wrong with the verticality ' 6238 + f'in stream {templateInner!r}: {vert!r} ' 6239 + f'its endTime {endTime} is less than its offset {offset}' 6240 ) 6241 6242 chordOrRest = vert.makeElement(quarterLength, 6243 addTies=addTies, 6244 addPartIdAsGroup=addPartIdAsGroup, 6245 removeRedundantPitches=removeRedundantPitches, 6246 copyPitches=copyPitches, 6247 ) 6248 6249 templateInner.coreInsert(opFrac(offset), chordOrRest) 6250 templateInner.coreElementsChanged() 6251 consolidateRests(templateInner) 6252 6253 def consolidateRests(templateInner): 6254 consecutiveRests = [] 6255 for el in list(templateInner.getElementsByClass('GeneralNote')): 6256 if not isinstance(el, note.Rest): 6257 removeConsecutiveRests(templateInner, consecutiveRests) 6258 consecutiveRests = [] 6259 else: 6260 consecutiveRests.append(el) 6261 removeConsecutiveRests(templateInner, consecutiveRests) 6262 6263 def removeConsecutiveRests(templateInner, consecutiveRests): 6264 if len(consecutiveRests) < 2: 6265 return 6266 totalDuration = sum(r.duration.quarterLength for r in consecutiveRests) 6267 startOffset = templateInner.elementOffset(consecutiveRests[0]) 6268 for r in consecutiveRests: 6269 templateInner.remove(r) 6270 rNew = note.Rest() 6271 rNew.duration.quarterLength = totalDuration 6272 templateInner.insert(startOffset, rNew) 6273 6274 # -------------------------------------- 6275 if toSoundingPitch: 6276 # environLocal.printDebug(['at sounding pitch', allParts[0].atSoundingPitch]) 6277 if (self.hasPartLikeStreams() 6278 and self.getElementsByClass('Stream').first().atSoundingPitch is False): 6279 workObj = self.toSoundingPitch(inPlace=False) 6280 elif self.atSoundingPitch is False: 6281 workObj = self.toSoundingPitch(inPlace=False) 6282 else: 6283 workObj = self 6284 else: 6285 workObj = self 6286 6287 if self.hasPartLikeStreams(): 6288 # use the measure boundaries of the first Part as a template. 6289 templateStream = workObj.getElementsByClass('Stream').first() 6290 else: 6291 templateStream = workObj 6292 6293 template = templateStream.template(fillWithRests=False, 6294 removeClasses=('GeneralNote',), 6295 retainVoices=False) 6296 6297 if template.hasMeasures(): 6298 measureIterator = template.getElementsByClass('Measure') 6299 templateMeasure: 'Measure' 6300 for i, templateMeasure in enumerate(measureIterator): 6301 # measurePart is likely a Score (MeasureSlice), not a measure 6302 measurePart: 'Measure' 6303 measurePart = workObj.measure(i, collect=(), indicesNotNumbers=True) 6304 if measurePart is not None: 6305 chordifyOneMeasure(templateMeasure, measurePart) 6306 else: 6307 environLocal.warn(f'Malformed Part object, {workObj}, at measure index {i}') 6308 else: 6309 chordifyOneMeasure(template, workObj) 6310 6311 # accidental displayStatus needs to change. 6312 for p in template.pitches: 6313 if p.accidental is not None and p.accidental.displayType != 'even-tied': 6314 p.accidental.displayStatus = None 6315 6316 if (hasattr(workObj, 'metadata') 6317 and workObj.metadata is not None 6318 and workObj.hasPartLikeStreams() is True): 6319 template.insert(0, copy.deepcopy(workObj.metadata)) 6320 6321 return template 6322 6323 def splitByClass(self, classObj, fx): 6324 # noinspection PyShadowingNames 6325 ''' 6326 Given a stream, get all objects of type classObj and divide them into 6327 two new streams depending on the results of fx. 6328 Fx should be a lambda or other function on elements. 6329 All elements where fx returns True go in the first stream. 6330 All other elements are put in the second stream. 6331 6332 If classObj is None then all elements are returned. ClassObj 6333 can also be a list of classes. 6334 6335 In this example, we will create 50 notes from midi note 30 (two 6336 octaves and a tritone below middle C) to midi note 80 (an octave 6337 and a minor sixth above middle C) and add them to a Stream. 6338 We then create a lambda function to split between those notes 6339 below middle C (midi note 60) and those above 6340 (google "lambda functions in Python" for more information on 6341 what these powerful tools are). 6342 6343 6344 >>> stream1 = stream.Stream() 6345 >>> for x in range(30, 81): 6346 ... n = note.Note() 6347 ... n.pitch.midi = x 6348 ... stream1.append(n) 6349 >>> fx = lambda n: n.pitch.midi < 60 6350 >>> b, c = stream1.splitByClass(note.Note, fx) 6351 6352 Stream b now contains all the notes below middle C, 6353 that is, 30 notes, beginning with F#1 and ending with B3 6354 while Stream c has the 21 notes from C4 to A-5: 6355 6356 >>> len(b) 6357 30 6358 >>> (b[0].nameWithOctave, b[-1].nameWithOctave) 6359 ('F#1', 'B3') 6360 >>> len(c) 6361 21 6362 >>> (c[0].nameWithOctave, c[-1].nameWithOctave) 6363 ('C4', 'G#5') 6364 ''' 6365 a = self.cloneEmpty(derivationMethod='splitByClass') 6366 b = self.cloneEmpty(derivationMethod='splitByClass') 6367 if classObj is not None: 6368 found = self.getElementsByClass(classObj).stream() 6369 else: 6370 found = self 6371 for e in found: 6372 if fx(e): 6373 a.coreInsert(found.elementOffset(e), e) # provide an offset here 6374 else: 6375 b.coreInsert(found.elementOffset(e), e) 6376 for e in found._endElements: 6377 if fx(e): 6378 # a.storeAtEnd(e) 6379 a.coreStoreAtEnd(e) 6380 else: 6381 # b.storeAtEnd(e) 6382 b.coreStoreAtEnd(e) 6383 a.coreElementsChanged() 6384 b.coreElementsChanged() 6385 return a, b 6386 6387 def offsetMap(self, srcObj=None): 6388 ''' 6389 Returns a list where each element is a NamedTuple 6390 consisting of the 'offset' of each element in a stream, the 6391 'endTime' (that is, the offset plus the duration) and the 6392 'element' itself. Also contains a 'voiceIndex' entry which 6393 contains the voice number of the element, or None if there 6394 are no voices. 6395 6396 >>> n1 = note.Note(type='quarter') 6397 >>> c1 = clef.AltoClef() 6398 >>> n2 = note.Note(type='half') 6399 >>> s1 = stream.Stream() 6400 >>> s1.append([n1, c1, n2]) 6401 >>> om = s1.offsetMap() 6402 >>> om[2].offset 6403 1.0 6404 >>> om[2].endTime 6405 3.0 6406 >>> om[2].element is n2 6407 True 6408 >>> om[2].voiceIndex 6409 6410 6411 Needed for makeMeasures and a few other places 6412 6413 The Stream source of elements is self by default, 6414 unless a `srcObj` is provided. 6415 6416 6417 >>> s = stream.Stream() 6418 >>> s.repeatAppend(note.Note(), 8) 6419 >>> for om in s.offsetMap(): 6420 ... om 6421 OffsetMap(element=<music21.note.Note C>, offset=0.0, endTime=1.0, voiceIndex=None) 6422 OffsetMap(element=<music21.note.Note C>, offset=1.0, endTime=2.0, voiceIndex=None) 6423 OffsetMap(element=<music21.note.Note C>, offset=2.0, endTime=3.0, voiceIndex=None) 6424 OffsetMap(element=<music21.note.Note C>, offset=3.0, endTime=4.0, voiceIndex=None) 6425 OffsetMap(element=<music21.note.Note C>, offset=4.0, endTime=5.0, voiceIndex=None) 6426 OffsetMap(element=<music21.note.Note C>, offset=5.0, endTime=6.0, voiceIndex=None) 6427 OffsetMap(element=<music21.note.Note C>, offset=6.0, endTime=7.0, voiceIndex=None) 6428 OffsetMap(element=<music21.note.Note C>, offset=7.0, endTime=8.0, voiceIndex=None) 6429 ''' 6430 if srcObj is None: 6431 srcObj = self 6432 # assume that flat/sorted options will be set before processing 6433 offsetMap = [] # list of start, start+dur, element 6434 if srcObj.hasVoices(): 6435 groups = [] 6436 for i, v in enumerate(srcObj.voices): 6437 groups.append((v.flatten(), i)) 6438 elsNotOfVoice = srcObj.getElementsNotOfClass('Voice') 6439 if len(elsNotOfVoice) > 0: 6440 groups.insert(0, (elsNotOfVoice, None)) 6441 else: # create a single collection 6442 groups = [(srcObj, None)] 6443 # environLocal.printDebug(['offsetMap', groups]) 6444 for group, voiceIndex in groups: 6445 for e in group._elements: 6446 # do not include barlines 6447 if isinstance(e, bar.Barline): 6448 continue 6449 dur = e.duration.quarterLength 6450 offset = group.elementOffset(e) 6451 endTime = opFrac(offset + dur) 6452 # NOTE: used to make a copy.copy of elements here; 6453 # this is not necessary b/c making deepcopy of entire Stream 6454 thisOffsetMap = _OffsetMap(e, offset, endTime, voiceIndex) 6455 # environLocal.printDebug(['offsetMap: thisOffsetMap', thisOffsetMap]) 6456 offsetMap.append(thisOffsetMap) 6457 # offsetMap.append((offset, offset + dur, e, voiceIndex)) 6458 # offsetMap.append([offset, offset + dur, copy.copy(e)]) 6459 return offsetMap 6460 6461 def makeMeasures( 6462 self, 6463 meterStream=None, 6464 refStreamOrTimeRange=None, 6465 searchContext=False, 6466 innerBarline=None, 6467 finalBarline='final', 6468 bestClef=False, 6469 inPlace=False, 6470 ): 6471 ''' 6472 Return a new stream (or if inPlace=True change in place) this 6473 Stream so that it has internal measures. 6474 6475 For more details, see :py:func:`~music21.stream.makeNotation.makeMeasures`. 6476 ''' 6477 return makeNotation.makeMeasures( 6478 self, 6479 meterStream=meterStream, 6480 refStreamOrTimeRange=refStreamOrTimeRange, 6481 searchContext=searchContext, 6482 innerBarline=innerBarline, 6483 finalBarline=finalBarline, 6484 bestClef=bestClef, 6485 inPlace=inPlace, 6486 ) 6487 6488 def makeRests( 6489 self, 6490 refStreamOrTimeRange=None, 6491 fillGaps=False, 6492 timeRangeFromBarDuration=False, 6493 inPlace=False, 6494 hideRests=False, 6495 ): 6496 ''' 6497 Calls :py:func:`~music21.stream.makeNotation.makeRests`. 6498 6499 Changed in v.7, inPlace=False by default. 6500 ''' 6501 return makeNotation.makeRests( 6502 self, 6503 refStreamOrTimeRange=refStreamOrTimeRange, 6504 fillGaps=fillGaps, 6505 timeRangeFromBarDuration=timeRangeFromBarDuration, 6506 inPlace=inPlace, 6507 hideRests=hideRests, 6508 ) 6509 6510 def makeTies(self, 6511 meterStream=None, 6512 inPlace=False, 6513 displayTiedAccidentals=False, 6514 classFilterList=(note.GeneralNote,), 6515 ): 6516 ''' 6517 Calls :py:func:`~music21.stream.makeNotation.makeTies`. 6518 6519 Changed in v.4., inPlace=False by default. 6520 Added in v.7, `classFilterList`. 6521 ''' 6522 return makeNotation.makeTies( 6523 self, 6524 meterStream=meterStream, 6525 inPlace=inPlace, 6526 displayTiedAccidentals=displayTiedAccidentals, 6527 classFilterList=classFilterList, 6528 ) 6529 6530 def makeBeams(self, *, inPlace=False, setStemDirections=True, failOnNoTimeSignature=False): 6531 ''' 6532 Return a new Stream, or modify the Stream in place, with beams applied to all 6533 notes. 6534 6535 See :py:func:`~music21.stream.makeNotation.makeBeams`. 6536 6537 New in v6.7 -- setStemDirections. 6538 New in v.7 -- failOnNoTimeSignature raises StreamException if no TimeSignature 6539 exists in the stream context from which to make measures. 6540 ''' 6541 return makeNotation.makeBeams( 6542 self, 6543 inPlace=inPlace, 6544 setStemDirections=setStemDirections, 6545 failOnNoTimeSignature=failOnNoTimeSignature, 6546 ) 6547 6548 def makeAccidentals( 6549 self, 6550 *, 6551 pitchPast: Optional[List[pitch.Pitch]] = None, 6552 pitchPastMeasure: Optional[List[pitch.Pitch]] = None, 6553 useKeySignature: Union[bool, key.KeySignature] = True, 6554 alteredPitches: Optional[List[pitch.Pitch]] = None, 6555 searchKeySignatureByContext: bool = False, 6556 cautionaryPitchClass: bool = True, 6557 cautionaryAll: bool = False, 6558 inPlace: bool = False, 6559 overrideStatus: bool = False, 6560 cautionaryNotImmediateRepeat: bool = True, 6561 tiePitchSet: Optional[Set[str]] = None 6562 ): 6563 ''' 6564 A method to set and provide accidentals given various conditions and contexts. 6565 6566 `pitchPast` is a list of pitches preceding this pitch in this measure. 6567 6568 `pitchPastMeasure` is a list of pitches preceding this pitch but in a previous measure. 6569 6570 6571 If `useKeySignature` is True, a :class:`~music21.key.KeySignature` will be searched 6572 for in this Stream or this Stream's defined contexts. An alternative KeySignature 6573 can be supplied with this object and used for temporary pitch processing. 6574 6575 If `alteredPitches` is a list of modified pitches (Pitches with Accidentals) that 6576 can be directly supplied to Accidental processing. These are the same values obtained 6577 from a :class:`music21.key.KeySignature` object using the 6578 :attr:`~music21.key.KeySignature.alteredPitches` property. 6579 6580 If `cautionaryPitchClass` is True, comparisons to past accidentals are made regardless 6581 of register. That is, if a past sharp is found two octaves above a present natural, 6582 a natural sign is still displayed. 6583 6584 If `cautionaryAll` is True, all accidentals are shown. 6585 6586 If `overrideStatus` is True, this method will ignore any current `displayStatus` setting 6587 found on the Accidental. By default this does not happen. If `displayStatus` is set to 6588 None, the Accidental's `displayStatus` is set. 6589 6590 If `cautionaryNotImmediateRepeat` is True, cautionary accidentals will be displayed for 6591 an altered pitch even if that pitch had already been displayed as altered. 6592 6593 If `tiePitchSet` is not None it should be a set of `.nameWithOctave` strings 6594 to determine whether following accidentals should be shown because the last 6595 note of the same pitch had a start or continue tie. 6596 6597 If `searchKeySignatureByContext` is True then keySignatures from the context of the 6598 stream will be used if none found. 6599 6600 The :meth:`~music21.pitch.Pitch.updateAccidentalDisplay` method is used to determine if 6601 an accidental is necessary. 6602 6603 This will assume that the complete Stream is the context of evaluation. For smaller context 6604 ranges, call this on Measure objects. 6605 6606 If `inPlace` is True, this is done in-place; if `inPlace` is False, 6607 this returns a modified deep copy. 6608 6609 Changed in v.6: does not return anything if inPlace is True. 6610 Changed in v.7: default inPlace is False 6611 6612 All arguments are keyword only. 6613 ''' 6614 if not inPlace: # make a copy 6615 returnObj = self.coreCopyAsDerivation('makeAccidentals') 6616 else: 6617 returnObj = self 6618 6619 # need to reset these lists unless values explicitly provided 6620 if pitchPast is None: 6621 pitchPast = [] 6622 if pitchPastMeasure is None: 6623 pitchPastMeasure = [] 6624 # see if there is any key signatures to add to altered pitches 6625 if alteredPitches is None: 6626 alteredPitches = [] 6627 addAlteredPitches: List[pitch.Pitch] = [] 6628 if isinstance(useKeySignature, key.KeySignature): 6629 addAlteredPitches = useKeySignature.alteredPitches 6630 elif useKeySignature is True: # get from defined contexts 6631 # will search local, then activeSite 6632 ksIter = None 6633 if searchKeySignatureByContext: 6634 ks = self.getContextByClass(key.KeySignature) 6635 if ks is not None: 6636 ksIter = [ks] 6637 else: 6638 ksIter = self.getElementsByClass(key.KeySignature) 6639 if ksIter: 6640 # assume we want the first found; in some cases it is possible 6641 # that this may not be true 6642 addAlteredPitches = ksIter[0].alteredPitches 6643 alteredPitches += addAlteredPitches 6644 # environLocal.printDebug(['processing makeAccidentals() with alteredPitches:', 6645 # alteredPitches]) 6646 6647 # need to move through notes in order 6648 # recurse to capture notes in substreams: https://github.com/cuthbertLab/music21/issues/577 6649 noteIterator = returnObj.recurse().notesAndRests 6650 6651 # environLocal.printDebug(['alteredPitches', alteredPitches]) 6652 # environLocal.printDebug(['pitchPast', pitchPast]) 6653 6654 if tiePitchSet is None: 6655 tiePitchSet = set() 6656 6657 last_measure: Optional[Measure] = None 6658 6659 for e in noteIterator: 6660 if e.activeSite is not None and e.activeSite.isMeasure: 6661 if last_measure is not None and e.activeSite is not last_measure: 6662 # New measure encountered: move pitchPast to 6663 # pitchPastMeasure and clear pitchPast 6664 pitchPastMeasure = pitchPast[:] 6665 pitchPast = [] 6666 last_measure = e.activeSite 6667 if isinstance(e, note.Note): 6668 if e.pitch.nameWithOctave in tiePitchSet: 6669 lastNoteWasTied = True 6670 else: 6671 lastNoteWasTied = False 6672 6673 e.pitch.updateAccidentalDisplay( 6674 pitchPast=pitchPast, 6675 pitchPastMeasure=pitchPastMeasure, 6676 alteredPitches=alteredPitches, 6677 cautionaryPitchClass=cautionaryPitchClass, 6678 cautionaryAll=cautionaryAll, 6679 overrideStatus=overrideStatus, 6680 cautionaryNotImmediateRepeat=cautionaryNotImmediateRepeat, 6681 lastNoteWasTied=lastNoteWasTied) 6682 pitchPast.append(e.pitch) 6683 6684 tiePitchSet.clear() 6685 if e.tie is not None and e.tie.type != 'stop': 6686 tiePitchSet.add(e.pitch.nameWithOctave) 6687 6688 elif isinstance(e, chord.Chord): 6689 # add all chord elements to past first 6690 # when reading a chord, this will apply an accidental 6691 # if pitches in the chord suggest an accidental 6692 seenPitchNames = set() 6693 6694 for n in list(e): 6695 p = n.pitch 6696 if p.nameWithOctave in tiePitchSet: 6697 lastNoteWasTied = True 6698 else: 6699 lastNoteWasTied = False 6700 6701 p.updateAccidentalDisplay( 6702 pitchPast=pitchPast, 6703 pitchPastMeasure=pitchPastMeasure, 6704 alteredPitches=alteredPitches, 6705 cautionaryPitchClass=cautionaryPitchClass, 6706 cautionaryAll=cautionaryAll, 6707 overrideStatus=overrideStatus, 6708 cautionaryNotImmediateRepeat=cautionaryNotImmediateRepeat, 6709 lastNoteWasTied=lastNoteWasTied) 6710 6711 if n.tie is not None and n.tie.type != 'stop': 6712 seenPitchNames.add(p.nameWithOctave) 6713 6714 tiePitchSet.clear() 6715 for pName in seenPitchNames: 6716 tiePitchSet.add(pName) 6717 6718 pitchPast += e.pitches 6719 else: 6720 tiePitchSet.clear() 6721 6722 returnObj.streamStatus.accidentals = True 6723 6724 if not inPlace: 6725 return returnObj 6726 6727 def haveAccidentalsBeenMade(self): 6728 # could be called: hasAccidentalDisplayStatusSet 6729 ''' 6730 If Accidentals.displayStatus is None for all 6731 contained pitches, it as assumed that accidentals 6732 have not been set for display and/or makeAccidentals 6733 has not been run. If any Accidental has displayStatus 6734 other than None, this method returns True, regardless 6735 of if makeAccidentals has actually been run. 6736 ''' 6737 return self.streamStatus.accidentals 6738 6739 def makeNotation(self, 6740 *, 6741 meterStream=None, 6742 refStreamOrTimeRange=None, 6743 inPlace=False, 6744 bestClef=False, 6745 pitchPast: Optional[List[pitch.Pitch]] = None, 6746 pitchPastMeasure: Optional[List[pitch.Pitch]] = None, 6747 useKeySignature: Union[bool, key.KeySignature] = True, 6748 alteredPitches: Optional[List[pitch.Pitch]] = None, 6749 cautionaryPitchClass: bool = True, 6750 cautionaryAll: bool = False, 6751 overrideStatus: bool = False, 6752 cautionaryNotImmediateRepeat: bool = True, 6753 tiePitchSet: Optional[Set[str]] = None 6754 ): 6755 ''' 6756 This method calls a sequence of Stream methods on this Stream to prepare 6757 notation, including creating voices for overlapped regions, Measures 6758 if necessary, creating ties, beams, accidentals, and tuplet brackets. 6759 6760 If `inPlace` is True, this is done in-place (changed in v7 -- returns None); 6761 if `inPlace` is False, this returns a modified deep copy. 6762 6763 The following additional parameters are documented on 6764 :meth:`~music21.stream.base.makeAccidentals`:: 6765 6766 pitchPast 6767 pitchPastMeasure 6768 useKeySignature 6769 alteredPitches 6770 cautionaryPitchClass 6771 cautionaryAll 6772 overrideStatus 6773 cautionaryNotImmediateRepeat 6774 tiePitchSet 6775 6776 6777 >>> s = stream.Stream() 6778 >>> n = note.Note('g') 6779 >>> n.quarterLength = 1.5 6780 >>> s.repeatAppend(n, 10) 6781 >>> sMeasures = s.makeNotation() 6782 >>> len(sMeasures.getElementsByClass('Measure')) 6783 4 6784 >>> sMeasures.getElementsByClass('Measure').last().rightBarline.type 6785 'final' 6786 ''' 6787 # determine what is the object to work on first 6788 if inPlace: 6789 returnStream = self 6790 else: 6791 returnStream = self.coreCopyAsDerivation('makeNotation') 6792 6793 # if 'finalBarline' in subroutineKeywords: 6794 # lastBarlineType = subroutineKeywords['finalBarline'] 6795 # else: 6796 # lastBarlineType = 'final' 6797 6798 # retrieve necessary spanners; insert only if making a copy 6799 returnStream.coreGatherMissingSpanners( 6800 insert=not inPlace, 6801 # definitely do NOT put a constrainingSpannerBundle constraint 6802 ) 6803 # only use inPlace arg on first usage 6804 if not self.hasMeasures(): 6805 # only try to make voices if no Measures are defined 6806 returnStream.makeVoices(inPlace=True, fillGaps=True) 6807 # if this is not inPlace, it will return a newStream; if 6808 # inPlace, this returns None 6809 # use inPlace=True, as already established above 6810 returnStream.makeMeasures( 6811 meterStream=meterStream, 6812 refStreamOrTimeRange=refStreamOrTimeRange, 6813 inPlace=True, 6814 bestClef=bestClef) 6815 6816 measureStream = returnStream.getElementsByClass('Measure').stream() 6817 # environLocal.printDebug(['Stream.makeNotation(): post makeMeasures, 6818 # length', len(returnStream)]) 6819 if not measureStream: 6820 raise StreamException( 6821 f'no measures found in stream with {len(self)} elements') 6822 6823 # for now, calling makeAccidentals once per measures 6824 # pitches from last measure are passed 6825 # this needs to be called before makeTies 6826 # note that this functionality is also placed in Part 6827 if not measureStream.streamStatus.accidentals: 6828 makeNotation.makeAccidentalsInMeasureStream( 6829 measureStream, 6830 pitchPast=pitchPast, 6831 pitchPastMeasure=pitchPastMeasure, 6832 useKeySignature=useKeySignature, 6833 alteredPitches=alteredPitches, 6834 cautionaryPitchClass=cautionaryPitchClass, 6835 cautionaryAll=cautionaryAll, 6836 overrideStatus=overrideStatus, 6837 cautionaryNotImmediateRepeat=cautionaryNotImmediateRepeat, 6838 tiePitchSet=tiePitchSet) 6839 6840 measureStream.makeTies(meterStream, inPlace=True) 6841 6842 # measureStream.makeBeams(inPlace=True) 6843 if not measureStream.streamStatus.beams: 6844 try: 6845 measureStream.makeBeams(inPlace=True) 6846 except meter.MeterException as me: 6847 environLocal.warn(['skipping makeBeams exception', me]) 6848 6849 # note: this needs to be after makeBeams, as placing this before 6850 # makeBeams was causing the duration's tuplet to lose its type setting 6851 # check for tuplet brackets one measure at a time 6852 # this means that they will never extend beyond one measure 6853 for m in measureStream: 6854 if not m.streamStatus.tuplets: 6855 makeNotation.makeTupletBrackets(m, inPlace=True) 6856 6857 if not inPlace: 6858 return returnStream 6859 6860 def extendDuration(self, objName, *, inPlace=False): 6861 ''' 6862 Given a Stream and an object class name, go through the Stream 6863 and find each instance of the desired object. The time between 6864 adjacent objects is then assigned to the duration of each object. 6865 The last duration of the last object is assigned to extend to the 6866 end of the Stream. 6867 6868 If `inPlace` is True, this is done in-place; if `inPlace` is 6869 False, this returns a modified deep copy. 6870 6871 >>> stream1 = stream.Stream() 6872 >>> n = note.Note(type='quarter') 6873 >>> n.duration.quarterLength 6874 1.0 6875 >>> stream1.repeatInsert(n, [0, 10, 20, 30, 40]) 6876 6877 >>> dyn = dynamics.Dynamic('ff') 6878 >>> stream1.insert(15, dyn) 6879 >>> stream1[-1].offset # offset of last element 6880 40.0 6881 >>> stream1.duration.quarterLength # total duration 6882 41.0 6883 >>> len(stream1) 6884 6 6885 6886 >>> stream2 = stream1.flatten().extendDuration(note.GeneralNote, inPlace=False) 6887 >>> len(stream2) 6888 6 6889 >>> stream2[0].duration.quarterLength 6890 10.0 6891 6892 The Dynamic does not affect the second note: 6893 6894 >>> stream2[1].offset 6895 10.0 6896 >>> stream2[1].duration.quarterLength 6897 10.0 6898 6899 >>> stream2[-1].duration.quarterLength # or extend to end of stream 6900 1.0 6901 >>> stream2.duration.quarterLength 6902 41.0 6903 >>> stream2[-1].offset 6904 40.0 6905 ''' 6906 6907 if not inPlace: # make a copy 6908 returnObj = self.coreCopyAsDerivation('extendDuration') 6909 else: 6910 returnObj = self 6911 6912 qLenTotal = returnObj.duration.quarterLength 6913 elements = list(returnObj.getElementsByClass(objName)) 6914 6915 # print(elements[-1], qLenTotal, elements[-1].duration) 6916 # print(_MOD, elements) 6917 for i in range(len(elements) - 1): 6918 # print(i, len(elements)) 6919 span = returnObj.elementOffset(elements[i + 1]) - returnObj.elementOffset(elements[i]) 6920 elements[i].duration.quarterLength = span 6921 6922 # handle last element 6923 # print(elements[-1], qLenTotal, elements[-1].duration) 6924 if elements: 6925 elements[-1].duration.quarterLength = (qLenTotal 6926 - returnObj.elementOffset(elements[-1])) 6927 # print(elements[-1], elements[-1].duration) 6928 if not inPlace: 6929 return returnObj 6930 6931 @common.deprecated('v7', 'v8', 'call extendDurations() and getElementsByClass() separately') 6932 def extendDurationAndGetBoundaries(self, objName, *, inPlace=False): # pragma: no cover 6933 ''' 6934 DEPRECATED in v.7 -- to be removed in v.8 6935 6936 Extend the Duration of elements specified by objName; 6937 then, collect a dictionary for every matched element of objName class, 6938 where the matched element is the value and the key is the (start, end) offset value. 6939 6940 >>> from pprint import pprint as pp 6941 >>> s = stream.Stream() 6942 >>> s.insert(3, dynamics.Dynamic('mf')) 6943 >>> s.insert(7, dynamics.Dynamic('f')) 6944 >>> s.insert(12, dynamics.Dynamic('ff')) 6945 >>> #_DOCS_SHOW pp(s.extendDurationAndGetBoundaries('Dynamic')) 6946 {(3.0, 7.0): <music21.dynamics.Dynamic mf>, 6947 (7.0, 12.0): <music21.dynamics.Dynamic f>, 6948 (12.0, 12.0): <music21.dynamics.Dynamic ff>} 6949 6950 6951 TODO: only allow inPlace=True or delete or something, can't return two different things 6952 ''' 6953 if not inPlace: # make a copy 6954 returnObj = copy.deepcopy(self) 6955 else: 6956 returnObj = self 6957 returnObj.extendDuration(objName, inPlace=True) 6958 # TODO: use iteration. 6959 elements = returnObj.getElementsByClass(objName) 6960 boundaries = {} 6961 if not elements: 6962 raise StreamException('no elements of this class defined in this Stream') 6963 6964 for e in elements: 6965 start = returnObj.elementOffset(e) 6966 end = start + e.duration.quarterLength 6967 boundaries[(start, end)] = e 6968 return boundaries 6969 6970 def stripTies( 6971 self, 6972 inPlace=False, 6973 matchByPitch=True 6974 ): 6975 # noinspection PyShadowingNames 6976 ''' 6977 Find all notes that are tied; remove all tied notes, 6978 then make the first of the tied notes have a duration 6979 equal to that of all tied constituents. Lastly, 6980 remove the formerly-tied notes. 6981 6982 This method can be used on Stream and Stream subclasses. 6983 When used on a stream containing Part-like substreams, as with many scores, 6984 :class:`~music21.stream.Part`, :class:`~music21.stream.Measure`, and other 6985 Stream subclasses are retained. 6986 6987 `inPlace` controls whether the input stream is modified or whether a deep copy 6988 is made. (New in v7, to conform to the rest of music21, `inPlace=True` returns `None`.) 6989 6990 Presently, this only works if tied notes are sequential in the same voice; ultimately 6991 this will need to look at .to and .from attributes (if they exist) 6992 6993 >>> a = stream.Stream() 6994 >>> n = note.Note() 6995 >>> n.quarterLength = 6 6996 >>> a.append(n) 6997 >>> m = a.makeMeasures() 6998 >>> m.makeTies(inPlace=True) 6999 >>> len(m.flatten().notes) 7000 2 7001 7002 >>> m = m.stripTies() 7003 >>> len(m.flatten().notes) 7004 1 7005 7006 In cases where notes are manipulated after initial tie creation, 7007 some chord members might lack ties. This will not prevent merging the tied notes 7008 if all the pitches match, and `matchByPitch=True` (default): 7009 7010 >>> c1 = chord.Chord('C4 E4') 7011 >>> c1.tie = tie.Tie('start') 7012 7013 >>> c2 = chord.Chord('C4 E4') 7014 >>> c2.tie = tie.Tie('stop') 7015 7016 >>> m = stream.Measure() 7017 >>> m.append([c1, c2]) 7018 7019 >>> c1.add(note.Note('G4')) 7020 >>> c2.add(note.Note('G4')) 7021 7022 >>> c2.notes[-1].tie is None 7023 True 7024 7025 >>> strippedPitchMatching = m.stripTies() 7026 >>> len(strippedPitchMatching.flatten().notes) 7027 1 7028 7029 This can be prevented with `matchByPitch=False`, in which case every note, 7030 including each chord member, must have stop and/or continue tie types, 7031 which was not the case above: 7032 7033 >>> strippedMixedTieTypes = m.stripTies(matchByPitch=False) 7034 >>> len(strippedMixedTieTypes.flatten().notes) 7035 2 7036 7037 >>> c2.notes[0].tie = tie.Tie('stop') 7038 >>> c2.notes[1].tie = tie.Tie('stop') 7039 >>> c2.notes[2].tie = tie.Tie('stop') 7040 >>> strippedUniformTieTypes = m.stripTies(matchByPitch=False) 7041 >>> len(strippedUniformTieTypes.flatten().notes) 7042 1 7043 7044 Notice the matching happens even after altering the pitches: 7045 7046 >>> c3 = c2.transpose(6) 7047 >>> otherM = stream.Measure([c1, c3]) 7048 >>> strippedTransposed = otherM.stripTies(matchByPitch=False) 7049 >>> len(strippedTransposed.flatten().notes) 7050 1 7051 7052 Changed in v.7 -- `matchByPitch` defaults True, and the following 7053 behavior defined regarding chords with a tie type "continue": 7054 7055 >>> c1.notes[0].tie = tie.Tie('continue') 7056 >>> c1.notes[1].tie = tie.Tie('start') 7057 >>> c1.notes[2].tie = tie.Tie('start') 7058 7059 Continue is accepted here as an ersatz-start: 7060 7061 >>> stripped1 = m.stripTies(matchByPitch=True) 7062 >>> len(stripped1.flatten().notes) 7063 1 7064 7065 But prepend an element so that it's considered as a tie continuation: 7066 7067 >>> c0 = chord.Chord('C4 E4 G4') 7068 >>> c0.tie = tie.Tie('start') 7069 >>> m2 = stream.Measure() 7070 >>> m2.append([c0, c1, c2]) 7071 7072 Now the mixed tie types on c1 will only be connected to c2 7073 on the permissive option (`matchByPitch=True`): 7074 7075 >>> stripped2 = m2.stripTies(matchByPitch=True) 7076 >>> stripped2.elements 7077 (<music21.chord.Chord C4 E4 G4>,) 7078 7079 >>> stripped3 = m2.stripTies(matchByPitch=False) 7080 >>> stripped3.elements 7081 (<music21.chord.Chord C4 E4 G4>, 7082 <music21.chord.Chord C4 E4 G4>, 7083 <music21.chord.Chord C4 E4 G4>) 7084 7085 Now correct the tie types on c1 and try the strict option: 7086 7087 >>> c1.notes[0].tie = tie.Tie('continue') 7088 >>> c1.notes[1].tie = tie.Tie('continue') 7089 >>> c1.notes[2].tie = tie.Tie('continue') 7090 >>> stripped4 = m2.stripTies(matchByPitch=False) 7091 >>> stripped4.elements 7092 (<music21.chord.Chord C4 E4 G4>,) 7093 7094 Now replace the first element with just a single C4 note. 7095 The following chords will be merged with each other, but not with the single 7096 note, even on `matchByPitch=False`. 7097 (`matchByPitch=False` is permissive about pitch but strict about cardinality.) 7098 7099 >>> newC = note.Note('C4') 7100 >>> newC.tie = tie.Tie('start') 7101 >>> m2.replace(c0, newC) 7102 >>> stripped5 = m2.stripTies(matchByPitch=False) 7103 >>> stripped5.elements 7104 (<music21.note.Note C>, <music21.chord.Chord C4 E4 G4>) 7105 ''' 7106 # environLocal.printDebug(['calling stripTies']) 7107 if not inPlace: # make a copy 7108 returnObj = self.coreCopyAsDerivation('stripTies') 7109 else: 7110 returnObj = self 7111 7112 # Clear existing beaming because notes may be deleted at any level of hierarchy 7113 returnObj.streamStatus.beams = False 7114 7115 if returnObj.hasPartLikeStreams(): 7116 # part-like does not necessarily mean that the next level down is a stream.Part 7117 # object or that this is a stream.Score object, so do not substitute 7118 # returnObj.parts for this... 7119 for p in returnObj.getElementsByClass('Stream'): 7120 # already copied if necessary; edit in place 7121 p.stripTies(inPlace=True, matchByPitch=matchByPitch) 7122 if not inPlace: 7123 return returnObj 7124 else: 7125 return # exit 7126 7127 if returnObj.hasVoices(): 7128 for v in returnObj.voices: 7129 # already copied if necessary; edit in place 7130 v.stripTies(inPlace=True, matchByPitch=matchByPitch) 7131 if not inPlace: 7132 return returnObj 7133 else: 7134 return # exit 7135 7136 # need to just get .notesAndRests, as there may be other objects in the Measure 7137 # that come before the first Note, such as a SystemLayout object 7138 f = returnObj.flatten() 7139 notes = f.notesAndRests.stream() 7140 7141 posConnected = [] # temporary storage for index of tied notes 7142 posDelete = [] # store deletions to be processed later 7143 7144 def updateEndMatch(nInner) -> bool: 7145 ''' 7146 updateEndMatch based on nList, iLast, matchByPitch, etc. 7147 ''' 7148 # 2 cases before matchByPitch=False returns early. 7149 7150 # Case 1: nInner is not a chord, and it has a stop tie 7151 # can't trust chords, which only tell if SOME member has a tie 7152 # matchByPitch does not matter here 7153 # https://github.com/cuthbertLab/music21/issues/502 7154 if (hasattr(nInner, 'tie') 7155 and not isinstance(nInner, chord.Chord) 7156 and nInner.tie is not None 7157 and nInner.tie.type == 'stop'): 7158 return True 7159 # Case 2: matchByPitch=False and all chord members have a stop tie 7160 # and checking cardinality passes (don't match chords to single notes) 7161 if (hasattr(nInner, 'tie') 7162 and not matchByPitch 7163 and isinstance(nInner, chord.Chord) 7164 and None not in [inner_p.tie for inner_p in nInner.notes] 7165 and {inner_p.tie.type for inner_p in nInner.notes} == {'stop'} 7166 and nLast is not None and len(nLast.pitches) == len(nInner.pitches)): 7167 return True 7168 7169 # Now, matchByPitch 7170 # if we cannot find a stop tie, see if last note was connected 7171 # and this and the last note are the same pitch; this assumes 7172 # that connected and same pitch value is tied; this is not 7173 # frequently the case 7174 elif not matchByPitch: 7175 return False 7176 7177 # find out if the last index is in position connected 7178 # if the pitches are the same for each note 7179 if (nLast is not None 7180 and iLast in posConnected 7181 and hasattr(nLast, 'pitch') 7182 and hasattr(nInner, 'pitch') 7183 # before doing pitch comparison, need to 7184 # make sure we're not comparing a Note to a Chord 7185 and 'Chord' not in nLast.classes 7186 and 'Chord' not in nInner.classes 7187 and nLast.pitch == nInner.pitch): 7188 return True 7189 # looking for two chords of equal size 7190 if (nLast is not None 7191 and not isinstance(nInner, note.Note) 7192 and iLast in posConnected 7193 and hasattr(nLast, 'pitches') 7194 and hasattr(nInner, 'pitches')): 7195 if len(nLast.pitches) != len(nInner.pitches): 7196 return False 7197 7198 for pitchIndex in range(len(nLast.pitches)): 7199 if nLast.pitches[pitchIndex] != nInner.pitches[pitchIndex]: 7200 return False 7201 return True 7202 7203 return False 7204 7205 def allTiesAreContinue(nr: note.NotRest) -> bool: 7206 if nr.tie is None: # pragma: no cover 7207 return False 7208 if nr.tie.type != 'continue': 7209 return False 7210 # check every chord member, since tie type "continue" on a chord 7211 # only indicates that SOME member is tie-continue. 7212 if 'Chord' in nr.classes: 7213 for innerN in nr.notes: 7214 if innerN.tie is None: 7215 return False 7216 if innerN.tie.type != 'continue': 7217 return False 7218 return True 7219 7220 for i in range(len(notes)): 7221 endMatch = None # can be True, False, or None 7222 n = notes[i] 7223 if i > 0: # get i and n for the previous value 7224 iLast = i - 1 7225 nLast = notes[iLast] 7226 else: 7227 iLast = None 7228 nLast = None 7229 7230 # see if we have a tie and it is started 7231 # a start typed tie may not be a true start tie 7232 if (hasattr(n, 'tie') 7233 and n.tie is not None 7234 and n.tie.type == 'start'): 7235 # find a true start, add to known connected positions 7236 if iLast is None or iLast not in posConnected: 7237 posConnected = [i] # reset list with start 7238 # find a continuation: the last note was a tie 7239 # start and this note is a tie start (this may happen) 7240 elif iLast in posConnected: 7241 posConnected.append(i) 7242 # a connection has been started or continued, so no endMatch 7243 endMatch = False 7244 7245 # a continue may or may not imply a connection 7246 elif (hasattr(n, 'tie') 7247 and n.tie is not None 7248 and n.tie.type == 'continue'): 7249 # is this actually a start? 7250 if not posConnected: 7251 posConnected.append(i) 7252 endMatch = False 7253 elif matchByPitch: 7254 # try to match pitch against nLast 7255 # updateEndMatch() checks for equal cardinality 7256 tempEndMatch = updateEndMatch(n) 7257 if tempEndMatch: 7258 posConnected.append(i) 7259 # ... and keep going. 7260 endMatch = False 7261 else: 7262 # clear list and populate with this element 7263 posConnected = [i] 7264 endMatch = False 7265 elif allTiesAreContinue(n): 7266 # uniform-continue suffices if not matchByPitch 7267 # but still need to check cardinality 7268 if nLast and (len(nLast.pitches) != len(n.pitches)): 7269 # different sizes: clear list and populate with this element 7270 # since allTiesAreContinue, it is okay to treat as ersatz-start 7271 posConnected = [i] 7272 else: 7273 posConnected.append(i) 7274 # either way, this was not a stop 7275 endMatch = False 7276 else: 7277 # only SOME ties on this chord are "continue": reject 7278 posConnected = [] 7279 endMatch = False 7280 7281 # establish end condition 7282 if endMatch is None: # not yet set, not a start or continue 7283 endMatch = updateEndMatch(n) 7284 7285 # process end condition 7286 if endMatch: 7287 posConnected.append(i) # add this last position 7288 if len(posConnected) < 2: 7289 # an open tie, not connected to anything 7290 # should be an error; presently, just skipping 7291 # raise StreamException('cannot consolidate ties when only one tie is present', 7292 # notes[posConnected[0]]) 7293 # environLocal.printDebug( 7294 # ['cannot consolidate ties when only one tie is present', 7295 # notes[posConnected[0]]]) 7296 posConnected = [] 7297 continue 7298 7299 # get sum of durations for all notes 7300 # do not include first; will add to later; do not delete 7301 durSum = 0 7302 for q in posConnected[1:]: # all but the first 7303 durSum += notes[q].quarterLength 7304 posDelete.append(q) # store for deleting later 7305 # dur sum should always be greater than zero 7306 if durSum == 0: 7307 raise StreamException('aggregated ties have a zero duration sum') 7308 # change the duration of the first note to be self + sum 7309 # of all others 7310 qLen = notes[posConnected[0]].quarterLength 7311 notes[posConnected[0]].quarterLength = qLen + durSum 7312 7313 # set tie to None on first note 7314 notes[posConnected[0]].tie = None 7315 7316 # replace removed elements in spanners 7317 for sp in f.spanners: 7318 for index in posConnected[1:]: 7319 if notes[index] in sp: 7320 sp.replaceSpannedElement(notes[index], notes[posConnected[0]]) 7321 7322 posConnected = [] # reset to empty 7323 7324 # all results have been processed 7325 posDelete.reverse() # start from highest and go down 7326 7327 for i in posDelete: 7328 # environLocal.printDebug(['removing note', notes[i]]) 7329 # get the obj ref 7330 nTarget = notes[i] 7331 # Recurse rather than depend on the containers being Measures 7332 # https://github.com/cuthbertLab/music21/issues/266 7333 returnObj.remove(nTarget, recurse=True) 7334 7335 if not inPlace: 7336 return returnObj 7337 7338 def extendTies(self, ignoreRests=False, pitchAttr='nameWithOctave'): 7339 ''' 7340 Connect any adjacent pitch space values that are the 7341 same with a Tie. Adjacent pitches can be Chords, Notes, or Voices. 7342 7343 If `ignoreRests` is True, rests that occur between events will not be 7344 considered in matching pitches. 7345 7346 The `pitchAttr` determines the pitch attribute that is 7347 used for comparison. Any valid pitch attribute name can be used. 7348 ''' 7349 def _getNextElements(srcStream, currentIndex, targetOffset): 7350 # need to find next event that start at the appropriate offset 7351 if currentIndex == len(srcStream) - 1: # assume flat 7352 # environLocal.printDebug(['_getNextElements: nothing to process', 7353 # currentIndex, len(srcStream.notes) ]) 7354 return [] # nothing left 7355 # iterate over all possible elements 7356 if ignoreRests: 7357 # need to find the offset of the first thing that is not rest 7358 for j in range(currentIndex + 1, len(srcStream._elements)): 7359 el = srcStream._elements[j] 7360 if isinstance(el, note.NotRest): 7361 # change target offset to this position 7362 targetOffset = srcStream._elements[j].getOffsetBySite( 7363 srcStream) 7364 break 7365 match = srcStream.getElementsByOffset(targetOffset) 7366 # filter matched elements 7367 post = [] 7368 for matchEl in match: 7369 if isinstance(matchEl, note.NotRest): 7370 post.append(matchEl) 7371 return post 7372 7373 # take all flat elements; this will remove all voices; just use offset 7374 # position 7375 # do not need to worry about ._endElements 7376 srcFlat = self.flatten().notes.stream() 7377 for i, e in enumerate(srcFlat): 7378 pSrc = [] 7379 if isinstance(e, note.Note): 7380 pSrc = [e] 7381 elif isinstance(e, chord.Chord): 7382 pSrc = list(e) # get components 7383 else: 7384 continue 7385 # environLocal.printDebug(['examining', i, e]) 7386 connections = _getNextElements(srcFlat, i, 7387 e.getOffsetBySite(srcFlat) + e.duration.quarterLength) 7388 # environLocal.printDebug(['possible connections', connections]) 7389 7390 for p, m in itertools.product(pSrc, connections): 7391 # for each p, see if there is match in the next position 7392 # for each element, look for a pitch to match 7393 mSrc = [] 7394 if isinstance(m, note.Note): 7395 mSrc = [m] 7396 elif isinstance(m, chord.Chord): 7397 mSrc = list(m) # get components 7398 # final note comparison 7399 for q in mSrc: 7400 if getattr(q.pitch, pitchAttr) == getattr(p.pitch, pitchAttr): 7401 # create a tie from p to q 7402 if p.tie is None: 7403 p.tie = tie.Tie('start') 7404 elif p.tie.type == 'stop': 7405 p.tie.type = 'continue' 7406 # if dst tie exists, assume it connects 7407 q.tie = tie.Tie('stop') 7408 break # can only have one match from p to q 7409 7410 # -------------------------------------------------------------------------- 7411 7412 def sort(self, force=False): 7413 ''' 7414 Sort this Stream in place by offset, then priority, then 7415 standard class sort order (e.g., Clefs before KeySignatures before 7416 TimeSignatures). 7417 7418 Note that Streams automatically sort themselves unless 7419 autoSort is set to False (as in the example below) 7420 7421 If `force` is True, a sort will be attempted regardless of any other parameters. 7422 7423 7424 >>> n1 = note.Note('A') 7425 >>> n2 = note.Note('B') 7426 >>> s = stream.Stream() 7427 >>> s.autoSort = False 7428 >>> s.insert(100, n2) 7429 >>> s.insert(0, n1) # now a has a lower offset by higher index 7430 >>> [n.name for n in s] 7431 ['B', 'A'] 7432 >>> s[0].name 7433 'B' 7434 >>> s.sort() 7435 >>> s[0].name 7436 'A' 7437 >>> [n.name for n in s] 7438 ['A', 'B'] 7439 ''' 7440 # trust if this is sorted: do not sort again 7441 # experimental 7442 if (not self.isSorted and self._mutable) or force: 7443 self._elements.sort(key=lambda x: x.sortTuple(self)) 7444 self._endElements.sort(key=lambda x: x.sortTuple(self)) 7445 7446 # as sorting changes order, elements have changed; 7447 # need to clear cache, but flat status is the same 7448 self.coreElementsChanged( 7449 updateIsFlat=False, 7450 clearIsSorted=False, 7451 keepIndex=False, # this is False by default, but just to be sure for later 7452 ) 7453 self.isSorted = True 7454 # environLocal.printDebug(['_elements', self._elements]) 7455 7456 def sorted(self): 7457 # noinspection PyShadowingNames 7458 ''' 7459 (TL;DR: you probably do not need to call this method unless you have turned `.autoSort` to 7460 off.) 7461 7462 Returns a new Stream where all the elements are sorted according to offset time, then 7463 priority, then classSortOrder (so that, for instance, a Clef at offset 0 appears before 7464 a Note at offset 0). 7465 7466 If this Stream is not flat, then only the elements directly in the stream itself are sorted. 7467 To sort all, run myStream.flatten().sorted(). 7468 7469 Changed in v7 -- made into a method, not a property. 7470 7471 For instance, here is an unsorted Stream: 7472 7473 >>> s = stream.Stream() 7474 >>> s.autoSort = False # if True, sorting is automatic 7475 >>> s.insert(1, note.Note('D')) 7476 >>> s.insert(0, note.Note('C')) 7477 >>> s.show('text') 7478 {1.0} <music21.note.Note D> 7479 {0.0} <music21.note.Note C> 7480 7481 7482 But a sorted version of the Stream puts the C first: 7483 7484 >>> s.sorted().show('text') 7485 {0.0} <music21.note.Note C> 7486 {1.0} <music21.note.Note D> 7487 7488 While the original stream remains unsorted: 7489 7490 >>> s.show('text') 7491 {1.0} <music21.note.Note D> 7492 {0.0} <music21.note.Note C> 7493 7494 7495 OMIT_FROM_DOCS 7496 7497 >>> s = stream.Stream() 7498 >>> s.autoSort = False 7499 >>> s.repeatInsert(note.Note('C#'), [0, 2, 4]) 7500 >>> s.repeatInsert(note.Note('D-'), [1, 3, 5]) 7501 >>> s.isSorted 7502 False 7503 >>> g = '' 7504 >>> for myElement in s: 7505 ... g += '%s: %s; ' % (myElement.offset, myElement.name) 7506 >>> g 7507 '0.0: C#; 2.0: C#; 4.0: C#; 1.0: D-; 3.0: D-; 5.0: D-; ' 7508 >>> y = s.sorted() 7509 >>> y.isSorted 7510 True 7511 >>> g = '' 7512 >>> for myElement in y: 7513 ... g += '%s: %s; ' % (myElement.offset, myElement.name) 7514 >>> g 7515 '0.0: C#; 1.0: D-; 2.0: C#; 3.0: D-; 4.0: C#; 5.0: D-; ' 7516 >>> farRight = note.Note('E') 7517 >>> farRight.priority = 5 7518 >>> farRight.offset = 2.0 7519 >>> y.insert(farRight) 7520 >>> g = '' 7521 >>> for myElement in y: 7522 ... g += '%s: %s; ' % (myElement.offset, myElement.name) 7523 >>> g 7524 '0.0: C#; 1.0: D-; 2.0: C#; 3.0: D-; 4.0: C#; 5.0: D-; 2.0: E; ' 7525 >>> z = y.sorted() 7526 >>> g = '' 7527 >>> for myElement in z: 7528 ... g += '%s: %s; ' % (myElement.offset, myElement.name) 7529 >>> g 7530 '0.0: C#; 1.0: D-; 2.0: C#; 2.0: E; 3.0: D-; 4.0: C#; 5.0: D-; ' 7531 >>> z[2].name, z[3].name 7532 ('C#', 'E') 7533 ''' 7534 cache_sorted = self._cache.get('sorted') 7535 if cache_sorted is not None: 7536 return cache_sorted 7537 shallowElements = copy.copy(self._elements) # already a copy 7538 shallowEndElements = copy.copy(self._endElements) # already a copy 7539 s = copy.copy(self) 7540 # assign directly to _elements, as we do not need to call 7541 # coreElementsChanged() 7542 s._elements = shallowElements 7543 s._endElements = shallowEndElements 7544 7545 for e in shallowElements + shallowEndElements: 7546 s.coreSetElementOffset(e, self.elementOffset(e), addElement=True) 7547 e.sites.add(s) 7548 # need to explicitly set activeSite 7549 s.coreSelfActiveSite(e) 7550 # now just sort this stream in place; this will update the 7551 # isSorted attribute and sort only if not already sorted 7552 s.sort() 7553 self._cache['sorted'] = s 7554 return s 7555 7556 def flatten(self, retainContainers=False): 7557 ''' 7558 A very important method that returns a new Stream 7559 that has all sub-containers "flattened" within it, 7560 that is, it returns a new Stream where no elements nest within 7561 other elements. 7562 7563 Here is a simple example of the usefulness of .flatten(). We 7564 will create a Score with two Parts in it, each with two Notes: 7565 7566 >>> sc = stream.Score() 7567 >>> p1 = stream.Part() 7568 >>> p1.id = 'part1' 7569 >>> n1 = note.Note('C4') 7570 >>> n2 = note.Note('D4') 7571 >>> p1.append(n1) 7572 >>> p1.append(n2) 7573 7574 >>> p2 = stream.Part() 7575 >>> p2.id = 'part2' 7576 >>> n3 = note.Note('E4') 7577 >>> n4 = note.Note('F4') 7578 >>> p2.append(n3) 7579 >>> p2.append(n4) 7580 7581 >>> sc.insert(0, p1) 7582 >>> sc.insert(0, p2) 7583 7584 When we look at sc, we will see only the two parts: 7585 7586 >>> sc.elements 7587 (<music21.stream.Part part1>, <music21.stream.Part part2>) 7588 7589 We can get at the notes by using the indices of the 7590 stream to get the parts and then looking at the .elements 7591 there: 7592 7593 >>> sc[0].elements 7594 (<music21.note.Note C>, <music21.note.Note D>) 7595 7596 >>> sc.getElementById('part2').elements 7597 (<music21.note.Note E>, <music21.note.Note F>) 7598 7599 ...but if we want to get all the notes, storing their 7600 offsets related to the beginning of the containing stream, 7601 one way 7602 is via calling .flatten() on sc and looking at the elements 7603 there: 7604 7605 >>> sc.flatten().elements 7606 (<music21.note.Note C>, <music21.note.Note E>, 7607 <music21.note.Note D>, <music21.note.Note F>) 7608 7609 Flattening a stream is a great way to get at all the notes in 7610 a larger piece. For instance if we load a four-part 7611 Bach chorale into music21 from the integrated corpus, it 7612 will appear at first that there are no notes in the piece: 7613 7614 >>> bwv66 = corpus.parse('bach/bwv66.6') 7615 >>> len(bwv66.notes) 7616 0 7617 7618 This is because all the notes in the piece lie within :class:`music21.stream.Measure` 7619 objects and those measures lie within :class:`music21.stream.Part` 7620 objects. It'd be a pain to navigate all the way through all those 7621 objects just to count notes. Fortunately we can get a Stream of 7622 all the notes in the piece with .flatten().notes and then use the 7623 length of that Stream to count notes: 7624 7625 >>> bwv66flat = bwv66.flatten() 7626 >>> len(bwv66flat.notes) 7627 165 7628 7629 When, as is commonly the case, we want to find all of the notes, 7630 but do not care to have offsets related to the origin of the stream, 7631 then `.recurse()` is a more efficient way of working: 7632 7633 >>> len(bwv66.recurse().notes) 7634 165 7635 7636 If `retainContainers=True` then a "semiFlat" version of the stream 7637 is returned where Streams are also included in the output stream. 7638 7639 In general you will not need to use this because `.recurse()` is 7640 more efficient and does not lead to problems of the same 7641 object appearing in the hierarchy more than once. 7642 7643 >>> n1 = note.Note('C5') 7644 >>> m1 = stream.Measure([n1], number='1a') 7645 >>> p1 = stream.Part([m1]) 7646 >>> p1.id = 'part1' 7647 7648 >>> n2 = note.Note('D5') 7649 >>> m2 = stream.Measure([n2], number='1b') 7650 >>> p2 = stream.Part([m2]) 7651 >>> p2.id = 'part2' 7652 7653 >>> sc = stream.Score([p1, p2]) 7654 7655 `sf` will be the "semi-flattened" version of the score. 7656 7657 >>> sf = sc.flatten(retainContainers=True) 7658 >>> sf.elements 7659 (<music21.stream.Part part1>, 7660 <music21.stream.Measure 1a offset=0.0>, 7661 <music21.stream.Part part2>, 7662 <music21.stream.Measure 1b offset=0.0>, 7663 <music21.note.Note C>, 7664 <music21.note.Note D>) 7665 >>> sf[0] 7666 <music21.stream.Part part1> 7667 7668 Notice that these all return the same object: 7669 7670 >>> sf[0][0][0] 7671 <music21.note.Note C> 7672 >>> sf[1][0] 7673 <music21.note.Note C> 7674 >>> sf[4] 7675 <music21.note.Note C> 7676 7677 Unless it is important to get iterate in order from 7678 front of score to back of the score, you are generally better off using recurse 7679 instead of `.flatten(retainContainers=True)`, with `.getOffsetInHierarchy()` 7680 to figure out where in the score each element lies. 7681 7682 For instance, this is how we can iterate using recurse(): 7683 7684 >>> for el in sc.recurse(): 7685 ... print(el) 7686 <music21.stream.Part part1> 7687 <music21.stream.Measure 1a offset=0.0> 7688 <music21.note.Note C> 7689 <music21.stream.Part part2> 7690 <music21.stream.Measure 1b offset=0.0> 7691 <music21.note.Note D> 7692 7693 If you look back to our simple example of four notes above, 7694 you can see that the E (the first note in part2) comes before the D 7695 (the second note of part1). This is because the flat stream 7696 is automatically sorted like all streams are by default. The 7697 next example shows how to change this behavior. 7698 7699 >>> s = stream.Stream() 7700 >>> s.autoSort = False 7701 >>> s.repeatInsert(note.Note('C#'), [0, 2, 4]) 7702 >>> s.repeatInsert(note.Note('D-'), [1, 3, 5]) 7703 >>> s.isSorted 7704 False 7705 7706 >>> g = '' 7707 >>> for myElement in s: 7708 ... g += '%s: %s; ' % (myElement.offset, myElement.name) 7709 ... 7710 7711 >>> g 7712 '0.0: C#; 2.0: C#; 4.0: C#; 1.0: D-; 3.0: D-; 5.0: D-; ' 7713 7714 >>> y = s.sorted() 7715 >>> y.isSorted 7716 True 7717 7718 >>> g = '' 7719 >>> for myElement in y: 7720 ... g += '%s: %s; ' % (myElement.offset, myElement.name) 7721 ... 7722 7723 >>> g 7724 '0.0: C#; 1.0: D-; 2.0: C#; 3.0: D-; 4.0: C#; 5.0: D-; ' 7725 7726 >>> q = stream.Stream() 7727 >>> for i in range(5): 7728 ... p = stream.Stream() 7729 ... p.repeatInsert(base.Music21Object(), [0, 1, 2, 3, 4]) 7730 ... q.insert(i * 10, p) 7731 ... 7732 7733 >>> len(q) 7734 5 7735 7736 >>> qf = q.flatten() 7737 >>> len(qf) 7738 25 7739 >>> qf[24].offset 7740 44.0 7741 7742 Note that combining `.flatten(retainContainers=True)` with pure `.flatten()` 7743 can lead to unstable Streams where the same object appears more than once, 7744 in violation of a `music21` lookup rule. 7745 7746 >>> sc.flatten(retainContainers=True).flatten().elements 7747 (<music21.note.Note C>, 7748 <music21.note.Note C>, 7749 <music21.note.Note C>, 7750 <music21.note.Note D>, 7751 <music21.note.Note D>, 7752 <music21.note.Note D>) 7753 7754 OMIT_FROM_DOCS 7755 7756 >>> r = stream.Stream() 7757 >>> for j in range(5): 7758 ... q = stream.Stream() 7759 ... for i in range(5): 7760 ... p = stream.Stream() 7761 ... p.repeatInsert(base.Music21Object(), [0, 1, 2, 3, 4]) 7762 ... q.insert(i * 10, p) 7763 ... r.insert(j * 100, q) 7764 7765 >>> len(r) 7766 5 7767 7768 >>> len(r.flatten()) 7769 125 7770 7771 >>> r.flatten()[124].offset 7772 444.0 7773 ''' 7774 # environLocal.printDebug(['flatten(): self', self, 7775 # 'self.activeSite', self.activeSite]) 7776 if retainContainers: 7777 method = 'semiFlat' 7778 else: 7779 method = 'flat' 7780 7781 cached_version = self._cache.get(method) 7782 if cached_version is not None: 7783 return cached_version 7784 7785 # this copy will have a shared sites object 7786 # note that copy.copy() in some cases seems to not cause secondary 7787 # problems that self.__class__() does 7788 sNew = copy.copy(self) 7789 7790 if sNew.id != id(sNew): 7791 sOldId = sNew.id 7792 if common.isNum(sOldId) and sOldId > defaults.minIdNumberToConsiderMemoryLocation: 7793 sOldId = hex(sOldId) 7794 7795 newId = str(sOldId) + '_' + method 7796 sNew.id = newId 7797 7798 sNew._derivation = derivation.Derivation(sNew) 7799 sNew._derivation.origin = self 7800 sNew.derivation.method = method 7801 # storing .elements in here necessitates 7802 # create a new, independent cache instance in the flat representation 7803 sNew._cache = {} 7804 sNew._offsetDict = {} 7805 sNew._elements = [] 7806 sNew._endElements = [] 7807 sNew.coreElementsChanged() 7808 7809 ri = iterator.RecursiveIterator(self, 7810 restoreActiveSites=False, 7811 includeSelf=False, 7812 ignoreSorting=True, 7813 ) 7814 for e in ri: 7815 if e.isStream and not retainContainers: 7816 continue 7817 sNew.coreInsert(ri.currentHierarchyOffset(), 7818 e, 7819 setActiveSite=False) 7820 if not retainContainers: 7821 sNew.isFlat = True 7822 7823 if self.autoSort is True: 7824 sNew.sort() # sort it immediately so that cache is not invalidated 7825 else: 7826 sNew.coreElementsChanged() 7827 # here, we store the source stream from which this stream was derived 7828 self._cache[method] = sNew 7829 7830 return sNew 7831 7832 @property 7833 def flat(self): 7834 ''' 7835 A property that returns the same flattened representation as `.flatten()` 7836 as of music21 v7. 7837 7838 See :meth:`~music21.stream.base.Stream.flatten()` for documentation. 7839 7840 This property will be deprecated in v8 and removed in v9. 7841 ''' 7842 return self.flatten(retainContainers=False) 7843 7844 @property 7845 def semiFlat(self): 7846 ''' 7847 The same as `.flatten(retainContainers=True)`. This 7848 property should be rarely used, in favor of `.recurse()`, and will 7849 be removed as a property in version 8. 7850 ''' 7851 return self.flatten(retainContainers=True) 7852 7853 def recurse(self, 7854 *, 7855 streamsOnly=False, 7856 restoreActiveSites=True, 7857 classFilter=(), 7858 skipSelf=True, 7859 includeSelf=None): 7860 ''' 7861 `.recurse()` is a fundamental method of music21 for getting into 7862 elements contained in a Score, Part, or Measure, where elements such as 7863 notes are contained in sub-Stream elements. 7864 7865 Returns an iterator that iterates over a list of Music21Objects 7866 contained in the Stream, starting with self's elements (unless 7867 skipSelf=False in which case, it starts with the element itself), 7868 and whenever finding a Stream subclass in self, 7869 that Stream subclass's elements. 7870 7871 Here's an example. Let's create a simple score. 7872 7873 >>> s = stream.Score(id='mainScore') 7874 >>> p0 = stream.Part(id='part0') 7875 >>> p1 = stream.Part(id='part1') 7876 7877 >>> m01 = stream.Measure(number=1) 7878 >>> m01.append(note.Note('C', type='whole')) 7879 >>> m02 = stream.Measure(number=2) 7880 >>> m02.append(note.Note('D', type='whole')) 7881 >>> m11 = stream.Measure(number=1) 7882 >>> m11.append(note.Note('E', type='whole')) 7883 >>> m12 = stream.Measure(number=2) 7884 >>> m12.append(note.Note('F', type='whole')) 7885 7886 >>> p0.append([m01, m02]) 7887 >>> p1.append([m11, m12]) 7888 7889 >>> s.insert(0, p0) 7890 >>> s.insert(0, p1) 7891 >>> s.show('text') 7892 {0.0} <music21.stream.Part part0> 7893 {0.0} <music21.stream.Measure 1 offset=0.0> 7894 {0.0} <music21.note.Note C> 7895 {4.0} <music21.stream.Measure 2 offset=4.0> 7896 {0.0} <music21.note.Note D> 7897 {0.0} <music21.stream.Part part1> 7898 {0.0} <music21.stream.Measure 1 offset=0.0> 7899 {0.0} <music21.note.Note E> 7900 {4.0} <music21.stream.Measure 2 offset=4.0> 7901 {0.0} <music21.note.Note F> 7902 7903 Now we could assign the `.recurse()` method to something, 7904 but that won't have much effect: 7905 7906 >>> sRecurse = s.recurse() 7907 >>> sRecurse 7908 <music21.stream.iterator.RecursiveIterator for Score:mainScore @:0> 7909 7910 So, that's not how we use `.recurse()`. Instead use it in a `for` loop: 7911 7912 >>> for el in s.recurse(): 7913 ... tup = (el, el.offset, el.activeSite) 7914 ... print(tup) 7915 (<music21.stream.Part part0>, 0.0, <music21.stream.Score mainScore>) 7916 (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part0>) 7917 (<music21.note.Note C>, 0.0, <music21.stream.Measure 1 offset=0.0>) 7918 (<music21.stream.Measure 2 offset=4.0>, 4.0, <music21.stream.Part part0>) 7919 (<music21.note.Note D>, 0.0, <music21.stream.Measure 2 offset=4.0>) 7920 (<music21.stream.Part part1>, 0.0, <music21.stream.Score mainScore>) 7921 (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part1>) 7922 (<music21.note.Note E>, 0.0, <music21.stream.Measure 1 offset=0.0>) 7923 (<music21.stream.Measure 2 offset=4.0>, 4.0, <music21.stream.Part part1>) 7924 (<music21.note.Note F>, 0.0, <music21.stream.Measure 2 offset=4.0>) 7925 7926 If we specify `includeSelf=True` then the original stream is also iterated: 7927 7928 >>> for el in s.recurse(includeSelf=True): 7929 ... tup = (el, el.offset, el.activeSite) 7930 ... print(tup) 7931 (<music21.stream.Score mainScore>, 0.0, None) 7932 (<music21.stream.Part part0>, 0.0, <music21.stream.Score mainScore>) 7933 (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part0>) 7934 (<music21.note.Note C>, 0.0, <music21.stream.Measure 1 offset=0.0>) 7935 ... 7936 7937 Notice that like calling `.show('text')`, the offsets are relative to their containers. 7938 7939 Compare the difference between putting `classFilter='Note'` and `.flatten().notes`: 7940 7941 >>> for el in s.recurse(classFilter='Note'): 7942 ... tup = (el, el.offset, el.activeSite) 7943 ... print(tup) 7944 (<music21.note.Note C>, 0.0, <music21.stream.Measure 1 offset=0.0>) 7945 (<music21.note.Note D>, 0.0, <music21.stream.Measure 2 offset=4.0>) 7946 (<music21.note.Note E>, 0.0, <music21.stream.Measure 1 offset=0.0>) 7947 (<music21.note.Note F>, 0.0, <music21.stream.Measure 2 offset=4.0>) 7948 7949 >>> for el in s.flatten().notes: 7950 ... tup = (el, el.offset, el.activeSite) 7951 ... print(tup) 7952 (<music21.note.Note C>, 0.0, <music21.stream.Score mainScore_flat>) 7953 (<music21.note.Note E>, 0.0, <music21.stream.Score mainScore_flat>) 7954 (<music21.note.Note D>, 4.0, <music21.stream.Score mainScore_flat>) 7955 (<music21.note.Note F>, 4.0, <music21.stream.Score mainScore_flat>) 7956 7957 If you don't need correct offsets or activeSites, set `restoreActiveSites` to `False`. 7958 Then the last offset/activeSite will be used. It's a bit of a speedup, but leads to some 7959 bad code, so use it only in highly optimized situations. 7960 7961 We'll also test using multiple classes here... the Stream given is the same as the 7962 s.flatten().notes stream. 7963 7964 >>> for el in s.recurse(classFilter=('Note', 'Rest'), restoreActiveSites=False): 7965 ... tup = (el, el.offset, el.activeSite) 7966 ... print(tup) 7967 (<music21.note.Note C>, 0.0, <music21.stream.Score mainScore_flat>) 7968 (<music21.note.Note D>, 4.0, <music21.stream.Score mainScore_flat>) 7969 (<music21.note.Note E>, 0.0, <music21.stream.Score mainScore_flat>) 7970 (<music21.note.Note F>, 4.0, <music21.stream.Score mainScore_flat>) 7971 7972 So, this is pretty unreliable so don't use it unless the tiny speedup is worth it. 7973 7974 The other two attributes are pretty self-explanatory: `streamsOnly` will put only Streams 7975 in, while `includeSelf` will add the initial stream from recursion. If the inclusion or 7976 exclusion of `self` is important to you, put it in explicitly. 7977 7978 >>> for el in s.recurse(includeSelf=False, streamsOnly=True): 7979 ... tup = (el, el.offset, el.activeSite) 7980 ... print(tup) 7981 (<music21.stream.Part part0>, 0.0, <music21.stream.Score mainScore>) 7982 (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part0>) 7983 (<music21.stream.Measure 2 offset=4.0>, 4.0, <music21.stream.Part part0>) 7984 (<music21.stream.Part part1>, 0.0, <music21.stream.Score mainScore>) 7985 (<music21.stream.Measure 1 offset=0.0>, 0.0, <music21.stream.Part part1>) 7986 (<music21.stream.Measure 2 offset=4.0>, 4.0, <music21.stream.Part part1>) 7987 7988 .. warning:: 7989 7990 Remember that like all iterators, it is dangerous to alter 7991 the components of the Stream being iterated over during iteration. 7992 if you need to edit while recursing, list(s.recurse()) is safer. 7993 7994 Changed in v5.5 -- `skipSelf` is True as promised. All attributes are keyword only. 7995 `includeSelf` is added and now preferred over `skipSelf`. `skipSelf` will be removed 7996 in or after 2022 in v. 7 or v. 8. 7997 ''' 7998 if includeSelf is None: 7999 includeSelf = not skipSelf 8000 ri = iterator.RecursiveIterator(self, 8001 streamsOnly=streamsOnly, 8002 restoreActiveSites=restoreActiveSites, 8003 includeSelf=includeSelf 8004 ) 8005 if classFilter: 8006 ri.addFilter(filters.ClassFilter(classFilter), returnClone=False) 8007 return ri 8008 8009 def containerInHierarchy(self, el, *, setActiveSite=True) -> Optional['music21.stream.Stream']: 8010 ''' 8011 Returns the container in a hierarchy that this element belongs to. 8012 8013 For instance, assume a Note (n) is in a Measure (m1) which is in a Part, in a Score (s1), 8014 and the Note is also in another hierarchy (say, a chordified version of a Score, s2). 8015 if s1.containerInHierarchy(n) is called, it will return m1, 8016 the Measure that contains the note. 8017 8018 Unless `setActiveSite` is False, n's activeSite will be set to m1, and 8019 its `.offset` will be the offset in `m1`. 8020 8021 >>> s1 = stream.Score(id='s1') 8022 >>> p1 = stream.Part() 8023 >>> m1 = stream.Measure(id='m1') 8024 >>> n = note.Note('D') 8025 >>> m1.append(n) 8026 >>> p1.append(m1) 8027 >>> s1.insert(0, p1) 8028 >>> s2 = stream.Stream(id='s2') 8029 >>> s2.append(n) 8030 >>> n.activeSite.id 8031 's2' 8032 >>> s1.containerInHierarchy(n).id 8033 'm1' 8034 >>> n.activeSite.id 8035 'm1' 8036 >>> n.activeSite = s2 8037 >>> s1.containerInHierarchy(n, setActiveSite=False).id 8038 'm1' 8039 >>> n.activeSite.id 8040 's2' 8041 8042 If the element cannot be found in the hierarchy then None is returned. 8043 8044 >>> s3 = stream.Stream() 8045 >>> s3.containerInHierarchy(n) is None 8046 True 8047 ''' 8048 elSites = el.sites 8049 for s in self.recurse(includeSelf=True, streamsOnly=True, restoreActiveSites=False): 8050 if s in elSites: 8051 if setActiveSite: 8052 s.coreSelfActiveSite(el) 8053 return s 8054 return None 8055 8056 def makeImmutable(self): 8057 ''' 8058 Clean this Stream: for self and all elements, purge all dead locations 8059 and remove all non-contained sites. Further, restore all active sites. 8060 ''' 8061 if self._mutable is not False: 8062 self.sort() # must sort before making immutable 8063 for e in self.recurse(streamsOnly=True, includeSelf=False): 8064 # e.purgeLocations(rescanIsDead=True) 8065 # NOTE: calling this method was having the side effect of removing 8066 # sites from locations when a Note was both in a Stream and in 8067 # an Interval 8068 if e.isStream: 8069 e.sort() # sort before making immutable 8070 e._mutable = False 8071 self._mutable = False 8072 8073 def makeMutable(self, recurse=True): 8074 self._mutable = True 8075 if recurse: 8076 for e in self.recurse(streamsOnly=True): 8077 # do not recurse, as will get all Stream 8078 e.makeMutable(recurse=False) 8079 self.coreElementsChanged() 8080 8081 # -------------------------------------------------------------------------- 8082 # duration and offset methods and properties 8083 8084 @property 8085 def highestOffset(self): 8086 ''' 8087 Get start time of element with the highest offset in the Stream. 8088 Note the difference between this property and highestTime 8089 which gets the end time of the highestOffset 8090 8091 >>> stream1 = stream.Stream() 8092 >>> for offset in [0, 4, 8]: 8093 ... n = note.Note('G#', type='whole') 8094 ... stream1.insert(offset, n) 8095 >>> stream1.highestOffset 8096 8.0 8097 >>> stream1.highestTime 8098 12.0 8099 ''' 8100 # TODO: Perfect timespans candidate 8101 if 'HighestOffset' in self._cache and self._cache['HighestOffset'] is not None: 8102 pass # return cache unaltered 8103 elif not self._elements: 8104 self._cache['HighestOffset'] = 0.0 8105 elif self.isSorted is True: 8106 eLast = self._elements[-1] 8107 self._cache['HighestOffset'] = self.elementOffset(eLast) 8108 else: # iterate through all elements 8109 highestOffsetSoFar = None 8110 for e in self._elements: 8111 candidateOffset = self.elementOffset(e) 8112 if highestOffsetSoFar is None or candidateOffset > highestOffsetSoFar: 8113 highestOffsetSoFar = candidateOffset 8114 8115 if highestOffsetSoFar is not None: 8116 self._cache['HighestOffset'] = float(highestOffsetSoFar) 8117 else: 8118 self._cache['HighestOffset'] = None 8119 return self._cache['HighestOffset'] 8120 8121 def _setHighestTime(self, value): 8122 '''For internal use only. 8123 ''' 8124 self._cache['HighestTime'] = value 8125 8126 @property 8127 def highestTime(self): 8128 ''' 8129 Returns the maximum of all Element offsets plus their Duration 8130 in quarter lengths. This value usually represents the last 8131 "release" in the Stream. 8132 8133 Stream.duration is usually equal to the highestTime 8134 expressed as a Duration object, but it can be set separately 8135 for advanced operations. 8136 8137 Example: Insert a dotted half note at position 0 and see where 8138 it cuts off: 8139 8140 >>> p1 = stream.Stream() 8141 >>> p1.highestTime 8142 0.0 8143 8144 >>> n = note.Note('A-') 8145 >>> n.quarterLength = 3 8146 >>> p1.insert(0, n) 8147 >>> p1.highestTime 8148 3.0 8149 8150 Now insert in the same stream, the dotted half note 8151 at positions 1, 2, 3, 4 and see when the final note cuts off: 8152 8153 >>> p1.repeatInsert(n, [1, 2, 3, 4]) 8154 >>> p1.highestTime 8155 7.0 8156 8157 Another example. 8158 8159 >>> n = note.Note('C#') 8160 >>> n.quarterLength = 3 8161 8162 >>> q = stream.Stream() 8163 >>> for i in [20, 0, 10, 30, 40]: 8164 ... p = stream.Stream() 8165 ... p.repeatInsert(n, [0, 1, 2, 3, 4]) 8166 ... q.insert(i, p) # insert out of order 8167 >>> len(q.flatten()) 8168 25 8169 >>> q.highestTime # this works b/c the component Stream has an duration 8170 47.0 8171 >>> r = q.flatten() 8172 8173 Changed in v6.5 -- highestTime can return a Fraction. 8174 8175 8176 OMIT_FROM_DOCS 8177 8178 Make sure that the cache really is empty 8179 >>> 'HighestTime' in r._cache 8180 False 8181 >>> r.highestTime # 44 + 3 8182 47.0 8183 8184 Can highestTime be a fraction? 8185 8186 >>> n = note.Note('D') 8187 >>> n.duration.quarterLength = 1/3 8188 >>> n.duration.quarterLength 8189 Fraction(1, 3) 8190 8191 >>> s = stream.Stream() 8192 >>> s.append(note.Note('C')) 8193 >>> s.append(n) 8194 >>> s.highestTime 8195 Fraction(4, 3) 8196 ''' 8197 # TODO(msc) -- why is cache 'HighestTime' and not 'highestTime'? 8198 # environLocal.printDebug(['_getHighestTime', 'isSorted', self.isSorted, self]) 8199 8200 # remove cache -- durations might change... 8201 if 'HighestTime' in self._cache and self._cache['HighestTime'] is not None: 8202 pass # return cache unaltered 8203 elif not self._elements: 8204 # _endElements does not matter here, since ql > 0 on endElements not allowed. 8205 self._cache['HighestTime'] = 0.0 8206 return 0.0 8207 else: 8208 highestTimeSoFar = 0.0 8209 # TODO: optimize for a faster way of doing this. 8210 # but cannot simply look at the last element even if isSorted 8211 # because what if the penultimate 8212 # element, with a 8213 # lower offset has a longer duration than the last? 8214 # Take the case where a whole note appears a 0.0, but a 8215 # textExpression (ql=0) at 0.25 -- 8216 # isSorted would be true, but highestTime should be 4.0 not 0.25 8217 for e in self._elements: 8218 candidateOffset = (self.elementOffset(e) 8219 + e.duration.quarterLength) 8220 if candidateOffset > highestTimeSoFar: 8221 highestTimeSoFar = candidateOffset 8222 self._cache['HighestTime'] = opFrac(highestTimeSoFar) 8223 return self._cache['HighestTime'] 8224 8225 @property 8226 def lowestOffset(self): 8227 ''' 8228 Get the start time of the Element with the lowest offset in the Stream. 8229 8230 >>> stream1 = stream.Stream() 8231 >>> for x in range(3, 5): 8232 ... n = note.Note('G#') 8233 ... stream1.insert(x, n) 8234 ... 8235 >>> stream1.lowestOffset 8236 3.0 8237 8238 8239 If the Stream is empty, then the lowest offset is 0.0: 8240 8241 8242 >>> stream2 = stream.Stream() 8243 >>> stream2.lowestOffset 8244 0.0 8245 8246 8247 8248 >>> p = stream.Stream() 8249 >>> p.repeatInsert(note.Note('D5'), [0, 1, 2, 3, 4]) 8250 >>> q = stream.Stream() 8251 >>> q.repeatInsert(p, list(range(0, 50, 10))) 8252 >>> len(q.flatten()) 8253 25 8254 >>> q.lowestOffset 8255 0.0 8256 >>> r = stream.Stream() 8257 >>> r.repeatInsert(q, list(range(97, 500, 100))) 8258 >>> len(r.flatten()) 8259 125 8260 >>> r.lowestOffset 8261 97.0 8262 ''' 8263 if 'lowestOffset' in self._cache and self._cache['LowestOffset'] is not None: 8264 pass # return cache unaltered 8265 elif not self._elements: 8266 self._cache['LowestOffset'] = 0.0 8267 elif self.isSorted is True: 8268 eFirst = self._elements[0] 8269 self._cache['LowestOffset'] = self.elementOffset(eFirst) 8270 else: # iterate through all elements 8271 minOffsetSoFar = None 8272 for e in self._elements: 8273 candidateOffset = self.elementOffset(e) 8274 if minOffsetSoFar is None or candidateOffset < minOffsetSoFar: 8275 minOffsetSoFar = candidateOffset 8276 self._cache['LowestOffset'] = minOffsetSoFar 8277 8278 # environLocal.printDebug(['_getLowestOffset: iterated elements', min]) 8279 8280 return self._cache['LowestOffset'] 8281 8282 def _getDuration(self): 8283 ''' 8284 Gets the duration of the whole stream (generally the highestTime, but can be set 8285 independently). 8286 ''' 8287 if self._unlinkedDuration is not None: 8288 return self._unlinkedDuration 8289 elif 'Duration' in self._cache and self._cache['Duration'] is not None: 8290 # environLocal.printDebug(['returning cached duration']) 8291 return self._cache['Duration'] 8292 else: 8293 # environLocal.printDebug(['creating new duration based on highest time']) 8294 self._cache['Duration'] = duration.Duration(quarterLength=self.highestTime) 8295 return self._cache['Duration'] 8296 8297 def _setDuration(self, durationObj): 8298 ''' 8299 Set the total duration of the Stream independently of the highestTime 8300 of the stream. Useful to define the scope of the stream as independent 8301 of its constituted elements. 8302 8303 If set to None, then the default behavior of computing automatically from 8304 highestTime is reestablished. 8305 ''' 8306 if isinstance(durationObj, duration.Duration): 8307 self._unlinkedDuration = durationObj 8308 elif durationObj is None: 8309 self._unlinkedDuration = None 8310 else: # need to permit Duration object assignment here 8311 raise StreamException(f'this must be a Duration object, not {durationObj}') 8312 8313 duration = property(_getDuration, _setDuration, doc=''' 8314 Returns the total duration of the Stream, from the beginning of the 8315 stream until the end of the final element. 8316 May be set independently by supplying a Duration object. 8317 8318 >>> a = stream.Stream() 8319 >>> q = note.Note(type='quarter') 8320 >>> a.repeatInsert(q, [0, 1, 2, 3]) 8321 >>> a.highestOffset 8322 3.0 8323 >>> a.highestTime 8324 4.0 8325 >>> a.duration 8326 <music21.duration.Duration 4.0> 8327 >>> a.duration.quarterLength 8328 4.0 8329 8330 8331 Advanced usage: override the duration from what is set: 8332 8333 >>> newDuration = duration.Duration('half') 8334 >>> newDuration.quarterLength 8335 2.0 8336 8337 >>> a.duration = newDuration 8338 >>> a.duration.quarterLength 8339 2.0 8340 8341 Restore normal behavior by setting duration to None: 8342 8343 >>> a.duration = None 8344 >>> a.duration 8345 <music21.duration.Duration 4.0> 8346 8347 Note that the highestTime for the stream is the same 8348 whether duration is overridden or not: 8349 8350 >>> a.highestTime 8351 4.0 8352 ''') 8353 8354 def _setSeconds(self, value): 8355 pass 8356 8357 def _getSeconds(self): 8358 getTempoFromContext = False 8359 # need to find all tempo indications and the number of quarter lengths 8360 # under each 8361 tiStream = self.getElementsByClass('TempoIndication') 8362 offsetMetronomeMarkPairs = [] 8363 8364 if not tiStream: 8365 getTempoFromContext = True 8366 else: 8367 for ti in tiStream: 8368 o = self.elementOffset(ti) 8369 # get the desired metronome mark from any of ti classes 8370 mm = ti.getSoundingMetronomeMark() 8371 offsetMetronomeMarkPairs.append([o, mm]) 8372 8373 for i, (o, mm) in enumerate(offsetMetronomeMarkPairs): 8374 if i == 0 and o > 0.0: 8375 getTempoFromContext = True 8376 break # just need first 8377 8378 if getTempoFromContext: 8379 ti = self.getContextByClass('TempoIndication') 8380 if ti is None: 8381 if self.highestTime != 0.0: 8382 return float('nan') 8383 else: 8384 return 0.0 8385 # insert at zero offset position, even though coming from 8386 # outside this stream 8387 mm = ti.getSoundingMetronomeMark() 8388 offsetMetronomeMarkPairs = [[0.0, mm]] + offsetMetronomeMarkPairs 8389 sec = 0.0 8390 for i, (o, mm) in enumerate(offsetMetronomeMarkPairs): 8391 # handle only one mm right away 8392 if len(offsetMetronomeMarkPairs) == 1: 8393 sec += mm.durationToSeconds(self.highestTime) 8394 break 8395 oStart, mmStart = o, mm 8396 # if not the last ti, get the next by index to get the offset 8397 if i < len(offsetMetronomeMarkPairs) - 1: 8398 # cases of two or more remain 8399 oEnd, unused_mmEnd = offsetMetronomeMarkPairs[i + 1] 8400 else: # at the last 8401 oEnd = self.highestTime 8402 sec += mmStart.durationToSeconds(oEnd - oStart) 8403 8404 return sec 8405 8406 seconds = property(_getSeconds, _setSeconds, doc=''' 8407 Get or set the duration of this Stream in seconds, assuming that 8408 this object contains a :class:`~music21.tempo.MetronomeMark` or 8409 :class:`~music21.tempo.MetricModulation`. 8410 8411 >>> s = corpus.parse('bwv66.6') # piece without a tempo 8412 >>> sFlat = s.flatten() 8413 >>> t = tempo.MetronomeMark('adagio') 8414 >>> sFlat.insert(0, t) 8415 >>> sFlat.seconds 8416 38.57142857... 8417 >>> tFast = tempo.MetronomeMark('allegro') 8418 >>> sFlat.replace(t, tFast) 8419 >>> sFlat.seconds 8420 16.363... 8421 8422 Setting seconds on streams is not supported. Ideally it would instead 8423 scale all elements to fit, but this is a long way off. 8424 8425 If a stream does not have a tempo-indication in it then the property 8426 returns 0.0 if an empty Stream (or self.highestTime is 0.0) or 'nan' 8427 if there are non-zero duration objects in the stream: 8428 8429 >>> s = stream.Stream() 8430 >>> s.seconds 8431 0.0 8432 >>> s.insert(0, clef.TrebleClef()) 8433 >>> s.seconds 8434 0.0 8435 >>> s.append(note.Note(type='half')) 8436 >>> s.seconds 8437 nan 8438 >>> import math 8439 >>> math.isnan(s.seconds) 8440 True 8441 8442 Changed in v6.3 -- return nan rather than raising an exception. Do not 8443 attempt to change seconds on a stream, as it did not do what you would expect. 8444 ''') 8445 8446 def metronomeMarkBoundaries(self, srcObj=None): 8447 ''' 8448 Return a list of offset start, offset end, 8449 MetronomeMark triples for all TempoIndication objects found 8450 in this Stream or a Stream provided by srcObj. 8451 8452 If no MetronomeMarks are found, or an initial region does not 8453 have a MetronomeMark, a mark of quarter equal to 120 is provided as default. 8454 8455 Note that if other TempoIndication objects are defined, 8456 they will be converted to MetronomeMarks and returned here 8457 8458 8459 >>> s = stream.Stream() 8460 >>> s.repeatAppend(note.Note(), 8) 8461 >>> s.insert([6, tempo.MetronomeMark(number=240)]) 8462 >>> s.metronomeMarkBoundaries() 8463 [(0.0, 6.0, <music21.tempo.MetronomeMark animato Quarter=120>), 8464 (6.0, 8.0, <music21.tempo.MetronomeMark Quarter=240>)] 8465 ''' 8466 if srcObj is None: 8467 srcObj = self 8468 # get all tempo indications from the flat Stream; 8469 # using flat here may not always be desirable 8470 # may want to do a recursive upward search as well 8471 srcFlat = srcObj.flatten() 8472 tiStream = srcFlat.getElementsByClass('TempoIndication') 8473 mmBoundaries = [] # a list of (start, end, mm) 8474 8475 # not sure if this should be taken from the flat representation 8476 highestTime = srcFlat.highestTime 8477 lowestOffset = srcFlat.lowestOffset 8478 8479 # if no tempo 8480 if not tiStream: 8481 mmDefault = tempo.MetronomeMark(number=120) # a default 8482 mmBoundaries.append((lowestOffset, highestTime, mmDefault)) 8483 # if just one tempo 8484 elif len(tiStream) == 1: 8485 # get offset from flat source 8486 o = tiStream[0].getOffsetBySite(srcFlat) 8487 mm = tiStream[0].getSoundingMetronomeMark() 8488 if o > lowestOffset: 8489 mmDefault = tempo.MetronomeMark(number=120) # a default 8490 mmBoundaries.append((lowestOffset, o, mmDefault)) 8491 mmBoundaries.append((o, highestTime, mm)) 8492 else: 8493 mmBoundaries.append((lowestOffset, highestTime, mm)) 8494 # one or more tempi 8495 else: 8496 offsetPairs = [] 8497 for ti in tiStream: 8498 o = ti.getOffsetBySite(srcFlat) 8499 offsetPairs.append([o, ti.getSoundingMetronomeMark()]) 8500 # fill boundaries 8501 # if lowest region not defined, supply as default 8502 if offsetPairs[0][0] > lowestOffset: 8503 mmDefault = tempo.MetronomeMark(number=120) # a default 8504 mmBoundaries.append((lowestOffset, offsetPairs[0][0], 8505 mmDefault)) 8506 mmBoundaries.append((offsetPairs[0][0], offsetPairs[1][0], 8507 offsetPairs[0][1])) 8508 else: # just add the first range 8509 mmBoundaries.append((offsetPairs[0][0], offsetPairs[1][0], 8510 offsetPairs[0][1])) 8511 # add any remaining ranges, starting from the second; if last, 8512 # use the highest time as the boundary 8513 for i, (o, mm) in enumerate(offsetPairs): 8514 if i == 0: 8515 continue # already added first 8516 elif i == len(offsetPairs) - 1: # last index 8517 mmBoundaries.append((offsetPairs[i][0], highestTime, 8518 offsetPairs[i][1])) 8519 else: # add with next boundary 8520 mmBoundaries.append((offsetPairs[i][0], offsetPairs[i + 1][0], 8521 offsetPairs[i][1])) 8522 8523 # environLocal.printDebug(['self.metronomeMarkBoundaries()', 8524 # 'got mmBoundaries:', mmBoundaries]) 8525 return mmBoundaries 8526 8527 def _accumulatedSeconds(self, mmBoundaries, oStart, oEnd): 8528 ''' 8529 Given MetronomeMark boundaries, for any pair of offsets, 8530 determine the realized duration in seconds. 8531 ''' 8532 # assume tt mmBoundaries are in order 8533 totalSeconds = 0.0 8534 activeStart = oStart 8535 activeEnd = None 8536 for s, e, mm in mmBoundaries: 8537 if s <= activeStart < e: 8538 # find time in this region 8539 if oEnd < e: # if end within this region 8540 activeEnd = oEnd 8541 else: # if end after this region 8542 activeEnd = e 8543 # environLocal.printDebug(['activeStart', activeStart, 8544 # 'activeEnd', activeEnd, 's, e, mm', s, e, mm]) 8545 totalSeconds += mm.durationToSeconds(activeEnd - activeStart) 8546 else: 8547 continue 8548 if activeEnd == oEnd: 8549 break 8550 else: # continue on 8551 activeStart = activeEnd 8552 return totalSeconds 8553 8554 def _getSecondsMap(self, srcObj=None): 8555 ''' 8556 Return a list of dictionaries for all elements in this Stream, 8557 where each dictionary defines the real-time characteristics of 8558 the stored events. This will attempt to find 8559 all :class:`~music21.tempo.TempoIndication` subclasses and use these 8560 values to realize tempi. If no initial tempo is found, 8561 a tempo of 120 BPM will be provided. 8562 ''' 8563 if srcObj is None: 8564 srcObj = self 8565 mmBoundaries = self.metronomeMarkBoundaries(srcObj=srcObj) 8566 8567 # not sure if this should be taken from the flat representation 8568 lowestOffset = srcObj.lowestOffset 8569 8570 secondsMap = [] # list of start, start+dur, element 8571 if srcObj.hasVoices(): 8572 groups = [] 8573 for i, v in enumerate(srcObj.voices): 8574 groups.append((v.flatten(), i)) 8575 else: # create a single collection 8576 groups = [(srcObj, None)] 8577 8578 # get accumulated time over many possible tempo changes for 8579 # start/end offset 8580 for group, voiceIndex in groups: 8581 for e in group: 8582 if isinstance(e, bar.Barline): 8583 continue 8584 dur = e.duration.quarterLength 8585 offset = round(e.getOffsetBySite(group), 8) 8586 # calculate all time regions given this offset 8587 8588 # all stored values are seconds 8589 # noinspection PyDictCreation 8590 secondsDict = {} 8591 secondsDict['offsetSeconds'] = srcObj._accumulatedSeconds( 8592 mmBoundaries, lowestOffset, offset) 8593 secondsDict['durationSeconds'] = srcObj._accumulatedSeconds( 8594 mmBoundaries, offset, offset + dur) 8595 secondsDict['endTimeSeconds'] = (secondsDict['offsetSeconds'] 8596 + secondsDict['durationSeconds']) 8597 secondsDict['element'] = e 8598 secondsDict['voiceIndex'] = voiceIndex 8599 secondsMap.append(secondsDict) 8600 return secondsMap 8601 8602 # do not make a property decorator since _getSecondsMap takes arguments 8603 secondsMap = property(_getSecondsMap, doc=''' 8604 Returns a list where each element is a dictionary 8605 consisting of the 'offsetSeconds' in seconds of each element in a Stream, 8606 the 'duration' in seconds, the 'endTimeSeconds' in seconds (that is, the offset 8607 plus the duration), and the 'element' itself. Also contains a 'voiceIndex' entry which 8608 contains the voice number of the element, or None if there 8609 are no voices. 8610 8611 8612 >>> mm1 = tempo.MetronomeMark(number=120) 8613 >>> n1 = note.Note(type='quarter') 8614 >>> c1 = clef.AltoClef() 8615 >>> n2 = note.Note(type='half') 8616 >>> s1 = stream.Stream() 8617 >>> s1.append([mm1, n1, c1, n2]) 8618 >>> om = s1.secondsMap 8619 >>> om[3]['offsetSeconds'] 8620 0.5 8621 >>> om[3]['endTimeSeconds'] 8622 1.5 8623 >>> om[3]['element'] is n2 8624 True 8625 >>> om[3]['voiceIndex'] 8626 ''') 8627 8628 # -------------------------------------------------------------------------- 8629 # Metadata access 8630 8631 def _getMetadata(self): 8632 ''' 8633 8634 8635 >>> a = stream.Stream() 8636 >>> a.metadata = metadata.Metadata() 8637 ''' 8638 mdList = self.getElementsByClass('Metadata') 8639 # only return metadata that has an offset = 0.0 8640 mdList = mdList.getElementsByOffset(0) 8641 return mdList.first() 8642 8643 def _setMetadata(self, metadataObj): 8644 ''' 8645 8646 >>> a = stream.Stream() 8647 >>> a.metadata = metadata.Metadata() 8648 ''' 8649 oldMetadata = self._getMetadata() 8650 if oldMetadata is not None: 8651 # environLocal.printDebug(['removing old metadata', oldMetadata]) 8652 junk = self.pop(self.index(oldMetadata)) 8653 8654 if metadataObj is not None and isinstance(metadataObj, metadata.Metadata): 8655 self.insert(0, metadataObj) 8656 8657 metadata = property(_getMetadata, _setMetadata, 8658 doc=''' 8659 Get or set the :class:`~music21.metadata.Metadata` object 8660 found at the beginning (offset 0) of this Stream. 8661 8662 >>> s = stream.Stream() 8663 >>> s.metadata = metadata.Metadata() 8664 >>> s.metadata.composer = 'frank' 8665 >>> s.metadata.composer 8666 'frank' 8667 ''') 8668 8669 # -------------------------------------------------------------------------- 8670 # these methods override the behavior inherited from base.py 8671 8672 def _getMeasureOffset(self): 8673 # this normally returns the offset of this object in its container 8674 # for now, simply return the offset for the activeSite 8675 return self.getOffsetBySite(self.activeSite) 8676 8677 @property 8678 def beat(self): 8679 ''' 8680 beat returns None for a Stream. 8681 ''' 8682 # this normally returns the beat within a measure; here, it could 8683 # be beats from the beginning? 8684 return None 8685 8686 @property 8687 def beatStr(self): 8688 ''' 8689 unlike other Music21Objects, streams always have beatStr (beat string) of None 8690 8691 May change to '' soon. 8692 ''' 8693 return None 8694 8695 @property 8696 def beatDuration(self): 8697 ''' 8698 unlike other Music21Objects, streams always have beatDuration of None 8699 ''' 8700 # this returns the duration of the active beat 8701 return None 8702 8703 @property 8704 def beatStrength(self): 8705 ''' 8706 unlike other Music21Objects, streams always have beatStrength of None 8707 ''' 8708 # this returns the accent weight of the active beat 8709 return None 8710 8711 def beatAndMeasureFromOffset(self, searchOffset, fixZeros=True): 8712 ''' 8713 Returns a two-element tuple of the beat and the Measure object (or the first one 8714 if there are several at the same offset; unlikely but possible) for a given 8715 offset from the start of this Stream (that contains measures). 8716 8717 Recursively searches for measures. Note that this method assumes that all parts 8718 have measures of consistent length. If that's not the case, this 8719 method can be called on the relevant part. 8720 8721 This algorithm should work even for weird time signatures such as 2+3+2/8. 8722 8723 8724 >>> bach = corpus.parse('bach/bwv1.6') 8725 >>> bach.parts[0].measure(2).getContextByClass('TimeSignature') 8726 <music21.meter.TimeSignature 4/4> 8727 >>> returnTuples = [] 8728 >>> for offset in [0.0, 1.0, 2.0, 5.0, 5.5]: 8729 ... returnTuples.append(bach.beatAndMeasureFromOffset(offset)) 8730 >>> returnTuples 8731 [(4.0, <music21.stream.Measure 0 offset=0.0>), 8732 (1.0, <music21.stream.Measure 1 offset=1.0>), 8733 (2.0, <music21.stream.Measure 1 offset=1.0>), 8734 (1.0, <music21.stream.Measure 2 offset=5.0>), 8735 (1.5, <music21.stream.Measure 2 offset=5.0>)] 8736 8737 To get just the measureNumber and beat, use a transformation like this: 8738 >>> [(beat, measureObj.number) for beat, measureObj in returnTuples] 8739 [(4.0, 0), (1.0, 1), (2.0, 1), (1.0, 2), (1.5, 2)] 8740 8741 Adapted from contributed code by Dmitri Tymoczko. With thanks to DT. 8742 ''' 8743 myStream = self 8744 if not myStream.hasMeasures(): 8745 if myStream.hasPartLikeStreams(): 8746 foundPart = False 8747 for subStream in myStream: 8748 if not subStream.isStream: 8749 continue 8750 if subStream.hasMeasures(): 8751 foundPart = True 8752 myStream = subStream 8753 break 8754 if not foundPart: 8755 raise StreamException('beatAndMeasureFromOffset: could not find any parts!') 8756 # was return False 8757 else: 8758 if not myStream.hasMeasures(): 8759 raise StreamException('beatAndMeasureFromOffset: could not find any measures!') 8760 # return False 8761 # Now we get the measure containing our offset. 8762 # In most cases this second part of the code does the job. 8763 myMeas = myStream.getElementAtOrBefore(searchOffset, classList=['Measure']) 8764 if myMeas is None: 8765 raise StreamException('beatAndMeasureFromOffset: no measure at that offset.') 8766 ts1 = myMeas.timeSignature 8767 if ts1 is None: 8768 ts1 = myMeas.getContextByClass('TimeSignature') 8769 8770 if ts1 is None: 8771 raise StreamException( 8772 'beatAndMeasureFromOffset: could not find a time signature for that place.') 8773 try: 8774 myBeat = ts1.getBeatProportion(searchOffset - myMeas.offset) 8775 except: 8776 raise StreamException( 8777 'beatAndMeasureFromOffset: offset is beyond the end of the piece') 8778 foundMeasureNumber = myMeas.number 8779 # deal with second half of partial measures... 8780 8781 # Now we deal with the problem case, where we have the second half of a partial measure. 8782 # These are 8783 # treated as unnumbered measures (or measures with suffix 'X') by notation programs, 8784 # even though they are 8785 # logically part of the previous measure. 8786 # The variable padBeats will represent extra beats we add to the front 8787 # of our partial measure 8788 numSuffix = myMeas.numberSuffix 8789 if numSuffix == '': 8790 numSuffix = None 8791 8792 if numSuffix is not None or (fixZeros and foundMeasureNumber == 0): 8793 prevMeas = myStream.getElementBeforeOffset(myMeas.offset, classList=['Measure']) 8794 if prevMeas: 8795 ts2 = prevMeas.getContextByClass('TimeSignature') 8796 if not ts2: 8797 raise StreamException( 8798 'beatAndMeasureFromOffset: partial measure found, ' 8799 + 'but could not find a time signature for the preceding measure') 8800 # foundMeasureNumber = prevMeas.number 8801 8802 # need this for chorales 197 and 280, where we 8803 # have a full-length measure followed by a pickup in 8804 # a new time signature 8805 if prevMeas.highestTime == ts2.barDuration.quarterLength: 8806 padBeats = ts2.beatCount 8807 else: 8808 padBeats = ts2.getBeatProportion(prevMeas.highestTime) - 1 8809 return (myBeat + padBeats, prevMeas) 8810 else: 8811 # partial measure at start of piece 8812 padBeats = ts1.getBeatProportion( 8813 ts1.barDuration.quarterLength - myMeas.duration.quarterLength) - 1 8814 return (myBeat + padBeats, myMeas) 8815 else: 8816 return (myBeat, myMeas) 8817 8818 # -------------------------------------------------------------------------- 8819 # transformations 8820 8821 def transpose( 8822 self, 8823 value, 8824 inPlace=False, 8825 recurse=True, 8826 classFilterList=None 8827 ): 8828 # noinspection PyShadowingNames 8829 ''' 8830 Transpose all specified classes in the 8831 Stream by the 8832 user-provided value. If the value is an integer, the 8833 transposition is treated in half steps. If the value is 8834 a string, any Interval string specification can be 8835 provided. 8836 8837 returns a new Stream by default, but if the 8838 optional "inPlace" key is set to True then 8839 it modifies pitches in place. 8840 8841 TODO: for generic interval set accidental by key signature. 8842 8843 >>> aInterval = interval.Interval('d5') 8844 8845 >>> aStream = corpus.parse('bach/bwv324.xml') 8846 >>> part = aStream.parts[0] 8847 >>> [str(p) for p in aStream.parts[0].pitches[:10]] 8848 ['B4', 'D5', 'B4', 'B4', 'B4', 'B4', 'C5', 'B4', 'A4', 'A4'] 8849 8850 >>> bStream = aStream.parts[0].flatten().transpose('d5') 8851 >>> [str(p) for p in bStream.pitches[:10]] 8852 ['F5', 'A-5', 'F5', 'F5', 'F5', 'F5', 'G-5', 'F5', 'E-5', 'E-5'] 8853 8854 Test that aStream hasn't been changed: 8855 8856 >>> [str(p) for p in aStream.parts[0].pitches[:10]] 8857 ['B4', 'D5', 'B4', 'B4', 'B4', 'B4', 'C5', 'B4', 'A4', 'A4'] 8858 8859 >>> cStream = bStream.flatten().transpose('a4') 8860 >>> [str(p) for p in cStream.pitches[:10]] 8861 ['B5', 'D6', 'B5', 'B5', 'B5', 'B5', 'C6', 'B5', 'A5', 'A5'] 8862 8863 >>> cStream.flatten().transpose(aInterval, inPlace=True) 8864 >>> [str(p) for p in cStream.pitches[:10]] 8865 ['F6', 'A-6', 'F6', 'F6', 'F6', 'F6', 'G-6', 'F6', 'E-6', 'E-6'] 8866 ''' 8867 # only change the copy 8868 if not inPlace: 8869 post = self.coreCopyAsDerivation('transpose') 8870 else: 8871 post = self 8872 # for p in post.pitches: # includes chords 8873 # # do inplace transpositions on the deepcopy 8874 # p.transpose(value, inPlace=True) 8875 # 8876 # for e in post.getElementsByClass(classFilterList=classFilterList): 8877 # e.transpose(value, inPlace=True) 8878 8879 # this will get all elements at this level and downward. 8880 if recurse is True: 8881 sIterator = post.recurse() 8882 else: 8883 sIterator = post.__iter__() 8884 8885 if classFilterList: 8886 sIterator = sIterator.addFilter(filters.ClassFilter(classFilterList)) 8887 8888 for e in sIterator: 8889 if e.isStream: 8890 continue 8891 if hasattr(e, 'transpose'): 8892 if (hasattr(value, 'classes') 8893 and 'GenericInterval' in value.classes): 8894 # do not transpose KeySignatures w/ Generic Intervals 8895 if not isinstance(e, key.KeySignature) and hasattr(e, 'pitches'): 8896 k = e.getContextByClass('KeySignature') 8897 for p in e.pitches: 8898 value.transposePitchKeyAware(p, k, inPlace=True) 8899 else: 8900 e.transpose(value, inPlace=True) 8901 if not inPlace: 8902 return post 8903 else: 8904 return None 8905 8906 def scaleOffsets(self, amountToScale, *, anchorZero='lowest', 8907 anchorZeroRecurse=None, inPlace=False): 8908 ''' 8909 Scale all offsets by a multiplication factor given 8910 in `amountToScale`. Durations are not altered. 8911 8912 To augment or diminish a Stream, 8913 see the :meth:`~music21.stream.Stream.augmentOrDiminish` method. 8914 8915 The `anchorZero` parameter determines if and/or where 8916 the zero offset is established for the set of 8917 offsets in this Stream before processing. 8918 Offsets are shifted to make either the lower 8919 or upper values the new zero; then offsets are scaled; 8920 then the shifts are removed. Accepted values are None 8921 (no offset shifting), "lowest", or "highest". 8922 8923 The `anchorZeroRecurse` parameter determines the 8924 anchorZero for all embedded Streams, and Streams 8925 embedded within those Streams. If the lowest offset 8926 in an embedded Stream is non-zero, setting this value 8927 to None will a the space between the start of that 8928 Stream and the first element to be scaled. If the 8929 lowest offset in an embedded Stream is non-zero, 8930 setting this value to 'lowest' will not alter the 8931 space between the start of that Stream and the first 8932 element to be scaled. 8933 8934 To shift all the elements in a Stream, see the 8935 :meth:`~music21.stream.Stream.shiftElements` method. 8936 8937 Changed in v.5 -- inPlace is default False, and anchorZero, anchorZeroRecurse 8938 and inPlace are keyword only arguments. 8939 ''' 8940 # if we have offsets at 0, 2, 4 8941 # we scale by 2, getting offsets at 0, 4, 8 8942 8943 # compare to offsets at 10, 12, 14 8944 # we scale by 2; if we do not anchor at lower, we get 20, 24, 28 8945 # if we anchor, we get 10, 14, 18 8946 8947 if not amountToScale > 0: 8948 raise StreamException('amountToScale must be greater than zero') 8949 8950 if not inPlace: # make a copy 8951 returnObj = self.coreCopyAsDerivation('scaleOffsets') 8952 else: 8953 returnObj = self 8954 8955 # first, get the offset shift requested 8956 if anchorZero in ['lowest']: 8957 offsetShift = Fraction(returnObj.lowestOffset) 8958 elif anchorZero in ['highest']: 8959 offsetShift = Fraction(returnObj.highestOffset) 8960 elif anchorZero in [None]: 8961 offsetShift = Fraction(0, 1) 8962 else: 8963 raise StreamException(f'an anchorZero value of {anchorZero} is not accepted') 8964 8965 for e in returnObj._elements: 8966 8967 # subtract the offset shift (and lowestOffset of 80 becomes 0) 8968 # then apply the amountToScale 8969 o = (returnObj.elementOffset(e) - offsetShift) * amountToScale 8970 # after scaling, return the shift taken away 8971 o += offsetShift 8972 8973 # environLocal.printDebug(['changing offset', o, scalar, offsetShift]) 8974 8975 returnObj.coreSetElementOffset(e, o) 8976 # need to look for embedded Streams, and call this method 8977 # on them, with inPlace , as already copied if 8978 # inPlace is != True 8979 # if hasattr(e, 'elements'): # recurse time: 8980 if e.isStream: 8981 e.scaleOffsets(amountToScale, 8982 anchorZero=anchorZeroRecurse, 8983 anchorZeroRecurse=anchorZeroRecurse, 8984 inPlace=True) 8985 8986 returnObj.coreElementsChanged() 8987 if not inPlace: 8988 return returnObj 8989 8990 def scaleDurations(self, amountToScale, *, inPlace=False): 8991 ''' 8992 Scale all durations by a provided scalar. Offsets are not modified. 8993 8994 To augment or diminish a Stream, see the 8995 :meth:`~music21.stream.Stream.augmentOrDiminish` method. 8996 8997 We do not retain durations in any circumstance; 8998 if inPlace=False, two deepcopies of each duration are done. 8999 ''' 9000 if not amountToScale > 0: 9001 raise StreamException('amountToScale must be greater than zero') 9002 9003 if not inPlace: # make a copy 9004 returnObj = self.coreCopyAsDerivation('scaleDurations') 9005 else: 9006 returnObj = self 9007 9008 for e in returnObj.recurse().getElementsNotOfClass('Stream'): 9009 e.duration = e.duration.augmentOrDiminish(amountToScale) 9010 9011 if inPlace is not True: 9012 return returnObj 9013 9014 def augmentOrDiminish(self, amountToScale, *, inPlace=False): 9015 ''' 9016 Given a number greater than zero, 9017 multiplies the current quarterLength of the 9018 duration of each element by this number 9019 as well as their offset and returns a new Stream. 9020 Or if `inPlace` is 9021 set to True, modifies the durations of each 9022 element within the stream. 9023 9024 9025 A number of 0.5 will halve the durations and relative 9026 offset positions; a number of 2 will double the 9027 durations and relative offset positions. 9028 9029 9030 9031 Note that the default for inPlace is the opposite 9032 of what it is for augmentOrDiminish on a Duration. 9033 This is done purposely to reflect the most common 9034 usage. 9035 9036 9037 9038 >>> s = stream.Stream() 9039 >>> n = note.Note() 9040 >>> s.repeatAppend(n, 10) 9041 >>> s.highestOffset, s.highestTime 9042 (9.0, 10.0) 9043 >>> s1 = s.augmentOrDiminish(2) 9044 >>> s1.highestOffset, s1.highestTime 9045 (18.0, 20.0) 9046 >>> s1 = s.augmentOrDiminish(0.5) 9047 >>> s1.highestOffset, s1.highestTime 9048 (4.5, 5.0) 9049 ''' 9050 if not amountToScale > 0: 9051 raise StreamException('amountToScale must be greater than zero') 9052 if not inPlace: # make a copy 9053 returnObj = self.coreCopyAsDerivation('augmentOrDiminish') 9054 else: 9055 returnObj = self 9056 9057 # inPlace is True as a copy has already been made if nec 9058 returnObj.scaleOffsets(amountToScale=amountToScale, anchorZero='lowest', 9059 anchorZeroRecurse=None, inPlace=True) 9060 returnObj.scaleDurations(amountToScale=amountToScale, inPlace=True) 9061 9062 # do not need to call elements changed, as called in sub methods 9063 return returnObj 9064 9065 def quantize( 9066 self, 9067 quarterLengthDivisors=None, 9068 processOffsets=True, 9069 processDurations=True, 9070 inPlace=False, 9071 recurse=False, 9072 ): 9073 # noinspection PyShadowingNames 9074 ''' 9075 Quantize time values in this Stream by snapping offsets 9076 and/or durations to the nearest multiple of a quarter length value 9077 given as one or more divisors of 1 quarter length. The quantized 9078 value found closest to a divisor multiple will be used. 9079 9080 The `quarterLengthDivisors` provides a flexible way to provide quantization 9081 settings. For example, (2,) will snap all events to eighth note grid. 9082 (4, 3) will snap events to sixteenth notes and eighth note triplets, 9083 whichever is closer. (4, 6) will snap events to sixteenth notes and 9084 sixteenth note triplets. If quarterLengthDivisors is not specified then 9085 defaults.quantizationQuarterLengthDivisors is used. The default is (4, 3). 9086 9087 `processOffsets` determines whether the Offsets are quantized. 9088 9089 `processDurations` determines whether the Durations are quantized. 9090 9091 Both are set to True by default. Setting both to False does nothing to the Stream. 9092 9093 if `inPlace` is True, then the quantization is done on the Stream itself. If False 9094 (default) then a new quantized Stream of the same class is returned. 9095 9096 If `recurse` is True, then all substreams are also quantized. 9097 If False (default), then only the highest level of the Stream is quantized. 9098 9099 Changed in v.7: 9100 - `recurse` defaults False 9101 - look-ahead approach to choosing divisors to avoid gaps when processing durations 9102 9103 >>> n = note.Note() 9104 >>> n.quarterLength = 0.49 9105 >>> s = stream.Stream() 9106 >>> s.repeatInsert(n, [0.1, 0.49, 0.9]) 9107 >>> nShort = note.Note() 9108 >>> nShort.quarterLength = 0.26 9109 >>> s.repeatInsert(nShort, [1.49, 1.76]) 9110 9111 >>> s.quantize([4], processOffsets=True, processDurations=True, inPlace=True) 9112 >>> [e.offset for e in s] 9113 [0.0, 0.5, 1.0, 1.5, 1.75] 9114 >>> [e.duration.quarterLength for e in s] 9115 [0.5, 0.5, 0.5, 0.25, 0.25] 9116 9117 The error in quantization is set in the editorial attribute for the note in 9118 two places `.offsetQuantizationError` and `.quarterLengthQuantizationError` 9119 9120 >>> [e.editorial.offsetQuantizationError for e in s.notes] 9121 [0.1, -0.01, -0.1, -0.01, 0.01] 9122 >>> [e.editorial.quarterLengthQuantizationError for e in s.notes] 9123 [-0.01, -0.01, -0.01, 0.01, 0.01] 9124 9125 with default quarterLengthDivisors... 9126 9127 >>> s = stream.Stream() 9128 >>> s.repeatInsert(n, [0.1, 0.49, 0.9]) 9129 >>> nShort = note.Note() 9130 >>> nShort.quarterLength = 0.26 9131 >>> s.repeatInsert(nShort, [1.49, 1.76]) 9132 >>> t = s.quantize(processOffsets=True, processDurations=True, inPlace=False) 9133 >>> [e.offset for e in t] 9134 [0.0, 0.5, 1.0, 1.5, 1.75] 9135 >>> [e.duration.quarterLength for e in t] 9136 [0.5, 0.5, 0.5, 0.25, 0.25] 9137 9138 Set `recurse=True` to quantize elements in substreams such as parts, measures, voices: 9139 9140 >>> myPart = converter.parse('tinynotation: c32 d32 e32 f32') 9141 >>> myPart.quantize(inPlace=True) 9142 >>> [e.offset for e in myPart.measure(1).notes] # no change! 9143 [0.0, 0.125, 0.25, 0.375] 9144 9145 >>> myPart.quantize(inPlace=True, recurse=True) 9146 >>> [e.offset for e in myPart.measure(1).notes] 9147 [0.0, 0.0, 0.25, Fraction(1, 3)] 9148 9149 New in v.7: if both `processDurations` and `processOffsets` are True, then 9150 the next note's quantized offset is taken into account when quantizing the 9151 duration of the current note. This is to prevent unnecessary gaps from applying 9152 different quantization units to adjacent notes: 9153 9154 >>> s2 = stream.Stream() 9155 >>> nOddLength = note.Note(quarterLength=0.385) 9156 >>> s2.repeatInsert(nOddLength, [0, 0.5, 1, 1.5]) 9157 >>> s2.show('t', addEndTimes=True) 9158 {0.0 - 0.385} <music21.note.Note C> 9159 {0.5 - 0.885} <music21.note.Note C> 9160 {1.0 - 1.385} <music21.note.Note C> 9161 {1.5 - 1.885} <music21.note.Note C> 9162 9163 Before v.7, this would have yielded four triplet-eighths (separated by 1/6 QL rests): 9164 9165 >>> s2.quantize(processOffsets=True, processDurations=True, inPlace=True) 9166 >>> s2.show('text', addEndTimes=True) 9167 {0.0 - 0.5} <music21.note.Note C> 9168 {0.5 - 1.0} <music21.note.Note C> 9169 {1.0 - 1.5} <music21.note.Note C> 9170 {1.5 - 1.8333} <music21.note.Note C> 9171 9172 OMIT_FROM_DOCS 9173 9174 Test changing defaults, running, and changing back... 9175 9176 >>> dd = defaults.quantizationQuarterLengthDivisors 9177 >>> defaults.quantizationQuarterLengthDivisors = (3,) 9178 9179 >>> u = s.quantize(processOffsets=True, processDurations=True, inPlace=False) 9180 >>> [e.offset for e in u] 9181 [0.0, Fraction(1, 3), 1.0, Fraction(4, 3), Fraction(5, 3)] 9182 >>> [e.duration.quarterLength for e in u] 9183 [Fraction(1, 3), Fraction(1, 3), Fraction(1, 3), Fraction(1, 3), Fraction(1, 3)] 9184 9185 Original unchanged because inPlace=False: 9186 9187 >>> [e.offset for e in s] 9188 [Fraction(1, 10), Fraction(49, 100), Fraction(9, 10), Fraction(149, 100), Fraction(44, 25)] 9189 9190 >>> defaults.quantizationQuarterLengthDivisors = dd 9191 >>> v = s.quantize(processOffsets=True, processDurations=True, inPlace=False) 9192 >>> [e.offset for e in v] 9193 [0.0, 0.5, 1.0, 1.5, 1.75] 9194 >>> [e.duration.quarterLength for e in v] 9195 [0.5, 0.5, 0.5, 0.25, 0.25] 9196 ''' 9197 if quarterLengthDivisors is None: 9198 quarterLengthDivisors = defaults.quantizationQuarterLengthDivisors 9199 9200 # this presently is not trying to avoid overlaps that 9201 # result from quantization; this may be necessary 9202 9203 def bestMatch(target, divisors): 9204 found = [] 9205 for div in divisors: 9206 match, error, signedErrorInner = common.nearestMultiple(target, (1 / div)) 9207 # Sort by unsigned error, then "tick" (divisor expressed as QL, e.g. 0.25) 9208 found.append(BestQuantizationMatch(error, 1 / div, match, signedErrorInner, div)) 9209 # get first, and leave out the error 9210 bestMatchTuple = sorted(found)[0] 9211 return bestMatchTuple 9212 9213 # if we have a min of 0.25 (sixteenth) 9214 # quarterLengthMin = quarterLengthDivisors[0] 9215 9216 if inPlace is False: 9217 returnStream = self.coreCopyAsDerivation('quantize') 9218 else: 9219 returnStream = self 9220 9221 useStreams = [returnStream] 9222 if recurse is True: 9223 useStreams = returnStream.recurse(streamsOnly=True, includeSelf=True) 9224 9225 rests_lacking_durations: List[note.Rest] = [] 9226 for useStream in useStreams: 9227 for i, e in enumerate(useStream._elements): 9228 if processOffsets: 9229 o = useStream.elementOffset(e) 9230 sign = 1 9231 if o < 0: 9232 sign = -1 9233 o = -1 * o 9234 o_matchTuple = bestMatch(float(o), quarterLengthDivisors) 9235 useStream.coreSetElementOffset(e, o_matchTuple.match * sign) 9236 if hasattr(e, 'editorial') and o_matchTuple.signedError != 0: 9237 e.editorial.offsetQuantizationError = o_matchTuple.signedError * sign 9238 if processDurations: 9239 ql = e.duration.quarterLength 9240 ql = max(ql, 0) # negative ql possible in buggy MIDI files? 9241 d_matchTuple = bestMatch(float(ql), quarterLengthDivisors) 9242 # Check that any gaps from this quantized duration to the next onset 9243 # are at least as large as the smallest quantization unit (largest divisor) 9244 # If not, then re-quantize this duration with the divisor 9245 # that will be used to quantize the next element's offset 9246 if processOffsets and i + 1 < len(useStream._elements): 9247 next_element = useStream._elements[i + 1] 9248 next_offset = useStream.elementOffset(next_element) 9249 look_ahead_result = bestMatch(float(next_offset), quarterLengthDivisors) 9250 next_offset = look_ahead_result.match 9251 next_divisor = look_ahead_result.divisor 9252 if (0 < next_offset - (e.offset + d_matchTuple.match) 9253 < 1 / max(quarterLengthDivisors)): 9254 # Overwrite the earlier matchTuple with a better result 9255 d_matchTuple = bestMatch(float(ql), (next_divisor,)) 9256 # Enforce nonzero duration for non-grace notes 9257 if (d_matchTuple.match == 0 9258 and isinstance(e, note.NotRest) 9259 and not e.duration.isGrace): 9260 e.quarterLength = 1 / max(quarterLengthDivisors) 9261 if hasattr(e, 'editorial'): 9262 e.editorial.quarterLengthQuantizationError = ql - e.quarterLength 9263 elif d_matchTuple.match == 0 and isinstance(e, note.Rest): 9264 rests_lacking_durations.append(e) 9265 else: 9266 e.duration.quarterLength = d_matchTuple.match 9267 if hasattr(e, 'editorial') and d_matchTuple.signedError != 0: 9268 e.editorial.quarterLengthQuantizationError = d_matchTuple.signedError 9269 9270 # end for e in ._elements 9271 # ran coreSetElementOffset 9272 useStream.coreElementsChanged(updateIsFlat=False) 9273 9274 for rest_to_remove in rests_lacking_durations: 9275 useStream.remove(rest_to_remove) 9276 9277 if inPlace is False: 9278 return returnStream 9279 9280 def expandRepeats(self, copySpanners=True): 9281 ''' 9282 Expand this Stream with repeats. Nested repeats 9283 given with :class:`~music21.bar.Repeat` objects, or 9284 repeats and sections designated with 9285 :class:`~music21.repeat.RepeatExpression` objects, are all expanded. 9286 9287 This method always returns a new Stream, with 9288 deepcopies of all contained elements at all levels. 9289 9290 Uses the :class:`~music21.repeat.Expander` object in the `repeat` module. 9291 9292 TODO: DOC TEST 9293 ''' 9294 if not self.hasMeasures(): 9295 raise StreamException( 9296 'cannot process repeats on Stream that does not contain measures' 9297 ) 9298 9299 ex = repeat.Expander(self) 9300 post = ex.process() 9301 9302 # copy all non-repeats 9303 # do not copy repeat brackets 9304 for e in self.getElementsNotOfClass('Measure'): 9305 if 'RepeatBracket' not in e.classes: 9306 eNew = copy.deepcopy(e) # assume that this is needed 9307 post.insert(self.elementOffset(e), eNew) 9308 9309 # all elements at this level and in measures have been copied; now we 9310 # need to reconnect spanners 9311 if copySpanners: 9312 # environLocal.printDebug(['Stream.expandRepeats', 'copying spanners']) 9313 # spannerBundle = spanner.SpannerBundle(list(post.flatten().spanners)) 9314 spannerBundle = post.spannerBundle 9315 # iterate over complete tree (need containers); find 9316 # all new/old pairs 9317 for e in post.recurse(): 9318 # update based on last id, new object 9319 if e.sites.hasSpannerSite(): 9320 origin = e.derivation.origin 9321 if (origin is not None 9322 and e.derivation.method == '__deepcopy__'): 9323 spannerBundle.replaceSpannedElement(origin, e) 9324 9325 return post 9326 9327 # -------------------------------------------------------------------------- 9328 # slicing and recasting a note as many notes 9329 9330 def sliceByQuarterLengths(self, quarterLengthList, *, target=None, 9331 addTies=True, inPlace=False): 9332 ''' 9333 Slice all :class:`~music21.duration.Duration` objects on all Notes and Rests 9334 of this Stream. 9335 Duration are sliced according to values provided in `quarterLengthList` list. 9336 If the sum of these values is less than the Duration, the values are accumulated 9337 in a loop to try to fill the Duration. If a match cannot be found, an 9338 Exception is raised. 9339 9340 If `target` is None, the entire Stream is processed. Otherwise, only the element 9341 specified is manipulated. 9342 9343 ''' 9344 if not inPlace: # make a copy 9345 returnObj = self.coreCopyAsDerivation('sliceByQuarterLengths') 9346 else: 9347 returnObj = self 9348 9349 if returnObj.hasMeasures(): 9350 # call on component measures 9351 for m in returnObj.getElementsByClass('Measure'): 9352 m.sliceByQuarterLengths(quarterLengthList, 9353 target=target, addTies=addTies, inPlace=True) 9354 returnObj.coreElementsChanged() 9355 return returnObj # exit 9356 9357 if returnObj.hasPartLikeStreams(): 9358 for p in returnObj.getElementsByClass('Part'): 9359 p.sliceByQuarterLengths(quarterLengthList, 9360 target=target, addTies=addTies, inPlace=True) 9361 returnObj.coreElementsChanged() 9362 return returnObj # exit 9363 9364 if not common.isListLike(quarterLengthList): 9365 quarterLengthList = [quarterLengthList] 9366 9367 if target is not None: 9368 # get the element out of return obj 9369 # need to use self.index to get index value 9370 eToProcess = [returnObj[self.index(target)]] 9371 else: # get elements list from Stream 9372 eToProcess = returnObj.notesAndRests 9373 9374 for e in eToProcess: 9375 # if qlList values are greater than the found duration, skip 9376 if opFrac(sum(quarterLengthList)) > e.quarterLength: 9377 continue 9378 elif not opFrac(sum(quarterLengthList)) == e.quarterLength: 9379 # try to map a list that is of sufficient duration 9380 qlProcess = [] 9381 i = 0 9382 while True: 9383 qlProcess.append( 9384 quarterLengthList[i % len(quarterLengthList)]) 9385 i += 1 9386 sumQL = opFrac(sum(qlProcess)) 9387 if sumQL >= e.quarterLength: 9388 break 9389 else: 9390 qlProcess = quarterLengthList 9391 9392 # environLocal.printDebug(['got qlProcess', qlProcess, 9393 # 'for element', e, e.quarterLength]) 9394 9395 if not opFrac(sum(qlProcess)) == e.quarterLength: 9396 raise StreamException( 9397 'cannot map quarterLength list into element Duration: %s, %s' % ( 9398 sum(qlProcess), e.quarterLength)) 9399 9400 post = e.splitByQuarterLengths(qlProcess, addTies=addTies) 9401 # remove e from the source 9402 oInsert = e.getOffsetBySite(returnObj) 9403 returnObj.remove(e) 9404 for eNew in post: 9405 returnObj.coreInsert(oInsert, eNew) 9406 oInsert = opFrac(oInsert + eNew.quarterLength) 9407 9408 returnObj.coreElementsChanged() 9409 9410 if not inPlace: 9411 return returnObj 9412 9413 def sliceByGreatestDivisor(self, *, addTies=True, inPlace=False): 9414 ''' 9415 Slice all :class:`~music21.duration.Duration` objects on all Notes and Rests of this Stream. 9416 Duration are sliced according to the approximate GCD found in all durations. 9417 ''' 9418 # when operating on a Stream, this should take all durations found 9419 # and use the approximateGCD to get a min duration; then, call sliceByQuarterLengths 9420 9421 if not inPlace: # make a copy 9422 returnObj = self.coreCopyAsDerivation('sliceByGreatestDivisor') 9423 else: 9424 returnObj = self 9425 9426 if returnObj.hasMeasures(): 9427 # call on component measures 9428 for m in returnObj.getElementsByClass('Measure'): 9429 m.sliceByGreatestDivisor(addTies=addTies, inPlace=True) 9430 return returnObj # exit 9431 9432 uniqueQuarterLengths = [] 9433 for e in returnObj.notesAndRests: 9434 if e.quarterLength not in uniqueQuarterLengths: 9435 uniqueQuarterLengths.append(e.quarterLength) 9436 9437 # environLocal.printDebug(['unique quarter lengths', uniqueQuarterLengths]) 9438 9439 # will raise an exception if no gcd can be found 9440 divisor = common.approximateGCD(uniqueQuarterLengths) 9441 9442 # process in place b/c a copy, if necessary, has already been made 9443 returnObj.sliceByQuarterLengths(quarterLengthList=[divisor], 9444 target=None, addTies=addTies, inPlace=True) 9445 9446 if not inPlace: 9447 return returnObj 9448 9449 def sliceAtOffsets( 9450 self, 9451 offsetList, 9452 target=None, 9453 addTies=True, 9454 inPlace=False, 9455 displayTiedAccidentals=False 9456 ): 9457 # noinspection PyShadowingNames 9458 ''' 9459 Given a list of quarter lengths, slice and optionally tie all 9460 Music21Objects crossing these points. 9461 9462 >>> s = stream.Stream() 9463 >>> n = note.Note() 9464 >>> n.duration.type = 'whole' 9465 >>> s.append(n) 9466 >>> post = s.sliceAtOffsets([1, 2, 3], inPlace=True) 9467 >>> [(e.offset, e.quarterLength) for e in s] 9468 [(0.0, 1.0), (1.0, 1.0), (2.0, 1.0), (3.0, 1.0)] 9469 ''' 9470 if not inPlace: # make a copy 9471 returnObj = self.coreCopyAsDerivation('sliceAtOffsets') 9472 else: 9473 returnObj = self 9474 9475 if returnObj.hasMeasures(): 9476 # call on component measures 9477 for m in returnObj.getElementsByClass('Measure'): 9478 # offset values are not relative to measure; need to 9479 # shift by each measure's offset 9480 offsetListLocal = [o - m.getOffsetBySite(returnObj) for o in offsetList] 9481 m.sliceAtOffsets(offsetList=offsetListLocal, 9482 addTies=addTies, 9483 inPlace=True, 9484 displayTiedAccidentals=displayTiedAccidentals) 9485 return returnObj # exit 9486 9487 if returnObj.hasPartLikeStreams(): 9488 # part-like requires getting Streams, not Parts 9489 for p in returnObj.getElementsByClass('Stream'): 9490 offsetListLocal = [o - p.getOffsetBySite(returnObj) for o in offsetList] 9491 p.sliceAtOffsets(offsetList=offsetListLocal, 9492 addTies=addTies, 9493 inPlace=True, 9494 displayTiedAccidentals=displayTiedAccidentals) 9495 return returnObj # exit 9496 9497 # list of start, start+dur, element, all in abs offset time 9498 offsetMap = self.offsetMap(returnObj) 9499 9500 offsetList = [opFrac(o) for o in offsetList] 9501 9502 for ob in offsetMap: 9503 # if target is defined, only modify that object 9504 e, oStart, oEnd, unused_voiceCount = ob 9505 if target is not None and id(e) != id(target): 9506 continue 9507 9508 cutPoints = [] 9509 oStart = opFrac(oStart) 9510 oEnd = opFrac(oEnd) 9511 9512 for o in offsetList: 9513 if oStart < o < oEnd: 9514 cutPoints.append(o) 9515 # environLocal.printDebug(['cutPoints', cutPoints, 'oStart', oStart, 'oEnd', oEnd]) 9516 if cutPoints: 9517 # remove old 9518 # eProc = returnObj.remove(e) 9519 eNext = e 9520 oStartNext = oStart 9521 for o in cutPoints: 9522 oCut = o - oStartNext 9523 unused_eComplete, eNext = eNext.splitAtQuarterLength( 9524 oCut, 9525 retainOrigin=True, 9526 addTies=addTies, 9527 displayTiedAccidentals=displayTiedAccidentals 9528 ) 9529 # only need to insert eNext, as eComplete was modified 9530 # in place due to retainOrigin option 9531 # insert at o, not oCut (duration into element) 9532 returnObj.coreInsert(o, eNext) 9533 oStartNext = o 9534 returnObj.coreElementsChanged() 9535 if inPlace is False: 9536 return returnObj 9537 9538 def sliceByBeat(self, 9539 target=None, 9540 addTies=True, 9541 inPlace=False, 9542 displayTiedAccidentals=False): 9543 ''' 9544 Slice all elements in the Stream that have a Duration at 9545 the offsets determined to be the beat from the local TimeSignature. 9546 9547 Changed in v7. -- return None if inPlace is True 9548 ''' 9549 9550 if not inPlace: # make a copy 9551 returnObj = self.coreCopyAsDerivation('sliceByBeat') 9552 else: 9553 returnObj = self 9554 9555 if returnObj.hasMeasures(): 9556 # call on component measures 9557 for m in returnObj.getElementsByClass('Measure'): 9558 m.sliceByBeat(target=target, 9559 addTies=addTies, 9560 inPlace=True, 9561 displayTiedAccidentals=displayTiedAccidentals) 9562 return returnObj # exit 9563 9564 if returnObj.hasPartLikeStreams(): 9565 for p in returnObj.getElementsByClass('Part'): 9566 p.sliceByBeat(target=target, 9567 addTies=addTies, 9568 inPlace=True, 9569 displayTiedAccidentals=displayTiedAccidentals) 9570 return returnObj # exit 9571 9572 # this will return a default 9573 # using this method to work on Stream, not just Measures 9574 tsStream = returnObj.getTimeSignatures(returnDefault=True) 9575 9576 if not tsStream: 9577 raise StreamException('no time signature was found') 9578 9579 if len(tsStream) > 1: 9580 raise StreamException('not yet implemented: slice by changing time signatures') 9581 9582 offsetList = tsStream[0].getBeatOffsets() 9583 returnObj.sliceAtOffsets(offsetList, 9584 target=target, 9585 addTies=addTies, 9586 inPlace=True, 9587 displayTiedAccidentals=displayTiedAccidentals) 9588 9589 if not inPlace: 9590 return returnObj 9591 9592 # -------------------------------------------------------------------------- 9593 # get boolean information from the Stream 9594 9595 def hasMeasures(self): 9596 ''' 9597 Return a boolean value showing if this Stream contains Measures. 9598 9599 9600 >>> s = stream.Stream() 9601 >>> s.repeatAppend(note.Note(), 8) 9602 >>> s.hasMeasures() 9603 False 9604 >>> s.makeMeasures(inPlace=True) 9605 >>> len(s.getElementsByClass('Measure')) 9606 2 9607 >>> s.hasMeasures() 9608 True 9609 ''' 9610 if 'hasMeasures' not in self._cache or self._cache['hasMeasures'] is None: 9611 post = False 9612 # do not need to look in endElements 9613 for obj in self._elements: 9614 # if obj is a Part, we have multi-parts 9615 if getattr(obj, 'isMeasure', False): 9616 post = True 9617 break # only need one 9618 self._cache['hasMeasures'] = post 9619 return self._cache['hasMeasures'] 9620 9621 def hasVoices(self): 9622 ''' 9623 Return a boolean value showing if this Stream contains Voices 9624 ''' 9625 if 'hasVoices' not in self._cache or self._cache['hasVoices'] is None: 9626 post = False 9627 # do not need to look in endElements 9628 for obj in self._elements: 9629 # if obj is a Part, we have multi-parts 9630 if 'Voice' in obj.classes: 9631 post = True 9632 break # only need one 9633 self._cache['hasVoices'] = post 9634 return self._cache['hasVoices'] 9635 9636 def hasPartLikeStreams(self): 9637 ''' 9638 Return a boolean value showing if this Stream contains any Parts, 9639 or Part-like sub-Streams. 9640 9641 Part-like sub-streams are Streams that contain Measures or Notes. 9642 And where no sub-stream begins at an offset besides zero. 9643 9644 >>> s = stream.Score() 9645 >>> s.hasPartLikeStreams() 9646 False 9647 >>> p1 = stream.Part() 9648 >>> p1.repeatAppend(note.Note(), 8) 9649 >>> s.insert(0, p1) 9650 >>> s.hasPartLikeStreams() 9651 True 9652 9653 A stream that has a measure in it is not a part-like stream. 9654 9655 >>> s = stream.Score() 9656 >>> m1 = stream.Measure() 9657 >>> m1.repeatAppend(note.Note(), 4) 9658 >>> s.append(m1) 9659 >>> s.hasPartLikeStreams() 9660 False 9661 9662 9663 A stream with a single generic Stream substream at the beginning has part-like Streams... 9664 9665 >>> s = stream.Score() 9666 >>> m1 = stream.Stream() 9667 >>> m1.repeatAppend(note.Note(), 4) 9668 >>> s.append(m1) 9669 >>> s.hasPartLikeStreams() 9670 True 9671 9672 9673 Adding another though makes it not part-like. 9674 9675 >>> m2 = stream.Stream() 9676 >>> m2.repeatAppend(note.Note(), 4) 9677 >>> s.append(m2) 9678 >>> s.hasPartLikeStreams() 9679 False 9680 9681 Flat objects do not have part-like Streams: 9682 9683 >>> sf = s.flatten() 9684 >>> sf.hasPartLikeStreams() 9685 False 9686 ''' 9687 if 'hasPartLikeStreams' not in self._cache or self._cache['hasPartLikeStreams'] is None: 9688 multiPart = False 9689 if not self.isFlat: # if flat, does not have parts! 9690 # do not need to look in endElements 9691 obj: base.Music21Object 9692 for obj in self.getElementsByClass('Stream'): 9693 # if obj is a Part, we have multi-parts 9694 if 'Part' in obj.classes: 9695 multiPart = True 9696 break 9697 9698 elif 'Measure' in obj.classes or 'Voice' in obj.classes: 9699 multiPart = False 9700 break 9701 9702 elif obj.offset != 0.0: 9703 multiPart = False 9704 break 9705 9706 # if components are streams of Notes or Measures, 9707 # than assume this is like a Part 9708 elif (obj.getElementsByClass('Measure') 9709 or obj.notesAndRests): 9710 multiPart = True 9711 self._cache['hasPartLikeStreams'] = multiPart 9712 return self._cache['hasPartLikeStreams'] 9713 9714 def isTwelveTone(self): 9715 ''' 9716 Return true if this Stream only employs twelve-tone equal-tempered pitch values. 9717 9718 9719 >>> s = stream.Stream() 9720 >>> s.append(note.Note('G#4')) 9721 >>> s.isTwelveTone() 9722 True 9723 >>> s.append(note.Note('G~4')) 9724 >>> s.isTwelveTone() 9725 False 9726 ''' 9727 for p in self.pitches: 9728 if not p.isTwelveTone(): 9729 return False 9730 return True 9731 9732 def isWellFormedNotation(self) -> bool: 9733 # noinspection PyShadowingNames 9734 ''' 9735 Return True if, given the context of this Stream or Stream subclass, 9736 contains what appears to be well-formed notation. This often means 9737 the formation of Measures, or a Score that contains Part with Measures. 9738 9739 9740 >>> s = corpus.parse('bwv66.6') 9741 >>> s.isWellFormedNotation() 9742 True 9743 >>> s.parts[0].isWellFormedNotation() 9744 True 9745 >>> s.parts[0].getElementsByClass('Measure').first().isWellFormedNotation() 9746 True 9747 9748 >>> s2 = stream.Score() 9749 >>> m = stream.Measure() 9750 >>> s2.append(m) 9751 >>> s2.isWellFormedNotation() 9752 False 9753 9754 >>> o = stream.Opus([s]) 9755 >>> o.isWellFormedNotation() 9756 True 9757 >>> o2 = stream.Opus([s2]) 9758 >>> o2.isWellFormedNotation() 9759 False 9760 9761 Only Measures and Voices are allowed to contain notes and rests directly: 9762 >>> m.isWellFormedNotation() 9763 True 9764 >>> s2.append(note.Rest()) 9765 >>> s2.isWellFormedNotation() 9766 False 9767 ''' 9768 def allSubstreamsHaveMeasures(testStream): 9769 return all(s.hasMeasures() for s in testStream.getElementsByClass('Stream')) 9770 9771 # if a measure or voice, we assume we are well-formed 9772 if 'Measure' in self.classes or 'Voice' in self.classes: 9773 return True 9774 # all other Stream classes are not well-formed if they have "loose" notes 9775 elif self.getElementsByClass('GeneralNote'): 9776 return False 9777 elif 'Part' in self.classes: 9778 if self.hasMeasures(): 9779 return True 9780 elif 'Opus' in self.classes: 9781 return all(allSubstreamsHaveMeasures(s) for s in self.scores) 9782 elif self.hasPartLikeStreams(): 9783 return allSubstreamsHaveMeasures(self) 9784 # all other conditions are not well-formed notation 9785 return False 9786 9787 # -------------------------------------------------------------------------- 9788 @property 9789 def notesAndRests(self): 9790 ''' 9791 The notesAndRests property of a Stream returns a `StreamIterator` 9792 that consists only of the :class:`~music21.note.GeneralNote` objects found in 9793 the stream. The new Stream will contain 9794 mostly notes and rests (including 9795 :class:`~music21.note.Note`, 9796 :class:`~music21.chord.Chord`, 9797 :class:`~music21.note.Rest`) but also their subclasses, such as 9798 `Harmony` objects (`ChordSymbols`, `FiguredBass`), etc. 9799 9800 9801 >>> s1 = stream.Stream() 9802 >>> k1 = key.KeySignature(0) # key of C 9803 >>> n1 = note.Note('B') 9804 >>> r1 = note.Rest() 9805 >>> c1 = chord.Chord(['A', 'B-']) 9806 >>> s1.append([k1, n1, r1, c1]) 9807 >>> s1.show('text') 9808 {0.0} <music21.key.KeySignature of no sharps or flats> 9809 {0.0} <music21.note.Note B> 9810 {1.0} <music21.note.Rest quarter> 9811 {2.0} <music21.chord.Chord A B-> 9812 9813 `.notesAndRests` removes the `KeySignature` object but keeps the `Rest`. 9814 9815 >>> notes1 = s1.notesAndRests.stream() 9816 >>> notes1.show('text') 9817 {0.0} <music21.note.Note B> 9818 {1.0} <music21.note.Rest quarter> 9819 {2.0} <music21.chord.Chord A B-> 9820 9821 The same caveats about `Stream` classes and `.flatten()` in `.notes` apply here. 9822 ''' 9823 if 'notesAndRests' not in self._cache or self._cache['notesAndRests'] is None: 9824 # environLocal.printDebug(['updating noteAndRests cache:', str(self), id(self)]) 9825 noteIterator = self.getElementsByClass('GeneralNote') 9826 noteIterator.overrideDerivation = 'notesAndRests' 9827 self._cache['notesAndRests'] = noteIterator 9828 return self._cache['notesAndRests'] 9829 9830 # return self.getElementsByClass([note.GeneralNote, chord.Chord]) 9831 # using string class names is import for some test contexts where 9832 # absolute class name matching fails 9833 # return self.getElementsByClass(['GeneralNote', 'Chord']) 9834 9835 @property 9836 def notes(self): 9837 ''' 9838 The notes property of a Stream returns an iterator 9839 that consists only of the notes (that is, 9840 :class:`~music21.note.Note`, 9841 :class:`~music21.chord.Chord`, etc.) found 9842 in the stream. This excludes :class:`~music21.note.Rest` objects. 9843 9844 9845 >>> p1 = stream.Part() 9846 >>> k1 = key.KeySignature(0) # key of C 9847 >>> n1 = note.Note('B') 9848 >>> r1 = note.Rest() 9849 >>> c1 = chord.Chord(['A', 'B-']) 9850 >>> p1.append([k1, n1, r1, c1]) 9851 >>> p1.show('text') 9852 {0.0} <music21.key.KeySignature of no sharps or flats> 9853 {0.0} <music21.note.Note B> 9854 {1.0} <music21.note.Rest quarter> 9855 {2.0} <music21.chord.Chord A B-> 9856 9857 >>> noteStream = p1.notes.stream() 9858 >>> noteStream.show('text') 9859 {0.0} <music21.note.Note B> 9860 {2.0} <music21.chord.Chord A B-> 9861 9862 Notice that `.notes` returns a :class:`~music21.stream.iterator.StreamIterator` object 9863 9864 >>> p1.notes 9865 <music21.stream.iterator.StreamIterator for Part:0x105b56128 @:0> 9866 9867 Let's add a measure to `p1`: 9868 9869 >>> m1 = stream.Measure() 9870 >>> n2 = note.Note('D') 9871 >>> m1.insert(0, n2) 9872 >>> p1.append(m1) 9873 9874 Now note that `n2` is *not* found in `p1.notes` 9875 9876 >>> p1.notes.stream().show('text') 9877 {0.0} <music21.note.Note B> 9878 {2.0} <music21.chord.Chord A B-> 9879 9880 We need to call `p1.flatten().notes` to find it: 9881 9882 >>> p1.flatten().notes.stream().show('text') 9883 {0.0} <music21.note.Note B> 9884 {2.0} <music21.chord.Chord A B-> 9885 {3.0} <music21.note.Note D> 9886 9887 (Technical note: All elements of class NotRest are being found 9888 right now. This will eventually change to also filter out 9889 Unpitched objects, so that all elements returned by 9890 `.notes` have a `.pitches` attribute. 9891 ''' 9892 if 'notes' not in self._cache or self._cache['notes'] is None: 9893 noteIterator = self.getElementsByClass('NotRest') 9894 noteIterator.overrideDerivation = 'notes' 9895 self._cache['notes'] = noteIterator 9896 return self._cache['notes'] 9897 9898 @property 9899 def pitches(self): 9900 ''' 9901 Returns all :class:`~music21.pitch.Pitch` objects found in any 9902 element in the Stream as a Python List. Elements such as 9903 Streams, and Chords will have their Pitch objects accumulated as 9904 well. For that reason, a flat representation is not required. 9905 9906 Pitch objects are returned in a List, not a Stream. This usage 9907 differs from the .notes property, but makes sense since Pitch 9908 objects usually have by default a Duration of zero. This is an important difference 9909 between them and :class:`music21.note.Note` objects. 9910 9911 key.Key objects are subclasses of Scales, which DO have a .pitches list, but 9912 they are specifically exempt from looking for pitches, since that is unlikely 9913 what someone wants here. 9914 9915 N.B., TODO: This may turn to an Iterator soon. 9916 9917 >>> from music21 import corpus 9918 >>> a = corpus.parse('bach/bwv324.xml') 9919 >>> partOnePitches = a.parts[0].pitches 9920 >>> len(partOnePitches) 9921 25 9922 >>> partOnePitches[0] 9923 <music21.pitch.Pitch B4> 9924 >>> [str(p) for p in partOnePitches[0:10]] 9925 ['B4', 'D5', 'B4', 'B4', 'B4', 'B4', 'C5', 'B4', 'A4', 'A4'] 9926 9927 9928 Note that the pitches returned above are 9929 objects, not text: 9930 9931 >>> partOnePitches[0].octave 9932 4 9933 9934 Since all elements with a `.pitches` list are returned and streams themselves have 9935 `.pitches` properties (the docs you are reading now), pitches from embedded streams 9936 are also returned. Flattening a stream is not necessary. Whether this is a 9937 feature or a bug is in the eye of the beholder. 9938 9939 >>> len(a.pitches) 9940 104 9941 9942 Chords get their pitches found as well: 9943 9944 >>> c = chord.Chord(['C4', 'E4', 'G4']) 9945 >>> n = note.Note('F#4') 9946 >>> m = stream.Measure() 9947 >>> m.append(n) 9948 >>> m.append(c) 9949 >>> m.pitches 9950 [<music21.pitch.Pitch F#4>, <music21.pitch.Pitch C4>, 9951 <music21.pitch.Pitch E4>, <music21.pitch.Pitch G4>] 9952 ''' 9953 post = [] 9954 for e in self.elements: 9955 if isinstance(e, key.Key): 9956 continue # has .pitches but should not be added 9957 if hasattr(e, 'pitch'): 9958 post.append(e.pitch) 9959 # both Chords and Stream have a pitches properties; this just 9960 # causes a recursive pitch gathering 9961 elif hasattr(e, 'pitches'): 9962 post.extend(list(e.pitches)) 9963 return post 9964 9965 # -------------------------------------------------------------------------- 9966 # interval routines 9967 9968 def findConsecutiveNotes(self, 9969 skipRests=False, 9970 skipChords=False, 9971 skipUnisons=False, 9972 skipOctaves=False, 9973 skipGaps=False, 9974 getOverlaps=False, 9975 noNone=False, **keywords): 9976 r''' 9977 Returns a list of consecutive *pitched* Notes in a Stream. 9978 9979 A single "None" is placed in the list 9980 at any point there is a discontinuity (such as if there is a 9981 rest between two pitches), unless the `noNone` parameter is True. 9982 9983 How to determine consecutive pitches is a little tricky and there are many options: 9984 9985 The `skipUnisons` parameter uses the midi-note value (.ps) to determine unisons, 9986 so enharmonic transitions (F# -> Gb) are 9987 also skipped if `skipUnisons` is true. We believe that this is the most common usage. 9988 However, because 9989 of this, you cannot completely be sure that the 9990 x.findConsecutiveNotes() - x.findConsecutiveNotes(skipUnisons=True) 9991 will give you the number of P1s in the piece, because there could be 9992 d2's in there as well. 9993 9994 See test.TestStream.testFindConsecutiveNotes() for usage details. 9995 9996 This example is adapted from the tutorials/Examples page. 9997 9998 >>> s = converter.parse("tinynotation: 4/4 f8 d'8~ d'8 d'8~ d'4 b'-8 a'-8 a-8") 9999 >>> m = s.measure(1) 10000 >>> m.findConsecutiveNotes(skipUnisons=True, skipOctaves=True, 10001 ... skipRests=True, noNone=True) 10002 [<music21.note.Note F>, <music21.note.Note D>, 10003 <music21.note.Note B->, <music21.note.Note A->] 10004 10005 >>> m.findConsecutiveNotes(skipUnisons=False, 10006 ... skipRests=True, noNone=True) 10007 [<music21.note.Note F>, <music21.note.Note D>, 10008 <music21.note.Note D>, <music21.note.Note D>, <music21.note.Note D>, 10009 <music21.note.Note B->, <music21.note.Note A->] 10010 10011 Changed in v7 -- now finds notes in Voices without requiring `getOverlaps=True` 10012 and iterates over Parts rather than flattening. 10013 If `noNone=False`, inserts `None` when backing up to scan a subsequent voice or part. 10014 10015 OMIT_FROM_DOCS 10016 10017 N.B. for chords, currently, only the first pitch is tested for unison. 10018 this is a bug TODO: FIX 10019 10020 (\*\*kwargs is there so that other methods that pass along dicts to 10021 findConsecutiveNotes don't have to remove 10022 their own args; this method is used in melodicIntervals.) 10023 ''' 10024 if self.isSorted is False and self.autoSort: 10025 self.sort() 10026 returnList = [] 10027 lastStart = 0.0 10028 lastEnd = 0.0 10029 lastContainerEnd = 0.0 10030 lastWasNone = False 10031 lastPitch = None 10032 if skipOctaves is True: 10033 skipUnisons = True # implied 10034 10035 for container in self.recurse(streamsOnly=True, includeSelf=True): 10036 if (container.offset < lastContainerEnd 10037 and container.getElementsByClass(note.GeneralNote) 10038 and noNone is False): 10039 returnList.append(None) 10040 lastWasNone = True 10041 lastPitch = None 10042 10043 lastStart = 0.0 10044 lastEnd = 0.0 10045 10046 # NB: NOT a recursive search 10047 # do not want to capture Measure containing only Voices, 10048 # Part containing only Measures, etc. 10049 if container.getElementsByClass(note.GeneralNote): 10050 lastContainerEnd = container.highestTime 10051 10052 # Filter out all but notes and rests 10053 for e in container.getElementsByClass(note.GeneralNote): 10054 if (lastWasNone is False 10055 and skipGaps is False 10056 and e.offset > lastEnd): 10057 if not noNone: 10058 returnList.append(None) 10059 lastWasNone = True 10060 if hasattr(e, 'pitch'): 10061 # if (skipUnisons is False or isinstance(lastPitch, list) 10062 if not(skipUnisons is False 10063 or isinstance(lastPitch, tuple) 10064 or lastPitch is None 10065 or (hasattr(lastPitch, 'pitchClass') 10066 and e.pitch.pitchClass != lastPitch.pitchClass) 10067 or (skipOctaves is False 10068 and hasattr(lastPitch, 'pitchClass') 10069 and e.pitch.ps != lastPitch.ps)): 10070 continue 10071 if getOverlaps is False and e.offset < lastEnd: 10072 continue 10073 10074 returnList.append(e) 10075 if e.offset < lastEnd: # is an overlap... 10076 continue 10077 10078 lastStart = e.offset 10079 if hasattr(e, 'duration'): 10080 lastEnd = opFrac(lastStart + e.duration.quarterLength) 10081 else: 10082 lastEnd = lastStart 10083 lastWasNone = False 10084 lastPitch = e.pitch 10085 10086 # if we have a chord 10087 elif hasattr(e, 'pitches') and len(e.pitches) > 1: 10088 if skipChords is True: 10089 if lastWasNone is False and not noNone: 10090 returnList.append(None) 10091 lastWasNone = True 10092 lastPitch = None 10093 # if we have a chord 10094 elif (not (skipUnisons is True 10095 and isinstance(lastPitch, (list, tuple)) 10096 and e.pitches[0].ps == lastPitch[0].ps 10097 ) and (getOverlaps is True or e.offset >= lastEnd)): 10098 returnList.append(e) 10099 if e.offset < lastEnd: # is an overlap... 10100 continue 10101 10102 lastStart = e.offset 10103 if hasattr(e, 'duration'): 10104 lastEnd = opFrac(lastStart + e.duration.quarterLength) 10105 else: 10106 lastEnd = lastStart 10107 # this is the case where the last pitch is a 10108 # a list 10109 lastPitch = e.pitches 10110 lastWasNone = False 10111 10112 elif (skipRests is False 10113 and isinstance(e, note.Rest) 10114 and lastWasNone is False): 10115 if noNone is False: 10116 returnList.append(None) 10117 lastWasNone = True 10118 lastPitch = None 10119 elif skipRests is True and isinstance(e, note.Rest): 10120 lastEnd = opFrac(e.offset + e.duration.quarterLength) 10121 10122 if lastWasNone is True: 10123 returnList.pop() # removes the last-added element 10124 return returnList 10125 10126 def melodicIntervals(self, *skipArgs, **skipKeywords): 10127 ''' 10128 Returns a Stream of :class:`~music21.interval.Interval` objects 10129 between Notes (and by default, Chords) that follow each other in a stream. 10130 the offset of the Interval is the offset of the beginning of the interval 10131 (if two notes are adjacent, then this offset is equal to the offset of 10132 the second note, but if skipRests is set to True or there is a gap 10133 in the Stream, then these two numbers 10134 will be different). 10135 10136 See :meth:`~music21.stream.Stream.findConsecutiveNotes` in this class for 10137 a discussion of what is meant by default for "consecutive notes", and 10138 which keywords such as skipChords, skipRests, skipUnisons, etc. can be 10139 used to change that behavior. 10140 10141 The interval between a Note and a Chord (or between two chords) is the 10142 interval to the first pitch of the Chord (pitches[0]) which is usually the lowest. 10143 For more complex interval calculations, 10144 run :meth:`~music21.stream.Stream.findConsecutiveNotes` and then calculate 10145 your own intervals directly. 10146 10147 Returns an empty Stream if there are not at least two elements found by 10148 findConsecutiveNotes. 10149 10150 10151 >>> s1 = converter.parse("tinynotation: 3/4 c4 d' r b b'", makeNotation=False) 10152 >>> #_DOCS_SHOW s1.show() 10153 10154 .. image:: images/streamMelodicIntervals1.* 10155 :width: 246 10156 10157 >>> intervalStream1 = s1.melodicIntervals() 10158 >>> intervalStream1.show('text') 10159 {1.0} <music21.interval.Interval M9> 10160 {4.0} <music21.interval.Interval P8> 10161 10162 >>> M9 = intervalStream1[0] 10163 >>> M9.noteStart.nameWithOctave, M9.noteEnd.nameWithOctave 10164 ('C4', 'D5') 10165 10166 Using the skip attributes from :meth:`~music21.stream.Stream.findConsecutiveNotes`, 10167 we can alter which intervals are reported: 10168 10169 >>> intervalStream2 = s1.melodicIntervals(skipRests=True, skipOctaves=True) 10170 >>> intervalStream2.show('text') 10171 {1.0} <music21.interval.Interval M9> 10172 {2.0} <music21.interval.Interval m-3> 10173 10174 >>> m3 = intervalStream2[1] 10175 >>> m3.directedNiceName 10176 'Descending Minor Third' 10177 ''' 10178 returnList = self.findConsecutiveNotes(**skipKeywords) 10179 if len(returnList) < 2: 10180 return self.cloneEmpty(derivationMethod='melodicIntervals') 10181 10182 returnStream = self.cloneEmpty(derivationMethod='melodicIntervals') 10183 for i in range(len(returnList) - 1): 10184 firstNote = returnList[i] 10185 secondNote = returnList[i + 1] 10186 # returnList could contain None to represent a rest 10187 if firstNote is None or secondNote is None: 10188 continue 10189 # Protect against empty chords 10190 if not (firstNote.pitches and secondNote.pitches): 10191 continue 10192 if chord.Chord in firstNote.classSet: 10193 noteStart = firstNote.notes[0] 10194 else: 10195 noteStart = firstNote 10196 if chord.Chord in secondNote.classSet: 10197 noteEnd = secondNote.notes[0] 10198 else: 10199 noteEnd = secondNote 10200 # Prefer Note objects over Pitch objects so that noteStart is set correctly 10201 returnInterval = interval.Interval(noteStart, noteEnd) 10202 returnInterval.offset = opFrac(firstNote.offset + firstNote.quarterLength) 10203 returnInterval.duration = duration.Duration(opFrac( 10204 secondNote.offset - returnInterval.offset)) 10205 returnStream.insert(returnInterval) 10206 10207 return returnStream 10208 10209 # -------------------------------------------------------------------------- 10210 def _getDurSpan(self, flatStream): 10211 ''' 10212 Given a flat stream, create a list of the start and end 10213 times (as a tuple pair) of all elements in the Stream. 10214 10215 10216 >>> a = stream.Stream() 10217 >>> a.repeatInsert(note.Note(type='half'), [0, 1, 2, 3, 4]) 10218 >>> a._getDurSpan(a.flatten()) 10219 [(0.0, 2.0), (1.0, 3.0), (2.0, 4.0), (3.0, 5.0), (4.0, 6.0)] 10220 ''' 10221 post = [] 10222 for e in flatStream: 10223 dur = e.duration.quarterLength 10224 durSpan = (e.offset, opFrac(e.offset + dur)) 10225 post.append(durSpan) 10226 # assume this is already sorted 10227 # index found here will be the same as elementsSorted 10228 return post 10229 10230 def _durSpanOverlap(self, a, b, includeEndBoundary=False): 10231 ''' 10232 Compare two durSpans and find overlaps; optionally, 10233 include coincident boundaries. a and b are sorted to permit any ordering. 10234 10235 If an element ends at 3.0 and another starts at 3.0, this may or may not 10236 be considered an overlap. The includeCoincidentEnds parameter determines 10237 this behaviour, where ending and starting 3.0 being a type of overlap 10238 is set by the includeEndBoundary being True. 10239 10240 10241 >>> sc = stream.Stream() 10242 >>> sc._durSpanOverlap((0, 5), (4, 12), False) 10243 True 10244 >>> sc._durSpanOverlap((0, 10), (11, 12), False) 10245 False 10246 >>> sc._durSpanOverlap((11, 12), (0, 10), False) 10247 False 10248 >>> sc._durSpanOverlap((0, 3), (3, 6), False) 10249 False 10250 >>> sc._durSpanOverlap((0, 3), (3, 6), True) 10251 True 10252 ''' 10253 durSpans = [a, b] 10254 # sorting will ensure that leading numbers are ordered from low to high 10255 durSpans.sort() 10256 found = False 10257 10258 if includeEndBoundary: 10259 # if the start of b is before the end of a 10260 # if durSpans[1][0] <= durSpans[0][1]: 10261 if durSpans[1][0] <= durSpans[0][1]: 10262 found = True 10263 else: # do not include coincident boundaries 10264 # if durSpans[1][0] < durSpans[0][1]: 10265 if durSpans[1][0] < durSpans[0][1]: 10266 found = True 10267 return found 10268 10269 def _findLayering(self): 10270 ''' 10271 Find any elements in an elementsSorted list that have 10272 durations that cause overlaps. 10273 10274 Returns a lists that has elements with overlaps, 10275 all index values that match are included in that list. 10276 10277 See testOverlaps, in unit tests, for examples. 10278 10279 Used in getOverlaps inside makeVoices. 10280 ''' 10281 flatStream = self.flatten() 10282 if flatStream.isSorted is False: 10283 flatStream = flatStream.sorted() 10284 # these may not be sorted 10285 durSpanSorted = self._getDurSpan(flatStream) 10286 # According to the above comment, the spans may not be sorted 10287 # so we sort them to be sure, but keep track of their original indices 10288 durSpanSortedIndex = list(enumerate(durSpanSorted)) 10289 durSpanSortedIndex.sort() 10290 10291 # create a list with an entry for each element 10292 # in each entry, provide indices of all other elements that overlap 10293 overlapMap = [[] for dummy in range(len(durSpanSorted))] 10294 10295 for i in range(len(durSpanSortedIndex)): 10296 src = durSpanSortedIndex[i] 10297 for j in range(i + 1, len(durSpanSortedIndex)): 10298 dst = durSpanSortedIndex[j] 10299 if self._durSpanOverlap(src[1], dst[1]): 10300 overlapMap[src[0]].append(dst[0]) 10301 overlapMap[dst[0]].append(src[0]) 10302 else: 10303 break 10304 10305 # Preserve exact same behaviour as earlier code. 10306 # It is unclear if anything depends on the individual lists being sorted. 10307 for ls in overlapMap: 10308 ls.sort() 10309 return overlapMap 10310 10311 def _consolidateLayering(self, layeringMap): 10312 ''' 10313 Given a stream of flat elements and a map of equal length with lists of 10314 index values that meet a given condition (overlap or simultaneities), 10315 organize into a dictionary by the relevant or first offset 10316 ''' 10317 flatStream = self.flatten() 10318 if flatStream.isSorted is False: 10319 flatStream = flatStream.sorted() 10320 10321 if len(layeringMap) != len(flatStream): 10322 raise StreamException('layeringMap must be the same length as flatStream') 10323 10324 post = {} 10325 for i in range(len(layeringMap)): 10326 # print('examining i:', i) 10327 indices = layeringMap[i] 10328 if not indices: 10329 continue 10330 10331 srcElementObj = flatStream[i] 10332 srcOffset = srcElementObj.offset 10333 dstOffset = None 10334 # print('found indices', indices) 10335 # check indices 10336 for j in indices: # indices of other elements that overlap 10337 elementObj = flatStream[j] 10338 # check if this object has been stored anywhere yet 10339 # if so, use the offset of where it was stored to 10340 # to store the src element below 10341 store = True 10342 for k in post: 10343 # this comparison needs to be based on object id, not 10344 # matching equality 10345 if id(elementObj) in [id(e) for e in post[k]]: 10346 # if elementObj in post[key]: 10347 store = False 10348 dstOffset = k 10349 break 10350 if dstOffset is None: 10351 dstOffset = srcOffset 10352 if store: 10353 # print('storing offset', dstOffset) 10354 if dstOffset not in post: 10355 post[dstOffset] = [] # create dictionary entry 10356 post[dstOffset].append(elementObj) 10357 10358 # check if this object has been stored anywhere yet 10359 store = True 10360 for k in post: 10361 if id(srcElementObj) in [id(e) for e in post[k]]: 10362 # if srcElementObj in post[key]: 10363 store = False 10364 break 10365 # dst offset may have been set when looking at indices 10366 if store: 10367 if dstOffset is None: 10368 dstOffset = srcOffset 10369 if dstOffset not in post: 10370 post[dstOffset] = [] # create dictionary entry 10371 # print('storing offset', dstOffset) 10372 post[dstOffset].append(srcElementObj) 10373 # print(post) 10374 return post 10375 10376 def findGaps(self): 10377 # noinspection PyShadowingNames 10378 ''' 10379 Returns either (1) a Stream containing empty Music21Objects 10380 whose offsets and durations 10381 are the length of gaps in the Stream 10382 or (2) None if there are no gaps. 10383 10384 N.B. there may be gaps in the flattened representation of the stream 10385 but not in the unflattened. Hence why "isSequence" calls self.flatten().isGapless 10386 10387 >>> s = stream.Stream() 10388 >>> s.insert(1.0, note.Note('E', type='half')) 10389 >>> s.insert(5.0, note.Note('F', type='whole')) 10390 >>> s.storeAtEnd(bar.Barline('final')) 10391 >>> gapStream = s.findGaps() 10392 >>> gapStream.show('text', addEndTimes=True) 10393 {0.0 - 1.0} <music21.note.Rest quarter> 10394 {3.0 - 5.0} <music21.note.Rest half> 10395 10396 Returns None if not gaps: 10397 10398 >>> s2 = stream.Stream() 10399 >>> s2.append(note.Note('G')) 10400 >>> s2.findGaps() is None 10401 True 10402 10403 Changed in v7. -- gapStream is filled with rests instead of Music21Objects 10404 ''' 10405 if 'findGaps' in self._cache and self._cache['findGaps'] is not None: 10406 return self._cache['findGaps'] 10407 10408 gapStream = self.cloneEmpty(derivationMethod='findGaps') 10409 10410 highestCurrentEndTime = 0.0 10411 for e in self: 10412 eOffset = self.elementOffset(e, returnSpecial=True) 10413 if eOffset == OffsetSpecial.AT_END: 10414 break 10415 if eOffset > highestCurrentEndTime: 10416 gapElement = note.Rest() 10417 gapQuarterLength = opFrac(eOffset - highestCurrentEndTime) 10418 gapElement.duration.quarterLength = gapQuarterLength 10419 gapStream.insert(highestCurrentEndTime, gapElement, ignoreSort=True) 10420 eDur = e.duration.quarterLength 10421 highestCurrentEndTime = opFrac(max(highestCurrentEndTime, eOffset + eDur)) 10422 10423 # TODO: Is this even necessary, we do insert the elements in sorted order 10424 # and the stream is empty at the start 10425 gapStream.sort() 10426 10427 if not gapStream: 10428 return None 10429 else: 10430 self._cache['findGaps'] = gapStream 10431 return gapStream 10432 10433 @property 10434 def isGapless(self): 10435 ''' 10436 Returns True if there are no gaps between the lowest offset and the highest time. 10437 Otherwise returns False 10438 10439 10440 >>> s = stream.Stream() 10441 >>> s.append(note.Note('C')) 10442 >>> s.append(note.Note('D')) 10443 >>> s.isGapless 10444 True 10445 >>> s.insert(10.0, note.Note('E')) 10446 >>> s.isGapless 10447 False 10448 10449 OMIT_FROM_DOCS 10450 10451 Test cache: 10452 10453 >>> s.isGapless 10454 False 10455 ''' 10456 if 'isGapless' in self._cache and self._cache['isGapless'] is not None: 10457 return self._cache['isGapless'] 10458 else: 10459 if self.findGaps() is None: 10460 self._cache['isGapless'] = True 10461 return True 10462 else: 10463 self._cache['isGapless'] = False 10464 return False 10465 10466 def getOverlaps(self): 10467 ''' 10468 Find any elements that overlap. Overlapping might include elements 10469 that have zero-length duration simultaneous. 10470 10471 This method returns a dictionary, where keys 10472 are the start time of the first overlap and 10473 value are a list of all objects included in 10474 that overlap group. 10475 10476 This example demonstrates that end-joining overlaps do not count. 10477 10478 >>> a = stream.Stream() 10479 >>> for x in range(4): 10480 ... n = note.Note('G#') 10481 ... n.duration = duration.Duration('quarter') 10482 ... n.offset = x * 1 10483 ... a.insert(n) 10484 ... 10485 >>> d = a.getOverlaps() 10486 >>> len(d) 10487 0 10488 10489 Notes starting at the same time overlap: 10490 10491 >>> a = stream.Stream() 10492 >>> for x in [0, 0, 0, 0, 13, 13, 13]: 10493 ... n = note.Note('G#') 10494 ... n.duration = duration.Duration('half') 10495 ... n.offset = x 10496 ... a.insert(n) 10497 ... 10498 >>> d = a.getOverlaps() 10499 >>> len(d[0]) 10500 4 10501 >>> len(d[13]) 10502 3 10503 >>> a = stream.Stream() 10504 >>> for x in [0, 0, 0, 0, 3, 3, 3]: 10505 ... n = note.Note('G#') 10506 ... n.duration = duration.Duration('whole') 10507 ... n.offset = x 10508 ... a.insert(n) 10509 ... 10510 10511 Default is to not include coincident boundaries 10512 10513 >>> d = a.getOverlaps() 10514 >>> len(d[0]) 10515 7 10516 10517 ''' 10518 overlapMap = self._findLayering() 10519 # environLocal.printDebug(['overlapMap', overlapMap]) 10520 10521 return self._consolidateLayering(overlapMap) 10522 10523 def isSequence(self): 10524 '''A stream is a sequence if it has no overlaps. 10525 10526 10527 >>> a = stream.Stream() 10528 >>> for x in [0, 0, 0, 0, 3, 3, 3]: 10529 ... n = note.Note('G#') 10530 ... n.duration = duration.Duration('whole') 10531 ... n.offset = x * 1 10532 ... a.insert(n) 10533 ... 10534 >>> a.isSequence() 10535 False 10536 10537 OMIT_FROM_DOCS 10538 TODO: check that co-incident boundaries are properly handled 10539 10540 >>> a = stream.Stream() 10541 >>> for x in [0, 4, 8.0]: 10542 ... n = note.Note('G#') 10543 ... n.duration = duration.Duration('whole') 10544 ... a.append(n) 10545 ... 10546 >>> a.isSequence() 10547 True 10548 ''' 10549 overlapMap = self._findLayering() 10550 post = True 10551 for indexList in overlapMap: 10552 if indexList: 10553 post = False 10554 break 10555 return post 10556 10557 # -------------------------------------------------------------------------- 10558 # routines for dealing with relationships to other streams. 10559 # Formerly in twoStreams.py 10560 10561 def simultaneousAttacks(self, stream2): 10562 ''' 10563 returns an ordered list of offsets where elements are started (attacked) 10564 at the same time in both self and stream2. 10565 10566 In this example, we create one stream of Qtr, Half, Qtr, and one of Half, Qtr, Qtr. 10567 There are simultaneous attacks at offset 0.0 (the beginning) and at offset 3.0, 10568 but not at 1.0 or 2.0: 10569 10570 10571 >>> st1 = stream.Stream() 10572 >>> st2 = stream.Stream() 10573 >>> st1.append([note.Note(type='quarter'), 10574 ... note.Note(type='half'), 10575 ... note.Note(type='quarter')]) 10576 >>> st2.append([note.Note(type='half'), 10577 ... note.Note(type='quarter'), 10578 ... note.Note(type='quarter')]) 10579 >>> print(st1.simultaneousAttacks(st2)) 10580 [0.0, 3.0] 10581 ''' 10582 stream1Offsets = iterator.OffsetIterator(self) 10583 stream2Offsets = iterator.OffsetIterator(stream2) 10584 10585 returnKey = {} 10586 10587 for thisList in stream1Offsets: 10588 thisOffset = self.elementOffset(thisList[0]) 10589 returnKey[thisOffset] = 1 10590 10591 for thatList in stream2Offsets: 10592 thatOffset = thatList[0].getOffsetBySite(stream2) 10593 if thatOffset in returnKey: 10594 returnKey[thatOffset] += 1 10595 10596 returnList = [] 10597 for foundOffset in sorted(returnKey): 10598 if returnKey[foundOffset] >= 2: 10599 returnList.append(foundOffset) 10600 return returnList 10601 10602 # this method was supposed to be faster, but actually 2000 times slower on op133 10603 10604 # sOuter = Stream() 10605 # for e in self: 10606 # sOuter.coreInsert(e.offset, e) 10607 # for e in stream2: 10608 # sOuter.coreInsert(e.offset, e) 10609 # sOuter.coreElementsChanged(updateIsFlat=False) 10610 # sOuterTree = sOuter.asTree(flatten=False, groupOffsets=True) 10611 # return sorted(sOuterTree.simultaneityDict().keys()) 10612 10613 def attachIntervalsBetweenStreams(self, cmpStream): 10614 # noinspection PyShadowingNames 10615 ''' 10616 For each element in self, creates an interval.Interval object in the element's 10617 editorial that is the interval between it and the element in cmpStream that 10618 is sounding at the moment the element in srcStream is attacked. 10619 10620 Remember that if you are comparing two streams with measures, etc., 10621 you'll need to flatten each stream as follows: 10622 10623 >>> #_DOCS_SHOW stream1.flatten().attachIntervalsBetweenStreams(stream2.flatten()) 10624 10625 Example usage: 10626 10627 10628 >>> s1 = converter.parse('tinynotation: 7/4 C4 d8 e f# g A2 d2', makeNotation=False) 10629 >>> s2 = converter.parse('tinynotation: 7/4 g4 e8 d c4 a2 r2', makeNotation=False) 10630 >>> s1.attachIntervalsBetweenStreams(s2) 10631 >>> for n in s1.notes: 10632 ... if n.editorial.harmonicInterval is None: 10633 ... print('None') # if other voice had a rest... 10634 ... else: 10635 ... print(n.editorial.harmonicInterval.directedName) 10636 P12 10637 M2 10638 M-2 10639 A-4 10640 P-5 10641 P8 10642 None 10643 ''' 10644 for n in self.notes: 10645 # get simultaneous elements form other stream 10646 simultEls = cmpStream.getElementsByOffset(self.elementOffset(n), 10647 mustBeginInSpan=False, 10648 mustFinishInSpan=False) 10649 if simultEls: 10650 for simultNote in simultEls.notes: 10651 interval1 = None 10652 try: 10653 interval1 = interval.notesToInterval(n, simultNote) 10654 n.editorial.harmonicInterval = interval1 10655 except exceptions21.Music21Exception: 10656 pass 10657 if interval1 is not None: 10658 break # inner loop 10659 10660 def attachMelodicIntervals(self): 10661 ''' 10662 For each element in self, creates an interval.Interval object in the element's 10663 editorial that is the interval between it and the previous element in the stream. Thus, 10664 the first element will have a value of None. 10665 10666 DEPRECATED sometime soon. A replacement to come presently. 10667 10668 >>> s1 = converter.parse('tinyNotation: 7/4 C4 d8 e f# g A2 d2', makeNotation=False) 10669 >>> s1.attachMelodicIntervals() 10670 >>> for n in s1.notes: 10671 ... if n.editorial.melodicInterval is None: 10672 ... print('None') 10673 ... else: 10674 ... print(n.editorial.melodicInterval.directedName) 10675 None 10676 M9 10677 M2 10678 M2 10679 m2 10680 m-7 10681 P4 10682 10683 >>> s = stream.Stream() 10684 >>> s.append(note.Note('C')) 10685 >>> s.append(note.Note('D')) 10686 >>> s.append(note.Rest(quarterLength=4.0)) 10687 >>> s.append(note.Note('D')) 10688 >>> s.attachMelodicIntervals() 10689 >>> for n in s.notes: 10690 ... if n.editorial.melodicInterval is None: 10691 ... print('None') # if other voice had a rest... 10692 ... else: 10693 ... print(n.editorial.melodicInterval.directedName) 10694 None 10695 M2 10696 P1 10697 ''' 10698 10699 notes = self.notes.stream() 10700 currentObject = notes[0] 10701 previousObject = None 10702 while currentObject is not None: 10703 if (previousObject is not None 10704 and isinstance(currentObject, note.Note) 10705 and isinstance(previousObject, note.Note)): 10706 currentObject.editorial.melodicInterval = interval.notesToInterval( 10707 previousObject, currentObject) 10708 previousObject = currentObject 10709 currentObject = currentObject.next() 10710 10711 def playingWhenAttacked(self, el, elStream=None): 10712 ''' 10713 Given an element (from another Stream) returns the single element 10714 in this Stream that is sounding while the given element starts. 10715 10716 If there are multiple elements sounding at the moment it is 10717 attacked, the method returns the first element of the same class 10718 as this element, if any. If no element 10719 is of the same class, then the first element encountered is 10720 returned. For more complex usages, use allPlayingWhileSounding. 10721 10722 Returns None if no elements fit the bill. 10723 10724 The optional elStream is the stream in which el is found. 10725 If provided, el's offset 10726 in that Stream is used. Otherwise, the current offset in 10727 el is used. It is just 10728 in case you are paranoid that el.offset might not be what 10729 you want, because of some fancy manipulation of 10730 el.activeSite 10731 10732 >>> n1 = note.Note('G#') 10733 >>> n2 = note.Note('D#') 10734 >>> s1 = stream.Stream() 10735 >>> s1.insert(20.0, n1) 10736 >>> s1.insert(21.0, n2) 10737 10738 >>> n3 = note.Note('C#') 10739 >>> s2 = stream.Stream() 10740 >>> s2.insert(20.0, n3) 10741 >>> s1.playingWhenAttacked(n3) 10742 <music21.note.Note G#> 10743 10744 >>> n3.setOffsetBySite(s2, 20.5) 10745 >>> s1.playingWhenAttacked(n3) 10746 <music21.note.Note G#> 10747 10748 >>> n3.setOffsetBySite(s2, 21.0) 10749 >>> n3.offset 10750 21.0 10751 >>> s1.playingWhenAttacked(n3) 10752 <music21.note.Note D#> 10753 10754 If there is more than one item at the same time in the other stream 10755 then the first item matching the same class is returned, even if 10756 another element has a closer offset. 10757 10758 >>> n3.setOffsetBySite(s2, 20.5) 10759 >>> s1.insert(20.5, clef.BassClef()) 10760 >>> s1.playingWhenAttacked(n3) 10761 <music21.note.Note G#> 10762 >>> fc = clef.FClef() # superclass of BassClef 10763 >>> s2.insert(20.5, fc) 10764 >>> s1.playingWhenAttacked(fc) 10765 <music21.clef.BassClef> 10766 10767 But since clefs have zero duration, moving the FClef ever so slightly 10768 will find the note instead 10769 10770 >>> fc.setOffsetBySite(s2, 20.6) 10771 >>> s1.playingWhenAttacked(fc) 10772 <music21.note.Note G#> 10773 10774 10775 Optionally, specify the site to get the offset from: 10776 10777 >>> n3.setOffsetBySite(s2, 21.0) 10778 >>> n3.setOffsetBySite(None, 100) 10779 >>> n3.activeSite = None 10780 >>> s1.playingWhenAttacked(n3) is None 10781 True 10782 >>> s1.playingWhenAttacked(n3, s2).name 10783 'D#' 10784 ''' 10785 if elStream is not None: # bit of safety 10786 elOffset = el.getOffsetBySite(elStream) 10787 else: 10788 elOffset = el.offset 10789 10790 otherElements = self.getElementsByOffset(elOffset, mustBeginInSpan=False) 10791 if not otherElements: 10792 return None 10793 elif len(otherElements) == 1: 10794 return otherElements[0] 10795 else: 10796 for thisEl in otherElements: 10797 if isinstance(thisEl, el.__class__): 10798 return thisEl 10799 return otherElements[0] 10800 10801 def allPlayingWhileSounding(self, el, elStream=None): 10802 ''' 10803 Returns a new Stream of elements in this stream that sound 10804 at the same time as `el`, an element presumably in another Stream. 10805 10806 The offset of this new Stream is set to el's offset, while the 10807 offset of elements within the Stream are adjusted relative to 10808 their position with respect to the start of el. Thus, a note 10809 that is sounding already when el begins would have a negative 10810 offset. The duration of otherStream is forced 10811 to be the length of el -- thus a note sustained after el ends 10812 may have a release time beyond that of the duration of the Stream. 10813 10814 As above, elStream is an optional Stream to look up el's offset in. Use 10815 this to work on an element in another part. 10816 10817 The method always returns a Stream, but it might be an empty Stream. 10818 10819 OMIT_FROM_DOCS 10820 TODO: write: requireClass: 10821 Takes as an optional parameter "requireClass". If this parameter 10822 is boolean True then only elements 10823 of the same class as el are added to the new Stream. If requireClass 10824 is list, it is used like 10825 classList in elsewhere in stream to provide a list of classes that the 10826 el must be a part of. 10827 10828 ''' 10829 if elStream is not None: # bit of safety 10830 elOffset = el.getOffsetBySite(elStream) 10831 else: 10832 elOffset = el.offset 10833 elEnd = elOffset + el.quarterLength 10834 10835 if elEnd != elOffset: # i.e. not zero length 10836 otherElements = self.getElementsByOffset( 10837 elOffset, 10838 elEnd, 10839 mustBeginInSpan=False, 10840 includeEndBoundary=False, 10841 includeElementsThatEndAtStart=False).stream() 10842 else: 10843 otherElements = self.getElementsByOffset(elOffset, 10844 mustBeginInSpan=False).stream() 10845 10846 otherElements.offset = elOffset 10847 otherElements.quarterLength = el.quarterLength 10848 for thisEl in otherElements: 10849 thisEl.offset = thisEl.offset - elOffset 10850 10851 return otherElements 10852 10853 # -------------------------------------------------------------------------- 10854 # voice processing routines 10855 10856 def makeVoices(self, *, inPlace=False, fillGaps=True): 10857 ''' 10858 If this Stream has overlapping Notes or Chords, this method will isolate 10859 all overlaps in unique Voices, and place those Voices in the Stream. 10860 10861 >>> s = stream.Stream() 10862 >>> s.insert(0, note.Note('C4', quarterLength=4)) 10863 >>> s.repeatInsert(note.Note('b-4', quarterLength=0.5), [x * 0.5 for x in list(range(8))]) 10864 >>> s.makeVoices(inPlace=True) 10865 >>> len(s.voices) 10866 2 10867 >>> [n.pitch for n in s.voices[0].notes] 10868 [<music21.pitch.Pitch C4>] 10869 >>> [str(n.pitch) for n in s.voices[1].notes] 10870 ['B-4', 'B-4', 'B-4', 'B-4', 'B-4', 'B-4', 'B-4', 'B-4'] 10871 10872 Changed in v7 -- if `fillGaps=True` and called on an incomplete measure, 10873 makes trailing rests in voices. This scenario occurs when parsing MIDI. 10874 ''' 10875 # this method may not always 10876 # produce the optimal voice assignment based on context (register 10877 # chord formation, etc 10878 if not inPlace: # make a copy 10879 returnObj = self.coreCopyAsDerivation('makeVoices') 10880 else: 10881 returnObj = self 10882 # must be sorted 10883 if not returnObj.isSorted: 10884 returnObj.sort() 10885 olDict = returnObj.notes.stream().getOverlaps() 10886 # environLocal.printDebug(['makeVoices(): olDict', olDict]) 10887 # find the max necessary voices by finding the max number 10888 # of elements in each group; these may not all be necessary 10889 maxVoiceCount = 1 10890 for group in olDict.values(): 10891 if len(group) > maxVoiceCount: 10892 maxVoiceCount = len(group) 10893 if maxVoiceCount == 1: # nothing to do here 10894 if not inPlace: 10895 return returnObj 10896 return None 10897 10898 # store all voices in a list 10899 voices = [] 10900 for dummy in range(maxVoiceCount): 10901 voices.append(Voice()) # add voice classes 10902 10903 # iterate through all elements; if not in an overlap, place in 10904 # voice 1, otherwise, distribute 10905 for e in returnObj.notes: 10906 o = e.getOffsetBySite(returnObj) 10907 # cannot match here by offset, as olDict keys are representative 10908 # of the first overlapped offset, not all contained offsets 10909 # if o not in olDict: # place in a first voices 10910 # voices[0].insert(o, e) 10911 # find a voice to place in 10912 # as elements are sorted, can use the highest time 10913 # else: 10914 for v in voices: 10915 if v.highestTime <= o: 10916 v.insert(o, e) 10917 break 10918 # remove from source 10919 returnObj.remove(e) 10920 # remove any unused voices (possible if overlap group has sus) 10921 for v in voices: 10922 if v: # skip empty voices 10923 returnObj.insert(0, v) 10924 if fillGaps: 10925 returnObj.makeRests(fillGaps=True, 10926 inPlace=True, 10927 timeRangeFromBarDuration=True, 10928 ) 10929 # remove rests in returnObj 10930 returnObj.removeByClass('Rest') 10931 # elements changed will already have been called 10932 if not inPlace: 10933 return returnObj 10934 10935 def _maxVoiceCount(self, *, countById=False): 10936 ''' 10937 Returns the maximum number of voices in a part. Used by voicesToParts. 10938 Minimum returned is 1. If `countById` is True, returns a tuple of 10939 (maxVoiceCount, voiceIdList) 10940 10941 >>> import copy 10942 10943 >>> p0 = stream.Part() 10944 >>> p0._maxVoiceCount() 10945 1 10946 >>> v0 = stream.Voice(id='v0') 10947 >>> p0.insert(0, v0) 10948 >>> p0._maxVoiceCount() 10949 1 10950 >>> v1 = stream.Voice(id='v1') 10951 >>> p0.insert(0, v1) 10952 >>> p0._maxVoiceCount() 10953 2 10954 10955 >>> p1 = stream.Part() 10956 >>> m1 = stream.Measure(number=1) 10957 >>> p1.insert(0, m1) 10958 >>> p1._maxVoiceCount() 10959 1 10960 >>> m1.insert(0, v0) 10961 >>> p1._maxVoiceCount() 10962 1 10963 >>> m1.insert(0, v1) 10964 >>> p1._maxVoiceCount() 10965 2 10966 >>> m2 = stream.Measure(number=2) 10967 >>> p1.append(m2) 10968 >>> p1._maxVoiceCount() 10969 2 10970 >>> v01 = copy.copy(v0) 10971 >>> v11 = stream.Voice(id='v11') 10972 >>> m2.insert(0, v01) 10973 >>> m2.insert(0, v11) 10974 >>> p1._maxVoiceCount() 10975 2 10976 >>> v2 = stream.Voice(id='v2') 10977 >>> m2.insert(0, v2) 10978 >>> p1._maxVoiceCount() 10979 3 10980 10981 If `countById` is True then different voice ids create different parts. 10982 10983 >>> mvc, vIds = p1._maxVoiceCount(countById=True) 10984 >>> mvc 10985 4 10986 >>> vIds 10987 ['v0', 'v1', 'v11', 'v2'] 10988 10989 >>> v01.id = 'v01' 10990 >>> mvc, vIds = p1._maxVoiceCount(countById=True) 10991 >>> mvc 10992 5 10993 >>> vIds 10994 ['v0', 'v1', 'v01', 'v11', 'v2'] 10995 ''' 10996 voiceCount = 1 10997 voiceIds = [] 10998 10999 if self.hasMeasures(): 11000 for m in self.getElementsByClass('Measure'): 11001 mVoices = m.voices 11002 mVCount = len(mVoices) 11003 if not countById: 11004 voiceCount = max(mVCount, voiceCount) 11005 else: 11006 for v in mVoices: 11007 if v.id not in voiceIds: 11008 voiceIds.append(v.id) 11009 11010 elif self.hasVoices(): 11011 voices = self.voices 11012 if not countById: 11013 voiceCount = len(voices) 11014 else: 11015 voiceIds = [v.id for v in voices] 11016 else: # if no measure or voices, get one part 11017 voiceCount = 1 11018 11019 voiceCount = max(voiceCount, len(voiceIds)) 11020 11021 if not countById: 11022 return voiceCount 11023 else: 11024 return voiceCount, voiceIds 11025 11026 def voicesToParts(self, *, separateById=False): 11027 # noinspection PyShadowingNames 11028 ''' 11029 If this Stream defines one or more voices, 11030 extract each into a Part, returning a Score. 11031 11032 If this Stream has no voices, return the Stream as a Part within a Score. 11033 11034 >>> c = corpus.parse('demos/two-voices') 11035 >>> c.show('t') 11036 {0.0} <music21.text.TextBox 'Music21 Fr...'> 11037 {0.0} <music21.text.TextBox 'Music21'> 11038 {0.0} <music21.metadata.Metadata object at 0x109ce1630> 11039 {0.0} <music21.stream.Part Piano> 11040 {0.0} <music21.instrument.Instrument 'P1: Piano: '> 11041 {0.0} <music21.stream.Measure 1 offset=0.0> 11042 {0.0} <music21.layout.PageLayout> 11043 {0.0} <music21.layout.SystemLayout> 11044 ... 11045 {0.0} <music21.clef.BassClef> 11046 {0.0} <music21.key.Key of D major> 11047 {0.0} <music21.meter.TimeSignature 4/4> 11048 {0.0} <music21.stream.Voice 3> 11049 {0.0} <music21.note.Note E> 11050 ... 11051 {3.0} <music21.note.Rest quarter> 11052 {0.0} <music21.stream.Voice 4> 11053 {0.0} <music21.note.Note F#> 11054 ... 11055 {3.5} <music21.note.Note B> 11056 {4.0} <music21.stream.Measure 2 offset=4.0> 11057 {0.0} <music21.stream.Voice 3> 11058 {0.0} <music21.note.Note E> 11059 ... 11060 {3.0} <music21.note.Rest quarter> 11061 {0.0} <music21.stream.Voice 4> 11062 {0.0} <music21.note.Note E> 11063 ... 11064 {3.5} <music21.note.Note A> 11065 {8.0} <music21.stream.Measure 3 offset=8.0> 11066 {0.0} <music21.note.Rest whole> 11067 {4.0} <music21.bar.Barline type=final> 11068 {0.0} <music21.layout.ScoreLayout> 11069 11070 >>> ce = c.voicesToParts() 11071 >>> ce.show('t') 11072 {0.0} <music21.stream.Part Piano-v0> 11073 {0.0} <music21.instrument.Instrument 'P1: Piano: '> 11074 {0.0} <music21.stream.Measure 1 offset=0.0> 11075 {0.0} <music21.clef.TrebleClef> 11076 {0.0} <music21.key.Key of D major> 11077 {0.0} <music21.meter.TimeSignature 4/4> 11078 {0.0} <music21.note.Note E> 11079 ... 11080 {3.0} <music21.note.Rest quarter> 11081 {4.0} <music21.stream.Measure 2 offset=4.0> 11082 {0.0} <music21.note.Note E> 11083 ... 11084 {3.0} <music21.note.Rest quarter> 11085 {8.0} <music21.stream.Measure 3 offset=8.0> 11086 {0.0} <music21.note.Rest whole> 11087 {4.0} <music21.bar.Barline type=final> 11088 {0.0} <music21.stream.Part Piano-v1> 11089 {0.0} <music21.instrument.Instrument 'P1: Piano: '> 11090 {0.0} <music21.stream.Measure 1 offset=0.0> 11091 {0.0} <music21.clef.BassClef> 11092 {0.0} <music21.key.Key of D major> 11093 ... 11094 {3.5} <music21.note.Note B> 11095 {4.0} <music21.stream.Measure 2 offset=4.0> 11096 {0.0} <music21.note.Note E> 11097 ... 11098 {3.5} <music21.note.Note A> 11099 {8.0} <music21.stream.Measure 3 offset=8.0> 11100 {0.0} <music21.bar.Barline type=final> 11101 <BLANKLINE> 11102 11103 If `separateById` is True then all voices with the same id 11104 will be connected to the same Part, regardless of order 11105 they appear in the measure. 11106 11107 Compare the previous output: 11108 11109 >>> p0pitches = ce.parts[0].pitches 11110 >>> p1pitches = ce.parts[1].pitches 11111 >>> ' '.join([p.nameWithOctave for p in p0pitches]) 11112 'E4 D#4 D#4 E4 F#4 E4 B3 B3 E4 E4' 11113 >>> ' '.join([p.nameWithOctave for p in p1pitches]) 11114 'F#2 F#3 E3 E2 D#2 D#3 B2 B3 E2 E3 D3 D2 C#2 C#3 A2 A3' 11115 11116 Swap voice ids in first measure: 11117 11118 >>> m0 = c.parts[0].getElementsByClass('Measure').first() 11119 >>> m0.voices[0].id, m0.voices[1].id 11120 ('3', '4') 11121 >>> m0.voices[0].id = '4' 11122 >>> m0.voices[1].id = '3' 11123 11124 Now run voicesToParts with `separateById=True` 11125 11126 >>> ce = c.voicesToParts(separateById=True) 11127 >>> p0pitches = ce.parts[0].pitches 11128 >>> p1pitches = ce.parts[1].pitches 11129 >>> ' '.join([p.nameWithOctave for p in p0pitches]) 11130 'E4 D#4 D#4 E4 F#4 E2 E3 D3 D2 C#2 C#3 A2 A3' 11131 >>> ' '.join([p.nameWithOctave for p in p1pitches]) 11132 'F#2 F#3 E3 E2 D#2 D#3 B2 B3 E4 B3 B3 E4 E4' 11133 11134 Note that the second and subsequent measure's pitches were changed 11135 not the first, because separateById aligns the voices according to 11136 order first encountered, not by sorting the Ids. 11137 ''' 11138 s = Score() 11139 # s.metadata = self.metadata 11140 11141 # if this is a Score, call this recursively on each Part, then 11142 # add all parts to one Score 11143 if self.hasPartLikeStreams(): 11144 # part-like does not necessarily mean .parts 11145 for p in self.getElementsByClass('Stream'): 11146 sSub = p.voicesToParts(separateById=separateById) 11147 for pSub in sSub: 11148 s.insert(0, pSub) 11149 return s 11150 11151 # need to find maximum voice count 11152 mvcReturn = self._maxVoiceCount(countById=separateById) 11153 if not separateById: 11154 partCount = mvcReturn 11155 voiceIds = [] 11156 else: 11157 partCount, voiceIds = mvcReturn 11158 11159 # environLocal.printDebug(['voicesToParts(): got partCount', partCount]) 11160 11161 # create parts, naming ids by voice id? 11162 partDict = {} 11163 11164 for i in range(partCount): 11165 p = Part() 11166 s.insert(0, p) 11167 if not separateById: 11168 p.id = str(self.id) + '-v' + str(i) 11169 partDict[i] = p 11170 else: 11171 voiceId = voiceIds[i] 11172 p.id = str(self.id) + '-' + voiceId 11173 partDict[voiceId] = p 11174 11175 def doOneMeasureWithVoices(mInner): 11176 ''' 11177 This is the main routine for dealing with the most common 11178 and most difficult voice set. 11179 ''' 11180 mActive = Measure() 11181 mActive.mergeAttributes(mInner) # get groups, optional id 11182 # merge everything except Voices; this will get 11183 # clefs 11184 mActive.mergeElements( 11185 mInner, 11186 classFilterList=( 11187 'Barline', 'TimeSignature', 'Clef', 'KeySignature', 11188 ) 11189 ) 11190 11191 # vIndexInner = 0 should not be necessary, but pylint warns on loop variables 11192 # that could possibly be undefined used out of the loop. 11193 vIndexInner = 0 11194 11195 seenIdsThisMeasure = set() 11196 for vIndexInner, vInner in enumerate(mInner.voices): 11197 # TODO(msc): fix bugs if same voice id appears twice in same measure 11198 11199 # make an independent copy 11200 mNewInner = copy.deepcopy(mActive) 11201 # merge all elements from the voice 11202 mNewInner.mergeElements(vInner) 11203 # insert in the appropriate part 11204 vId = vInner.id 11205 if not separateById: 11206 pInner = partDict[vIndexInner] 11207 else: 11208 seenIdsThisMeasure.add(vId) 11209 pInner = partDict[vId] 11210 pInner.insert(self.elementOffset(mInner), mNewInner) 11211 11212 # vIndexInner is now the number of voices - 1. Fill empty voices 11213 if not separateById: 11214 for emptyIndex in range(vIndexInner + 1, partCount): 11215 pInner = partDict[emptyIndex] 11216 pInner.insert(self.elementOffset(mInner), copy.deepcopy(mActive)) 11217 else: 11218 for voiceIdInner in partDict: 11219 if voiceIdInner in seenIdsThisMeasure: 11220 continue 11221 pInner = partDict[voiceIdInner] 11222 pInner.insert(self.elementOffset(mInner), copy.deepcopy(mActive)) 11223 11224 # Place references to any instruments from the original part into the new parts 11225 for p in s.parts: 11226 p.mergeElements(self, classFilterList=('Instrument',)) 11227 11228 if self.hasMeasures(): 11229 for m in self.getElementsByClass('Measure'): 11230 if m.hasVoices(): 11231 doOneMeasureWithVoices(m) 11232 # if a measure does not have voices, simply populate 11233 # with elements and append 11234 else: 11235 mNew = Measure() 11236 mNew.mergeAttributes(m) # get groups, optional id 11237 # get all elements 11238 mNew.mergeElements(m) 11239 # always place in top-part 11240 s.parts[0].insert(self.elementOffset(m), mNew) 11241 for i in range(1, partCount): 11242 mEmpty = Measure() 11243 mEmpty.mergeAttributes(m) 11244 # Propagate bar, meter, key elements to lower parts 11245 mEmpty.mergeElements(m, classFilterList=('Barline', 11246 'TimeSignature', 'KeySignature')) 11247 s.parts[i].insert(self.elementOffset(m), mEmpty) 11248 # if part has no measures but has voices, contents of each voice go into the part 11249 elif self.hasVoices(): 11250 for vIndex, v in enumerate(self.voices): 11251 s.parts[vIndex].mergeElements(v) 11252 # if just a Stream of elements, add to a part 11253 else: 11254 s.parts[0].mergeElements(self) 11255 11256 # there is no way to assure proper clef information, so using 11257 # best clef here is desirable. 11258 for p in s.parts: 11259 # only add clef if measures are defined; otherwise, assume 11260 # best clef will be assigned later 11261 if p.hasMeasures(): 11262 # place in first measure 11263 p.getElementsByClass('Measure').first().clef = clef.bestClef(p, recurse=True) 11264 return s 11265 11266 def explode(self): 11267 ''' 11268 Create a multi-part extraction from a single polyphonic Part. 11269 11270 Currently just runs :meth:`~music21.stream.Stream.voicesToParts` 11271 but that will change as part explosion develops, and this 11272 method will use our best available quick method for part 11273 extraction. 11274 ''' 11275 return self.voicesToParts() 11276 11277 def flattenUnnecessaryVoices(self, *, force=False, inPlace=False): 11278 ''' 11279 If this Stream defines one or more internal voices, do the following: 11280 11281 * If there is more than one voice, and a voice has no elements, 11282 remove that voice. 11283 * If there is only one voice left that has elements, place those 11284 elements in the parent Stream. 11285 * If `force` is True, even if there is more than one Voice left, 11286 all voices will be flattened. 11287 11288 Changed in v. 5 -- inPlace is default False and a keyword only arg. 11289 11290 >>> s = stream.Stream(note.Note()) 11291 >>> s.insert(0, note.Note()) 11292 >>> s.insert(0, note.Note()) 11293 >>> s.makeVoices(inPlace=True) 11294 >>> len(s.voices) 11295 3 11296 11297 >>> s.remove(s.voices[1].notes[0], recurse=True) 11298 >>> s.remove(s.voices[2].notes[0], recurse=True) 11299 >>> voicesFlattened = s.flattenUnnecessaryVoices() 11300 >>> len(voicesFlattened.voices) 11301 0 11302 ''' 11303 if not self.voices: 11304 return None # do not make copy; return immediately 11305 11306 if not inPlace: # make a copy 11307 returnObj = copy.deepcopy(self) 11308 else: 11309 returnObj = self 11310 11311 # collect voices for removal and for flattening 11312 remove = [] 11313 flatten = [] 11314 for v in returnObj.voices: 11315 if not v: # might add other criteria 11316 remove.append(v) 11317 else: 11318 flatten.append(v) 11319 11320 for v in remove: 11321 returnObj.remove(v) 11322 11323 if len(flatten) == 1 or force: # always flatten 1 11324 for v in flatten: # usually one unless force 11325 # get offset of voice in returnObj 11326 shiftOffset = v.getOffsetBySite(returnObj) 11327 for e in v.elements: 11328 # insert shift + offset w/ voice 11329 returnObj.coreInsert(shiftOffset + e.getOffsetBySite(v), e) 11330 returnObj.remove(v) 11331 returnObj.coreElementsChanged() 11332 11333 if not inPlace: 11334 return returnObj 11335 else: 11336 return None 11337 11338 # -------------------------------------------------------------------------- 11339 # Lyric control 11340 # might be overwritten in base.splitAtDurations, but covered with a check 11341 # pylint: disable=method-hidden 11342 def lyrics(self, ignoreBarlines=True, recurse=False, skipTies=False): 11343 # noinspection PyShadowingNames 11344 ''' 11345 Returns a dict of lists of lyric objects (with the keys being 11346 the lyric numbers) found in self. Each list will have an element for each 11347 note in the stream (which may be a note.Lyric() or None). 11348 By default, this method automatically 11349 recurses through measures, but not other container streams. 11350 11351 11352 >>> s = converter.parse('tinynotation: 4/4 a4 b c d e f g a', makeNotation=False) 11353 >>> someLyrics = ['this', 'is', 'a', 'list', 'of', 'eight', 'lyric', 'words'] 11354 >>> for n, lyric in zip(s.notes, someLyrics): 11355 ... n.lyric = lyric 11356 11357 11358 >>> s.lyrics() 11359 {1: [<music21.note.Lyric number=1 syllabic=single text='this'>, ..., 11360 <music21.note.Lyric number=1 syllabic=single text='words'>]} 11361 11362 >>> s.notes[3].lyric = None 11363 >>> s.lyrics()[1] 11364 [<music21.note.Lyric number=1 syllabic=single text='this'>, ..., None, ..., 11365 <music21.note.Lyric number=1 syllabic=single text='words'>] 11366 11367 If ignoreBarlines is True, it will behave as if the elements in measures are all 11368 in a flattened stream (note that this is not stream.flatten() 11369 as it does not copy the elements) 11370 together without measure containers. This means that even if recurse is 11371 False, lyrics() will still essentially recurse through measures. 11372 11373 >>> s.makeMeasures(inPlace=True) 11374 >>> s.lyrics()[1] 11375 [<music21.note.Lyric number=1 syllabic=single text='this'>, ..., None, ..., 11376 <music21.note.Lyric number=1 syllabic=single text='words'>] 11377 11378 >>> list(s.lyrics(ignoreBarlines=False).keys()) 11379 [] 11380 11381 If recurse is True, this method will recurse through all container streams and 11382 build a nested list structure mirroring the hierarchy of the stream. 11383 Note that if ignoreBarlines is True, measure structure will not be reflected 11384 in the hierarchy, although if ignoreBarlines is False, it will. 11385 11386 Note that streams which do not contain any instance of a lyric number will not 11387 appear anywhere in the final list (not as a [] or otherwise). 11388 11389 >>> scr = stream.Score(s) 11390 11391 >>> scr.lyrics(ignoreBarlines=False, recurse=True)[1] 11392 [[[<music21.note.Lyric number=1 syllabic=single text='this'>, <...'is'>, <...'a'>, None], 11393 [<...'of'>, <...'eight'>, <...'lyric'>, <...'words'>]]] 11394 11395 Notice that the measures are nested in the part which is nested in the score. 11396 11397 >>> scr.lyrics(ignoreBarlines=True, recurse=True)[1] 11398 [[<music21.note.Lyric number=1 syllabic=single text='this'>, <...'is'>, <...'a'>, None, 11399 <...'of'>, <...'eight'>, <...'lyric'>, <...'words'>]] 11400 11401 Notice that this time, the measure structure is ignored. 11402 11403 >>> list(scr.lyrics(ignoreBarlines=True, recurse=False).keys()) 11404 [] 11405 11406 ''' 11407 returnLists = {} 11408 numNotes = 0 11409 11410 # -------------------- 11411 11412 # noinspection PyShadowingNames 11413 def appendLyricsFromNote(n, returnLists, numNonesToAppend): 11414 if not n.lyrics: 11415 for k in returnLists: 11416 returnLists[k].append(None) 11417 return 11418 11419 addLyricNums = [] 11420 for ly in n.lyrics: 11421 if ly.number not in returnLists: 11422 returnLists[ly.number] = [None for dummy in range(numNonesToAppend)] 11423 returnLists[ly.number].append(ly) 11424 addLyricNums.append(ly.number) 11425 for k in returnLists: 11426 if k not in addLyricNums: 11427 returnLists[k].append(None) 11428 11429 # ----------------------- 11430 # TODO: use new recurse 11431 for e in self: 11432 eClasses = e.classes 11433 if ignoreBarlines is True and 'Measure' in eClasses: 11434 m = e 11435 for n in m.notes: 11436 if skipTies is True: 11437 if n.tie is None or n.tie.type == 'start': 11438 appendLyricsFromNote(n, returnLists, numNotes) 11439 numNotes += 1 11440 else: 11441 pass # do nothing if end tie and skipTies is True 11442 else: 11443 appendLyricsFromNote(n, returnLists, numNotes) 11444 numNotes += 1 11445 11446 elif recurse is True and 'Stream' in eClasses: 11447 s = e 11448 sublists = s.lyrics(ignoreBarlines=ignoreBarlines, recurse=True, skipTies=skipTies) 11449 for k in sublists: 11450 if k not in returnLists: 11451 returnLists[k] = [] 11452 returnLists[k].append(sublists[k]) 11453 elif 'NotRest' in eClasses: # elif 'Stream' not in eClasses and hasattr(e, 'lyrics'): 11454 # noinspection PyTypeChecker 11455 n: 'music21.note.NotRest' = e 11456 if skipTies is True: 11457 if n.tie is None or n.tie.type == 'start': 11458 appendLyricsFromNote(n, returnLists, numNotes) 11459 numNotes += 1 11460 else: 11461 pass # do nothing if end tie and skipTies is True 11462 else: 11463 appendLyricsFromNote(n, returnLists, numNotes) 11464 numNotes += 1 11465 else: 11466 # e is a stream 11467 # (could be a measure if ignoreBarlines is False) and recurse is False 11468 pass # do nothing 11469 11470 return returnLists 11471 11472 # -------------------------------------------------------------------------- 11473 # Variant control 11474 @property 11475 def variants(self): 11476 ''' 11477 Return a Stream containing all :class:`~music21.variant.Variant` objects in this Stream. 11478 11479 >>> s = stream.Stream() 11480 >>> s.repeatAppend(note.Note('C4'), 8) 11481 >>> v1 = variant.Variant([note.Note('D#4'), note.Note('F#4')]) 11482 >>> s.insert(3, v1) 11483 11484 >>> varStream = s.variants 11485 >>> len(varStream) 11486 1 11487 >>> varStream[0] is v1 11488 True 11489 >>> len(s.variants[0]) 11490 2 11491 11492 Note that the D# and F# aren't found in the original Stream's pitches 11493 11494 >>> [str(p) for p in s.pitches] 11495 ['C4', 'C4', 'C4', 'C4', 'C4', 'C4', 'C4', 'C4'] 11496 11497 DEPRECATED in v7. 11498 ''' 11499 if 'variants' not in self._cache or self._cache['variants'] is None: 11500 self._cache['variants'] = self.getElementsByClass('Variant').stream( 11501 returnStreamSubClass=False) 11502 return self._cache['variants'] 11503 11504 # ---- Variant Activation Methods 11505 11506 def activateVariants(self, group=None, *, matchBySpan=True, inPlace=False): 11507 ''' 11508 For any :class:`~music21.variant.Variant` objects defined in this Stream 11509 (or selected by matching the `group` parameter), 11510 replace elements defined in the Variant with those in the calling Stream. 11511 Elements replaced will be gathered into a new Variant 11512 given the group 'default'. If a variant is activated with 11513 .replacementDuration different from its length, the appropriate elements 11514 in the stream will have their offsets shifted, and measure numbering 11515 will be fixed. If matchBySpan is True, variants with lengthType 11516 'replacement' will replace all of the elements in the 11517 replacement region of comparable class. If matchBySpan is False, 11518 elements will be swapped in when a match is found between an element 11519 in the variant and an element in the replacement region of the string. 11520 11521 >>> sStr = 'd4 e4 f4 g4 a2 b-4 a4 g4 a8 g8 f4 e4 d2 a2 ' 11522 >>> v1Str = ' a2. b-8 a8 ' 11523 >>> v2Str1 = ' d4 f4 a2 ' 11524 >>> v2Str2 = ' d4 f4 AA2 ' 11525 11526 >>> sStr += "d4 e4 f4 g4 a2 b-4 a4 g4 a8 b-8 c'4 c4 f1" 11527 11528 >>> s = converter.parse('tinynotation: 4/4 ' + sStr, makeNotation=False) 11529 >>> s.makeMeasures(inPlace=True) # maybe not necessary? 11530 >>> v1stream = converter.parse('tinynotation: 4/4 ' + v1Str, makeNotation=False) 11531 >>> v2stream1 = converter.parse('tinynotation: 4/4 ' + v2Str1, makeNotation=False) 11532 >>> v2stream2 = converter.parse('tinynotation: 4/4 ' + v2Str2, makeNotation=False) 11533 11534 11535 >>> v1 = variant.Variant() 11536 >>> v1measure = stream.Measure() 11537 >>> v1.insert(0.0, v1measure) 11538 >>> for e in v1stream.notesAndRests: 11539 ... v1measure.insert(e.offset, e) 11540 11541 >>> v2 = variant.Variant() 11542 >>> v2measure1 = stream.Measure() 11543 >>> v2measure2 = stream.Measure() 11544 >>> v2.insert(0.0, v2measure1) 11545 >>> v2.insert(4.0, v2measure2) 11546 >>> for e in v2stream1.notesAndRests: 11547 ... v2measure1.insert(e.offset, e) 11548 >>> for e in v2stream2.notesAndRests: 11549 ... v2measure2.insert(e.offset, e) 11550 11551 >>> v3 = variant.Variant() 11552 >>> v2.replacementDuration = 4.0 11553 >>> v3.replacementDuration = 4.0 11554 >>> v1.groups = ['docVariants'] 11555 >>> v2.groups = ['docVariants'] 11556 >>> v3.groups = ['docVariants'] 11557 11558 >>> s.insert(4.0, v1) # replacement variant 11559 >>> s.insert(12.0, v2) # insertion variant (2 bars replace 1 bar) 11560 >>> s.insert(20.0, v3) # deletion variant (0 bars replace 1 bar) 11561 >>> s.show('text') 11562 {0.0} <music21.stream.Measure 1 offset=0.0> 11563 {0.0} <music21.clef.TrebleClef> 11564 {0.0} <music21.meter.TimeSignature 4/4> 11565 {0.0} <music21.note.Note D> 11566 {1.0} <music21.note.Note E> 11567 {2.0} <music21.note.Note F> 11568 {3.0} <music21.note.Note G> 11569 {4.0} <music21.variant.Variant object of length 4.0> 11570 {4.0} <music21.stream.Measure 2 offset=4.0> 11571 {0.0} <music21.note.Note A> 11572 {2.0} <music21.note.Note B-> 11573 {3.0} <music21.note.Note A> 11574 {8.0} <music21.stream.Measure 3 offset=8.0> 11575 {0.0} <music21.note.Note G> 11576 {1.0} <music21.note.Note A> 11577 {1.5} <music21.note.Note G> 11578 {2.0} <music21.note.Note F> 11579 {3.0} <music21.note.Note E> 11580 {12.0} <music21.variant.Variant object of length 8.0> 11581 {12.0} <music21.stream.Measure 4 offset=12.0> 11582 {0.0} <music21.note.Note D> 11583 {2.0} <music21.note.Note A> 11584 {16.0} <music21.stream.Measure 5 offset=16.0> 11585 {0.0} <music21.note.Note D> 11586 {1.0} <music21.note.Note E> 11587 {2.0} <music21.note.Note F> 11588 {3.0} <music21.note.Note G> 11589 {20.0} <music21.variant.Variant object of length 0.0> 11590 {20.0} <music21.stream.Measure 6 offset=20.0> 11591 {0.0} <music21.note.Note A> 11592 {2.0} <music21.note.Note B-> 11593 {3.0} <music21.note.Note A> 11594 {24.0} <music21.stream.Measure 7 offset=24.0> 11595 {0.0} <music21.note.Note G> 11596 {1.0} <music21.note.Note A> 11597 {1.5} <music21.note.Note B-> 11598 {2.0} <music21.note.Note C> 11599 {3.0} <music21.note.Note C> 11600 {28.0} <music21.stream.Measure 8 offset=28.0> 11601 {0.0} <music21.note.Note F> 11602 {4.0} <music21.bar.Barline type=final> 11603 11604 >>> docVariant = s.activateVariants('docVariants') 11605 11606 >>> #_DOCS_SHOW s.show() 11607 11608 .. image:: images/stream_activateVariants1.* 11609 :width: 600 11610 11611 >>> #_DOCS_SHOW docVariant.show() 11612 11613 .. image:: images/stream_activateVariants2.* 11614 :width: 600 11615 11616 >>> docVariant.show('text') 11617 {0.0} <music21.stream.Measure 1 offset=0.0> 11618 {0.0} <music21.clef.TrebleClef> 11619 {0.0} <music21.meter.TimeSignature 4/4> 11620 {0.0} <music21.note.Note D> 11621 {1.0} <music21.note.Note E> 11622 {2.0} <music21.note.Note F> 11623 {3.0} <music21.note.Note G> 11624 {4.0} <music21.variant.Variant object of length 4.0> 11625 {4.0} <music21.stream.Measure 2 offset=4.0> 11626 {0.0} <music21.note.Note A> 11627 {3.0} <music21.note.Note B-> 11628 {3.5} <music21.note.Note A> 11629 {8.0} <music21.stream.Measure 3 offset=8.0> 11630 {0.0} <music21.note.Note G> 11631 {1.0} <music21.note.Note A> 11632 {1.5} <music21.note.Note G> 11633 {2.0} <music21.note.Note F> 11634 {3.0} <music21.note.Note E> 11635 {12.0} <music21.variant.Variant object of length 4.0> 11636 {12.0} <music21.stream.Measure 4 offset=12.0> 11637 {0.0} <music21.note.Note D> 11638 {1.0} <music21.note.Note F> 11639 {2.0} <music21.note.Note A> 11640 {16.0} <music21.stream.Measure 5 offset=16.0> 11641 {0.0} <music21.note.Note D> 11642 {1.0} <music21.note.Note F> 11643 {2.0} <music21.note.Note A> 11644 {20.0} <music21.stream.Measure 6 offset=20.0> 11645 {0.0} <music21.note.Note D> 11646 {1.0} <music21.note.Note E> 11647 {2.0} <music21.note.Note F> 11648 {3.0} <music21.note.Note G> 11649 {24.0} <music21.variant.Variant object of length 4.0> 11650 {24.0} <music21.stream.Measure 7 offset=24.0> 11651 {0.0} <music21.note.Note G> 11652 {1.0} <music21.note.Note A> 11653 {1.5} <music21.note.Note B-> 11654 {2.0} <music21.note.Note C> 11655 {3.0} <music21.note.Note C> 11656 {28.0} <music21.stream.Measure 8 offset=28.0> 11657 {0.0} <music21.note.Note F> 11658 {4.0} <music21.bar.Barline type=final> 11659 11660 After a variant group has been activated, the regions it replaced are 11661 stored as variants with the group 'default'. 11662 It should be noted that this means .activateVariants should rarely if 11663 ever be used on a stream which is returned 11664 by activateVariants because the group information is lost. 11665 11666 >>> defaultVariant = docVariant.activateVariants('default') 11667 >>> #_DOCS_SHOW defaultVariant.show() 11668 11669 .. image:: images/stream_activateVariants3.* 11670 :width: 600 11671 11672 >>> defaultVariant.show('text') 11673 {0.0} <music21.stream.Measure 1 offset=0.0> 11674 {0.0} <music21.clef.TrebleClef> 11675 {0.0} <music21.meter.TimeSignature 4/4> 11676 {0.0} <music21.note.Note D> 11677 {1.0} <music21.note.Note E> 11678 {2.0} <music21.note.Note F> 11679 {3.0} <music21.note.Note G> 11680 {4.0} <music21.variant.Variant object of length 4.0> 11681 {4.0} <music21.stream.Measure 2 offset=4.0> 11682 {0.0} <music21.note.Note A> 11683 {2.0} <music21.note.Note B-> 11684 {3.0} <music21.note.Note A> 11685 {8.0} <music21.stream.Measure 3 offset=8.0> 11686 {0.0} <music21.note.Note G> 11687 {1.0} <music21.note.Note A> 11688 {1.5} <music21.note.Note G> 11689 {2.0} <music21.note.Note F> 11690 {3.0} <music21.note.Note E> 11691 {12.0} <music21.variant.Variant object of length 8.0> 11692 {12.0} <music21.stream.Measure 4 offset=12.0> 11693 {0.0} <music21.note.Note D> 11694 {2.0} <music21.note.Note A> 11695 {16.0} <music21.stream.Measure 5 offset=16.0> 11696 {0.0} <music21.note.Note D> 11697 {1.0} <music21.note.Note E> 11698 {2.0} <music21.note.Note F> 11699 {3.0} <music21.note.Note G> 11700 {20.0} <music21.variant.Variant object of length 0.0> 11701 {20.0} <music21.stream.Measure 6 offset=20.0> 11702 {0.0} <music21.note.Note A> 11703 {2.0} <music21.note.Note B-> 11704 {3.0} <music21.note.Note A> 11705 {24.0} <music21.stream.Measure 7 offset=24.0> 11706 {0.0} <music21.note.Note G> 11707 {1.0} <music21.note.Note A> 11708 {1.5} <music21.note.Note B-> 11709 {2.0} <music21.note.Note C> 11710 {3.0} <music21.note.Note C> 11711 {28.0} <music21.stream.Measure 8 offset=28.0> 11712 {0.0} <music21.note.Note F> 11713 {4.0} <music21.bar.Barline type=final> 11714 ''' 11715 if not inPlace: # make a copy if inPlace is False 11716 returnObj = self.coreCopyAsDerivation('activateVariants') 11717 else: 11718 returnObj = self 11719 11720 # Define Lists to cache variants 11721 elongationVariants = [] 11722 deletionVariants = [] 11723 11724 # Loop through all variants, deal with replacement variants and 11725 # save insertion and deletion for later. 11726 for v in returnObj.variants: 11727 if group is not None: 11728 if group not in v.groups: 11729 continue # skip those that are not part of this group 11730 11731 lengthType = v.lengthType 11732 11733 # save insertions to perform last 11734 if lengthType == 'elongation': 11735 elongationVariants.append(v) 11736 # save deletions to perform after replacements 11737 elif lengthType == 'deletion': 11738 deletionVariants.append(v) 11739 # Deal with cases in which variant is the same length as what it replaces first. 11740 elif lengthType == 'replacement': 11741 returnObj._insertReplacementVariant(v, matchBySpan) 11742 11743 # Now deal with deletions before insertion variants. 11744 # For keeping track of which measure numbers have been removed 11745 deletedMeasures = [] 11746 # For keeping track of where new measures without measure numbers have been inserted, 11747 # will be a list of tuples (measureNumberPrior, [List, of, inserted, measures]) 11748 insertedMeasures = [] 11749 # For keeping track of the sections that are deleted 11750 # (so the offset gap can be closed later) 11751 deletedRegionsForRemoval = [] 11752 for v in deletionVariants: 11753 (deletedRegion, vDeletedMeasures, vInsertedMeasuresTuple 11754 ) = returnObj._insertDeletionVariant(v, matchBySpan) # deletes and inserts 11755 deletedRegionsForRemoval.append(deletedRegion) # Saves the deleted region 11756 deletedMeasures.extend(vDeletedMeasures) # Saves the deleted measure numbers 11757 # saves the inserted numberless measures (this will be empty unless there are 11758 # more bars in the variant than in the replacement region, which is unlikely 11759 # for a deletion variant. 11760 insertedMeasures.append(vInsertedMeasuresTuple) 11761 11762 # Squeeze out the gaps that were saved. 11763 returnObj._removeOrExpandGaps(deletedRegionsForRemoval, isRemove=True, inPlace=True) 11764 11765 # Before we can deal with insertions, we have to expand the stream to make space. 11766 insertionRegionsForExpansion = [] # For saving the insertion regions 11767 # go through all elongation variants to find the insertion regions. 11768 for v in elongationVariants: 11769 lengthDifference = v.replacementDuration - v.containedHighestTime 11770 insertionStart = v.getOffsetBySite(returnObj) + v.replacementDuration 11771 # Saves the information for each gap to be expanded 11772 insertionRegionsForExpansion.append((insertionStart, -1 * lengthDifference, [v])) 11773 11774 # Expands the appropriate gaps in the stream. 11775 returnObj._removeOrExpandGaps(insertionRegionsForExpansion, isRemove=False, inPlace=True) 11776 # Now deal with elongation variants properly 11777 for v in elongationVariants: 11778 (vInsertedMeasuresTuple, vDeletedMeasures 11779 ) = returnObj._insertInsertionVariant(v, matchBySpan) # deletes and inserts 11780 insertedMeasures.append(vInsertedMeasuresTuple) 11781 # Saves the numberless inserted measures 11782 # Saves deleted measures if any (it is unlikely that there will be unless 11783 # there are fewer measures in the variant than the replacement region, 11784 # which is unlikely for an elongation variant) 11785 deletedMeasures.extend(vDeletedMeasures) 11786 11787 # Now fix measure numbers given the saved information 11788 returnObj._fixMeasureNumbers(deletedMeasures, insertedMeasures) 11789 11790 # have to clear cached variants, as they are no longer the same 11791 returnObj.coreElementsChanged() 11792 11793 if not inPlace: 11794 return returnObj 11795 else: 11796 return None 11797 11798 def _insertReplacementVariant(self, v, matchBySpan=True): 11799 # noinspection PyShadowingNames 11800 ''' 11801 Helper function for activateVariants. Activates variants which are the same size there the 11802 region they replace. 11803 11804 11805 >>> v = variant.Variant() 11806 >>> variantDataM1 = [('b', 'eighth'), ('c', 'eighth'), 11807 ... ('a', 'quarter'), ('a', 'quarter'),('b', 'quarter')] 11808 >>> variantDataM2 = [('c', 'quarter'), ('d', 'quarter'), ('e', 'quarter'), ('e', 'quarter')] 11809 >>> variantData = [variantDataM1, variantDataM2] 11810 >>> for d in variantData: 11811 ... m = stream.Measure() 11812 ... for pitchName,durType in d: 11813 ... n = note.Note(pitchName) 11814 ... n.duration.type = durType 11815 ... m.append(n) 11816 ... v.append(m) 11817 >>> v.groups = ['paris'] 11818 >>> v.replacementDuration = 8.0 11819 11820 >>> s = stream.Stream() 11821 >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')] 11822 >>> streamDataM2 = [('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'), 11823 ... ('a', 'quarter'), ('b', 'quarter')] 11824 >>> streamDataM3 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')] 11825 >>> streamDataM4 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')] 11826 >>> streamData = [streamDataM1, streamDataM2, streamDataM3, streamDataM4] 11827 >>> for d in streamData: 11828 ... m = stream.Measure() 11829 ... for pitchName,durType in d: 11830 ... n = note.Note(pitchName) 11831 ... n.duration.type = durType 11832 ... m.append(n) 11833 ... s.append(m) 11834 >>> s.insert(4.0, v) 11835 11836 >>> deletedMeasures, insertedMeasuresTuple = s._insertReplacementVariant(v) 11837 >>> deletedMeasures 11838 [] 11839 >>> insertedMeasuresTuple 11840 (0, []) 11841 >>> s.show('text') 11842 {0.0} <music21.stream.Measure 0 offset=0.0> 11843 {0.0} <music21.note.Note A> 11844 {1.0} <music21.note.Note B> 11845 {2.0} <music21.note.Note A> 11846 {3.0} <music21.note.Note G> 11847 {4.0} <music21.variant.Variant object of length 8.0> 11848 {4.0} <music21.stream.Measure 0 offset=4.0> 11849 {0.0} <music21.note.Note B> 11850 {0.5} <music21.note.Note C> 11851 {1.0} <music21.note.Note A> 11852 {2.0} <music21.note.Note A> 11853 {3.0} <music21.note.Note B> 11854 {8.0} <music21.stream.Measure 0 offset=8.0> 11855 {0.0} <music21.note.Note C> 11856 {1.0} <music21.note.Note D> 11857 {2.0} <music21.note.Note E> 11858 {3.0} <music21.note.Note E> 11859 {12.0} <music21.stream.Measure 0 offset=12.0> 11860 {0.0} <music21.note.Note C> 11861 {1.0} <music21.note.Note B> 11862 {2.0} <music21.note.Note A> 11863 {3.0} <music21.note.Note A> 11864 ''' 11865 from music21 import variant 11866 11867 removed = variant.Variant() # replacement variant 11868 removed.groups = ['default'] # for now, default 11869 vStart = self.elementOffset(v) 11870 # this method matches and removes on an individual basis 11871 if not matchBySpan: 11872 targetsMatched = 0 11873 for e in v.elements: # get components in the Variant 11874 # get target offset relative to Stream 11875 oInStream = vStart + e.getOffsetBySite(v.containedSite) 11876 # get all elements at this offset, force a class match 11877 targets = self.getElementsByOffset(oInStream).getElementsByClass(e.classes[0]) 11878 # only replace if we match the start 11879 if targets: 11880 targetsMatched += 1 11881 # always assume we just want the first one? 11882 targetToReplace = targets[0] 11883 # environLocal.printDebug(['matchBySpan', matchBySpan, 11884 # 'found target to replace:', targetToReplace]) 11885 # remove the target, place in removed Variant 11886 removed.append(targetToReplace) 11887 self.remove(targetToReplace) 11888 # extract the variant component and insert into place 11889 self.insert(oInStream, e) 11890 11891 if getattr(targetToReplace, 'isMeasure', False): 11892 e.number = targetToReplace.number 11893 # only remove old and add removed if we matched 11894 if targetsMatched > 0: 11895 # remove the original variant 11896 self.remove(v) 11897 # place newly contained elements in position 11898 self.insert(vStart, removed) 11899 11900 # matching by span means that we remove all elements with the 11901 # span defined by the variant 11902 else: 11903 deletedMeasures = [] 11904 insertedMeasures = [] 11905 highestNumber = None 11906 11907 targets = v.replacedElements(self) 11908 11909 # this will always remove elements before inserting 11910 for e in targets: 11911 # need to get time relative to variant container's position 11912 oInVariant = self.elementOffset(e) - vStart 11913 removed.insert(oInVariant, e) 11914 # environLocal.printDebug( 11915 # ['matchBySpan', matchBySpan, 'activateVariants', 'removing', e]) 11916 self.remove(e) 11917 if getattr(e, 'isMeasure', False): 11918 # Save deleted measure numbers. 11919 deletedMeasures.append(e.number) 11920 11921 for e in v.elements: 11922 oInStream = vStart + e.getOffsetBySite(v.containedSite) 11923 self.insert(oInStream, e) 11924 if getattr(e, 'isMeasure', False): 11925 if deletedMeasures: # If there measure numbers left to use, use them. 11926 # Assign the next highest deleted measure number 11927 e.number = deletedMeasures.pop(False) 11928 # Save the highest number used so far (for use in the case 11929 # that there are extra measures with no numbers at the end) 11930 highestNumber = e.number 11931 11932 else: 11933 e.number = 0 11934 # If no measure numbers left, add this 11935 # numberless measure to insertedMeasures 11936 insertedMeasures.append(e) 11937 # remove the source variant 11938 self.remove(v) 11939 # place newly contained elements in position 11940 self.insert(vStart, removed) 11941 11942 # If deletedMeasures != [], then there were more deleted measures than 11943 # inserted and the remaining numbers in deletedMeasures are those that were removed. 11944 return deletedMeasures, (highestNumber, insertedMeasures) 11945 # In the case that the variant and stream are in the same time-signature, 11946 # this should return [] 11947 11948 def _insertDeletionVariant(self, v, matchBySpan=True): 11949 # noinspection PyShadowingNames 11950 ''' 11951 Helper function for activateVariants used for inserting variants that are shorter than 11952 the region they replace. Inserts elements in the variant and deletes elements in the 11953 replaced region but does not close gaps. 11954 11955 Returns a tuple describing the region where elements were removed, the 11956 gap is left behind to be dealt with by _removeOrExpandGaps. 11957 Tuple is of form (startOffset, lengthOfDeletedRegion, []). The empty list is 11958 expected by _removeOrExpandGaps 11959 and describes the list of elements which should be exempted from shifting 11960 for a particular gap. In the 11961 case of deletion, no elements need be exempted. 11962 11963 >>> v = variant.Variant() 11964 >>> variantDataM1 = [('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), 11965 ... ('a', 'quarter'),('b', 'quarter')] 11966 >>> variantDataM2 = [('c', 'quarter'), ('d', 'quarter'), 11967 ... ('e', 'quarter'), ('e', 'quarter')] 11968 >>> variantData = [variantDataM1, variantDataM2] 11969 >>> for d in variantData: 11970 ... m = stream.Measure() 11971 ... for pitchName,durType in d: 11972 ... n = note.Note(pitchName) 11973 ... n.duration.type = durType 11974 ... m.append(n) 11975 ... v.append(m) 11976 >>> v.groups = ['paris'] 11977 >>> v.replacementDuration = 12.0 11978 11979 >>> s = stream.Stream() 11980 >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')] 11981 >>> streamDataM2 = [('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'), 11982 ... ('a', 'quarter'), ('b', 'quarter')] 11983 >>> streamDataM3 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')] 11984 >>> streamDataM4 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')] 11985 >>> streamData = [streamDataM1, streamDataM2, streamDataM3, streamDataM4] 11986 >>> for d in streamData: 11987 ... m = stream.Measure() 11988 ... for pitchName,durType in d: 11989 ... n = note.Note(pitchName) 11990 ... n.duration.type = durType 11991 ... m.append(n) 11992 ... s.append(m) 11993 >>> s.insert(4.0, v) 11994 11995 11996 >>> deletedRegion, deletedMeasures, insertedMeasuresTuple = s._insertDeletionVariant(v) 11997 >>> deletedRegion 11998 (12.0, 4.0, []) 11999 >>> deletedMeasures 12000 [0] 12001 >>> insertedMeasuresTuple 12002 (0, []) 12003 >>> s.show('text') 12004 {0.0} <music21.stream.Measure 0 offset=0.0> 12005 {0.0} <music21.note.Note A> 12006 {1.0} <music21.note.Note B> 12007 {2.0} <music21.note.Note A> 12008 {3.0} <music21.note.Note G> 12009 {4.0} <music21.variant.Variant object of length 12.0> 12010 {4.0} <music21.stream.Measure 0 offset=4.0> 12011 {0.0} <music21.note.Note B> 12012 {0.5} <music21.note.Note C> 12013 {1.0} <music21.note.Note A> 12014 {2.0} <music21.note.Note A> 12015 {3.0} <music21.note.Note B> 12016 {8.0} <music21.stream.Measure 0 offset=8.0> 12017 {0.0} <music21.note.Note C> 12018 {1.0} <music21.note.Note D> 12019 {2.0} <music21.note.Note E> 12020 {3.0} <music21.note.Note E> 12021 12022 ''' 12023 from music21 import variant 12024 12025 deletedMeasures = [] # For keeping track of what measure numbers are deleted 12026 # length of the deleted region 12027 lengthDifference = v.replacementDuration - v.containedHighestTime 12028 12029 removed = variant.Variant() # what group should this have? 12030 removed.groups = ['default'] # for now, default 12031 removed.replacementDuration = v.containedHighestTime 12032 12033 vStart = self.elementOffset(v) 12034 deletionStart = vStart + v.containedHighestTime 12035 12036 targets = v.replacedElements(self) 12037 12038 # this will always remove elements before inserting 12039 for e in targets: 12040 if getattr(e, 'isMeasure', False): # if a measure is deleted, save its number 12041 deletedMeasures.append(e.number) 12042 oInVariant = self.elementOffset(e) - vStart 12043 removed.insert(oInVariant, e) 12044 self.remove(e) 12045 12046 # Next put in the elements from the variant 12047 highestNumber = None 12048 insertedMeasures = [] 12049 for e in v.elements: 12050 if getattr(e, 'isMeasure', False): 12051 # If there are deleted numbers still saved, assign this measure the 12052 # next highest and remove it from the list. 12053 if deletedMeasures: 12054 e.number = deletedMeasures.pop(False) 12055 # Save the highest number assigned so far. If there are numberless 12056 # inserted measures at the end, this will name where to begin numbering. 12057 highestNumber = e.number 12058 else: 12059 e.number = 0 12060 # If there are no deleted numbers left (unlikely) 12061 # save the inserted measures for renumbering later. 12062 insertedMeasures.append(e) 12063 12064 oInStream = vStart + e.getOffsetBySite(v.containedSite) 12065 self.insert(oInStream, e) 12066 12067 # remove the source variant 12068 self.remove(v) 12069 # place newly contained elements in position 12070 self.insert(vStart, removed) 12071 12072 # each variant leaves a gap, this saves the required information about those gaps 12073 # In most cases, inserted measures should be []. 12074 return (deletionStart, lengthDifference, []), deletedMeasures, ( 12075 highestNumber, insertedMeasures) 12076 12077 def _insertInsertionVariant(self, v, matchBySpan=True): 12078 # noinspection PyShadowingNames 12079 ''' 12080 Helper function for activateVariants. _removeOrExpandGaps must be called on the 12081 expanded regions before this function 12082 or it will not work properly. 12083 12084 12085 >>> v = variant.Variant() 12086 >>> variantDataM1 = [('b', 'eighth'), ('c', 'eighth'), ('a', 'quarter'), 12087 ... ('a', 'quarter'),('b', 'quarter')] 12088 >>> variantDataM2 = [('c', 'quarter'), ('d', 'quarter'), 12089 ... ('e', 'quarter'), ('e', 'quarter')] 12090 >>> variantData = [variantDataM1, variantDataM2] 12091 >>> for d in variantData: 12092 ... m = stream.Measure() 12093 ... for pitchName,durType in d: 12094 ... n = note.Note(pitchName) 12095 ... n.duration.type = durType 12096 ... m.append(n) 12097 ... v.append(m) 12098 >>> v.groups = ['paris'] 12099 >>> v.replacementDuration = 4.0 12100 12101 >>> s = stream.Stream() 12102 >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')] 12103 >>> streamDataM2 = [('b', 'eighth'), ('c', 'quarter'), ('a', 'eighth'), 12104 ... ('a', 'quarter'), ('b', 'quarter')] 12105 >>> streamDataM3 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')] 12106 >>> streamDataM4 = [('c', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('a', 'quarter')] 12107 >>> streamData = [streamDataM1, streamDataM2, streamDataM3, streamDataM4] 12108 >>> for d in streamData: 12109 ... m = stream.Measure() 12110 ... for pitchName,durType in d: 12111 ... n = note.Note(pitchName) 12112 ... n.duration.type = durType 12113 ... m.append(n) 12114 ... s.append(m) 12115 >>> s.insert(4.0, v) 12116 12117 >>> insertionRegionsForExpansion = [(8.0, 4.0, [v])] 12118 >>> s._removeOrExpandGaps(insertionRegionsForExpansion, isRemove=False, inPlace=True) 12119 12120 >>> insertedMeasuresTuple, deletedMeasures = s._insertInsertionVariant(v) 12121 >>> measurePrior, insertedMeasures = insertedMeasuresTuple 12122 >>> measurePrior 12123 0 12124 >>> len(insertedMeasures) 12125 1 12126 12127 >>> s.show('text') 12128 {0.0} <music21.stream.Measure 0 offset=0.0> 12129 {0.0} <music21.note.Note A> 12130 {1.0} <music21.note.Note B> 12131 {2.0} <music21.note.Note A> 12132 {3.0} <music21.note.Note G> 12133 {4.0} <music21.variant.Variant object of length 4.0> 12134 {4.0} <music21.stream.Measure 0 offset=4.0> 12135 {0.0} <music21.note.Note B> 12136 {0.5} <music21.note.Note C> 12137 {1.0} <music21.note.Note A> 12138 {2.0} <music21.note.Note A> 12139 {3.0} <music21.note.Note B> 12140 {8.0} <music21.stream.Measure 0 offset=8.0> 12141 {0.0} <music21.note.Note C> 12142 {1.0} <music21.note.Note D> 12143 {2.0} <music21.note.Note E> 12144 {3.0} <music21.note.Note E> 12145 {12.0} <music21.stream.Measure 0 offset=12.0> 12146 {0.0} <music21.note.Note C> 12147 {1.0} <music21.note.Note B> 12148 {2.0} <music21.note.Note A> 12149 {3.0} <music21.note.Note A> 12150 {16.0} <music21.stream.Measure 0 offset=16.0> 12151 {0.0} <music21.note.Note C> 12152 {1.0} <music21.note.Note B> 12153 {2.0} <music21.note.Note A> 12154 {3.0} <music21.note.Note A> 12155 12156 ''' 12157 from music21 import variant 12158 12159 deletedMeasures = [] 12160 removed = variant.Variant() # what group should this have? 12161 removed.groups = ['default'] # for now, default 12162 removed.replacementDuration = v.containedHighestTime 12163 vStart = self.elementOffset(v) 12164 12165 # First deal with the elements in the overlapping section (limit by class) 12166 targets = v.replacedElements(self) 12167 12168 # this will always remove elements before inserting 12169 for e in targets: 12170 if getattr(e, 'isMeasure', False): # Save deleted measure numbers. 12171 deletedMeasures.append(e.number) 12172 oInVariant = self.elementOffset(e) - vStart 12173 removed.insert(oInVariant, e) 12174 self.remove(e) 12175 12176 # Next put in the elements from the variant 12177 highestMeasure = None 12178 insertedMeasures = [] 12179 for e in v.elements: 12180 if getattr(e, 'isMeasure', False): 12181 # If there are deleted measure numbers left, assign the next 12182 # inserted measure the next highest number and remove it. 12183 if deletedMeasures: 12184 e.number = deletedMeasures.pop(False) 12185 highestMeasure = e.number 12186 # Save the highest number assigned so far 12187 # so we know where to being numbering new measures. 12188 else: 12189 e.number = 0 12190 insertedMeasures.append(e) 12191 # If there are no deleted measures, we have begun inserting as yet 12192 # unnumbered measures, save which those are. 12193 oInStream = vStart + e.getOffsetBySite(v.containedSite) 12194 self.insert(oInStream, e) 12195 12196 if highestMeasure is None: 12197 # If the highestMeasure is None (which will occur if the variant is 12198 # a strict insertion and replaces no measures, 12199 # we need to choose the highest measure number prior to the variant. 12200 measuresToCheck = self.getElementsByOffset(0.0, 12201 self.elementOffset(v), 12202 includeEndBoundary=True, 12203 mustFinishInSpan=False, 12204 mustBeginInSpan=True, 12205 ).getElementsByClass('Measure') 12206 highestMeasure = 0 12207 for m in measuresToCheck: 12208 if highestMeasure is None or m.number > highestMeasure: 12209 highestMeasure = m.number 12210 12211 # remove the source variant 12212 self.remove(v) 12213 # place newly contained elements in position 12214 self.insert(vStart, removed) 12215 12216 return (highestMeasure, insertedMeasures), deletedMeasures 12217 12218 def _removeOrExpandGaps(self, listOffsetDurExemption, 12219 isRemove=True, inPlace=False, exemptClasses=None): 12220 ''' 12221 Helper for activateVariants. Takes a list of tuples in the form 12222 (startOffset, duration, [list, of, exempt, objects]). If isRemove is True, 12223 gaps with duration will be closed at each startOffset. 12224 Exempt objects are useful for gap-expansion with variants. The gap must push all objects 12225 that occur after the insertion ahead, but the variant object 12226 itself should not be moved except by other gaps. This is poorly written 12227 and should be re-written, but it is difficult to describe. 12228 12229 12230 >>> s = stream.Stream() 12231 >>> s.insert(5.0, note.Note('a')) 12232 >>> s.insert(10.0, note.Note('b')) 12233 >>> s.insert(11.0, note.Note('c')) 12234 >>> s.insert(12.0, note.Note('d')) 12235 >>> s.insert(13.0, note.Note('e')) 12236 >>> s.insert(20.0, note.Note('f')) 12237 >>> n = note.Note('g') 12238 >>> s.insert(15.0, n) 12239 12240 >>> sGapsRemoved = s._removeOrExpandGaps( 12241 ... [(0.0, 5.0, []), (6.0, 4.0, []), (14.0, 6.0, [n])], isRemove=True) 12242 >>> sGapsRemoved.show('text') 12243 {0.0} <music21.note.Note A> 12244 {1.0} <music21.note.Note B> 12245 {2.0} <music21.note.Note C> 12246 {3.0} <music21.note.Note D> 12247 {4.0} <music21.note.Note E> 12248 {5.0} <music21.note.Note F> 12249 {15.0} <music21.note.Note G> 12250 12251 >>> sGapsExpanded = s._removeOrExpandGaps( 12252 ... [(0.0, 5.0, []), (11.0, 5.0, []), (14.0, 1.0, [n])], isRemove=False) 12253 >>> sGapsExpanded.show('text') 12254 {10.0} <music21.note.Note A> 12255 {15.0} <music21.note.Note B> 12256 {21.0} <music21.note.Note C> 12257 {22.0} <music21.note.Note D> 12258 {23.0} <music21.note.Note E> 12259 {25.0} <music21.note.Note G> 12260 {31.0} <music21.note.Note F> 12261 ''' 12262 if inPlace is True: 12263 returnObj = self 12264 else: 12265 returnObj = copy.deepcopy(self) 12266 12267 returnObjDuration = returnObj.duration.quarterLength 12268 12269 # If any classes should be exempt from gap closing or expanding, this deals with those. 12270 classList = [] 12271 if exemptClasses is None: 12272 classList = None 12273 else: 12274 for e in returnObj.elements: 12275 if type(e) not in classList: # pylint: disable=unidiomatic-typecheck 12276 classList.append(type(e)) 12277 for c in exemptClasses: 12278 if c in classList: 12279 classList.remove(c) 12280 12281 if isRemove is True: 12282 shiftDur = 0.0 12283 listSorted = sorted(listOffsetDurExemption, key=lambda target: target[0]) 12284 for i, durTuple in enumerate(listSorted): 12285 startOffset, durationAmount, exemptObjects = durTuple 12286 if i + 1 < len(listSorted): 12287 endOffset = listSorted[i + 1][0] 12288 includeEnd = False 12289 else: 12290 endOffset = returnObjDuration 12291 includeEnd = True 12292 12293 shiftDur = shiftDur + durationAmount 12294 for e in returnObj.getElementsByOffset(startOffset + durationAmount, 12295 endOffset, 12296 includeEndBoundary=includeEnd, 12297 mustFinishInSpan=False, 12298 mustBeginInSpan=True, 12299 classList=classList): 12300 12301 if e in exemptObjects: 12302 continue 12303 12304 elementOffset = e.getOffsetBySite(returnObj) 12305 returnObj.coreSetElementOffset(e, elementOffset - shiftDur) 12306 else: 12307 shiftDur = 0.0 12308 shiftsDict = {} 12309 listSorted = sorted(listOffsetDurExemption, key=lambda target: target[0]) 12310 for i, durTuple in enumerate(listSorted): 12311 startOffset, durationAmount, exemptObjects = durTuple 12312 12313 if i + 1 < len(listSorted): 12314 endOffset = listSorted[i + 1][0] 12315 includeEnd = False 12316 else: 12317 endOffset = returnObjDuration 12318 includeEnd = True 12319 12320 exemptShift = shiftDur 12321 shiftDur = shiftDur + durationAmount 12322 shiftsDict[startOffset] = (shiftDur, endOffset, includeEnd, 12323 exemptObjects, exemptShift) 12324 12325 for offset in sorted(shiftsDict, key=lambda off: -1 * off): 12326 shiftDur, endOffset, includeEnd, exemptObjects, exemptShift = shiftsDict[offset] 12327 for e in returnObj.getElementsByOffset(offset, 12328 endOffset, 12329 includeEndBoundary=includeEnd, 12330 mustFinishInSpan=False, 12331 mustBeginInSpan=True, 12332 classList=classList): 12333 12334 if e in exemptObjects: 12335 elementOffset = e.getOffsetBySite(returnObj) 12336 returnObj.coreSetElementOffset(e, elementOffset + exemptShift) 12337 continue 12338 12339 elementOffset = e.getOffsetBySite(returnObj) 12340 returnObj.coreSetElementOffset(e, elementOffset + shiftDur) 12341 12342 # ran coreSetElementOffset 12343 returnObj.coreElementsChanged() 12344 12345 if inPlace is True: 12346 return 12347 else: 12348 return returnObj 12349 12350 def _fixMeasureNumbers(self, deletedMeasures, insertedMeasures): 12351 # noinspection PyShadowingNames 12352 ''' 12353 Corrects the measures numbers of a string of measures given a list of measure numbers 12354 that have been deleted and a 12355 list of tuples (highest measure number below insertion, number of inserted measures). 12356 12357 >>> s = converter.parse('tinynotation: 4/4 d4 e4 f4 g4 a2 b-4 a4 g4 a8 g8 f4 e4 g1') 12358 >>> s[-1].offset = 20.0 12359 >>> s.show('text') 12360 {0.0} <music21.stream.Measure 1 offset=0.0> 12361 {0.0} <music21.clef.TrebleClef> 12362 {0.0} <music21.meter.TimeSignature 4/4> 12363 {0.0} <music21.note.Note D> 12364 {1.0} <music21.note.Note E> 12365 {2.0} <music21.note.Note F> 12366 {3.0} <music21.note.Note G> 12367 {4.0} <music21.stream.Measure 2 offset=4.0> 12368 {0.0} <music21.note.Note A> 12369 {2.0} <music21.note.Note B-> 12370 {3.0} <music21.note.Note A> 12371 {8.0} <music21.stream.Measure 3 offset=8.0> 12372 {0.0} <music21.note.Note G> 12373 {1.0} <music21.note.Note A> 12374 {1.5} <music21.note.Note G> 12375 {2.0} <music21.note.Note F> 12376 {3.0} <music21.note.Note E> 12377 {20.0} <music21.stream.Measure 4 offset=20.0> 12378 {0.0} <music21.note.Note G> 12379 {4.0} <music21.bar.Barline type=final> 12380 >>> s.remove(s.measure(2)) 12381 >>> s.show('text') 12382 {0.0} <music21.stream.Measure 1 offset=0.0> 12383 {0.0} <music21.clef.TrebleClef> 12384 ... 12385 {8.0} <music21.stream.Measure 3 offset=8.0> 12386 {0.0} <music21.note.Note G> 12387 ... 12388 {20.0} <music21.stream.Measure 4 offset=20.0> 12389 {0.0} <music21.note.Note G> 12390 {4.0} <music21.bar.Barline type=final> 12391 >>> deletedMeasures = [2] 12392 >>> m1 = stream.Measure() 12393 >>> m1.repeatAppend(note.Note('e'),4) 12394 >>> s.insert(12.0, m1) 12395 >>> m2 = stream.Measure() 12396 >>> m2.repeatAppend(note.Note('f'),4) 12397 >>> s.insert(16.0, m2) 12398 >>> s.show('text') 12399 {0.0} <music21.stream.Measure 1 offset=0.0> 12400 {0.0} <music21.clef.TrebleClef> 12401 ... 12402 {8.0} <music21.stream.Measure 3 offset=8.0> 12403 {0.0} <music21.note.Note G> 12404 ... 12405 {12.0} <music21.stream.Measure 0 offset=12.0> 12406 {0.0} <music21.note.Note E> 12407 {1.0} <music21.note.Note E> 12408 {2.0} <music21.note.Note E> 12409 {3.0} <music21.note.Note E> 12410 {16.0} <music21.stream.Measure 0 offset=16.0> 12411 {0.0} <music21.note.Note F> 12412 {1.0} <music21.note.Note F> 12413 {2.0} <music21.note.Note F> 12414 {3.0} <music21.note.Note F> 12415 {20.0} <music21.stream.Measure 4 offset=20.0> 12416 {0.0} <music21.note.Note G> 12417 {4.0} <music21.bar.Barline type=final> 12418 >>> insertedMeasures = [(3, [m1, m2])] 12419 12420 >>> s._fixMeasureNumbers(deletedMeasures, insertedMeasures) 12421 >>> s.show('text') 12422 {0.0} <music21.stream.Measure 1 offset=0.0> 12423 {0.0} <music21.clef.TrebleClef> 12424 {0.0} <music21.meter.TimeSignature 4/4> 12425 ... 12426 {8.0} <music21.stream.Measure 2 offset=8.0> 12427 {0.0} <music21.note.Note G> 12428 ... 12429 {12.0} <music21.stream.Measure 3 offset=12.0> 12430 {0.0} <music21.note.Note E> 12431 ... 12432 {16.0} <music21.stream.Measure 4 offset=16.0> 12433 {0.0} <music21.note.Note F> 12434 ... 12435 {20.0} <music21.stream.Measure 5 offset=20.0> 12436 {0.0} <music21.note.Note G> 12437 {4.0} <music21.bar.Barline type=final> 12438 >>> fixedNumbers = [] 12439 >>> for m in s.getElementsByClass('Measure'): 12440 ... fixedNumbers.append( m.number ) 12441 >>> fixedNumbers 12442 [1, 2, 3, 4, 5] 12443 12444 ''' 12445 deletedMeasures.extend(insertedMeasures) 12446 allMeasures = deletedMeasures 12447 12448 if not allMeasures: 12449 return 12450 12451 def measureNumberSortRoutine(numOrNumTuple): 12452 if isinstance(numOrNumTuple, tuple): 12453 return measureNumberSortRoutine(numOrNumTuple[0]) 12454 elif numOrNumTuple is None: 12455 return -9999 12456 else: 12457 return numOrNumTuple 12458 12459 allMeasures.sort(key=measureNumberSortRoutine) 12460 12461 oldMeasures = self.getElementsByClass('Measure').stream() 12462 newMeasures = [] 12463 12464 cumulativeNumberShift = 0 12465 oldCorrections = {} 12466 newCorrections = {} 12467 # the inserted measures must be treated differently than the original measures. 12468 # an inserted measure should not shift itself, 12469 # but it should shift measures with the same number. 12470 # However, inserted measures should still be shifted by every other correction. 12471 12472 # First collect dictionaries of shift boundaries and the amount of the shift. 12473 # at the same time, five un-numbered measures numbers that make sense. 12474 for measureNumber in allMeasures: 12475 if isinstance(measureNumber, tuple): # tuple implies insertion 12476 measurePrior, extendedMeasures = measureNumber 12477 if not extendedMeasures: # No measures were added, therefore no shift. 12478 continue 12479 cumulativeNumberShift += len(extendedMeasures) 12480 nextMeasure = measurePrior + 1 12481 for m in extendedMeasures: 12482 oldMeasures.remove(m) 12483 newMeasures.append(m) 12484 m.number = nextMeasure 12485 nextMeasure += 1 12486 oldCorrections[measurePrior + 1] = cumulativeNumberShift 12487 newCorrections[nextMeasure] = cumulativeNumberShift 12488 else: # integer implies deletion 12489 cumulativeNumberShift -= 1 12490 oldCorrections[measureNumber + 1] = cumulativeNumberShift 12491 newCorrections[measureNumber + 1] = cumulativeNumberShift 12492 12493 # Second, make corrections based on the dictionaries. The key is the measure number 12494 # above which measures should be shifted by the value up to the next key. It is easiest 12495 # to do this in reverse order so there is no overlapping. 12496 previousBoundary = None 12497 for k in sorted(oldCorrections, key=lambda x: -1 * x): 12498 shift = oldCorrections[k] 12499 for m in oldMeasures: 12500 if previousBoundary is None or m.number < previousBoundary: 12501 if m.number >= k: 12502 m.number = m.number + shift 12503 previousBoundary = k 12504 12505 previousBoundary = None 12506 for k in sorted(newCorrections, key=lambda x: -1 * x): 12507 shift = newCorrections[k] 12508 for m in newMeasures: 12509 if previousBoundary is None or m.number < previousBoundary: 12510 if m.number >= k: 12511 m.number = m.number + shift 12512 previousBoundary = k 12513 12514 def showVariantAsOssialikePart(self, containedPart, variantGroups, *, inPlace=False): 12515 # noinspection PyShadowingNames 12516 ''' 12517 Takes a part within the score and a list of variant groups within that part. 12518 Puts the variant object 12519 in a part surrounded by hidden rests to mimic the appearance of an ossia despite limited 12520 musicXML support for ossia staves. Note that this will ignore variants with .lengthType 12521 'elongation' and 'deletion' as there is no good way to represent ossia staves like those 12522 by this method. 12523 12524 >>> sPartStr = 'd4 e4 f4 g4 a2 b-4 a4 g4 a8 g8 f4 e4 d2 a2 ' 12525 >>> v1Str = ' a2. b-8 a8 ' 12526 >>> v2Str = ' d4 f4 a2 ' 12527 12528 >>> sPartStr += "d4 e4 f4 g4 a2 b-4 a4 g4 a8 b-8 c'4 c4 f1" 12529 12530 >>> sPartStream = converter.parse('tinynotation: 4/4 ' + sPartStr) 12531 >>> sPartStream.makeMeasures(inPlace=True) # maybe not necessary? 12532 >>> v1stream = converter.parse('tinynotation: 4/4 ' + v1Str) 12533 >>> v2stream = converter.parse('tinynotation: 4/4 ' + v2Str) 12534 12535 >>> v1 = variant.Variant() 12536 >>> v1measure = stream.Measure() 12537 >>> v1.insert(0.0, v1measure) 12538 >>> for e in v1stream.notesAndRests: 12539 ... v1measure.insert(e.offset, e) 12540 12541 >>> v2 = variant.Variant() 12542 >>> v2measure = stream.Measure() 12543 >>> v2.insert(0.0, v2measure) 12544 >>> for e in v2stream.notesAndRests: 12545 ... v2measure.insert(e.offset, e) 12546 12547 >>> v3 = variant.Variant() 12548 >>> v2.replacementDuration = 4.0 12549 >>> v3.replacementDuration = 4.0 12550 >>> v1.groups = ['variant1'] 12551 >>> v2.groups = ['variant2'] 12552 >>> v3.groups = ['variant3'] 12553 12554 >>> sPart = stream.Part() 12555 >>> for e in sPartStream: 12556 ... sPart.insert(e.offset, e) 12557 12558 >>> sPart.insert(4.0, v1) 12559 >>> sPart.insert(12.0, v2) 12560 >>> sPart.insert(20.0, v3) # This is a deletion variant and will be skipped 12561 >>> s = stream.Score() 12562 >>> s.insert(0.0, sPart) 12563 >>> streamWithOssia = s.showVariantAsOssialikePart(sPart, 12564 ... ['variant1', 'variant2', 'variant3'], inPlace=False) 12565 >>> #_DOCS_SHOW streamWithOssia.show() 12566 12567 ''' 12568 from music21 import variant 12569 12570 # containedPart must be in self, or an exception is raised. 12571 if not (containedPart in self): 12572 raise variant.VariantException(f'Could not find {containedPart} in {self}') 12573 12574 if inPlace is True: 12575 returnObj = self 12576 returnPart = containedPart 12577 else: 12578 returnObj = self.coreCopyAsDerivation('showVariantAsOssialikePart') 12579 containedPartIndex = self.parts.stream().index(containedPart) 12580 returnPart = returnObj.parts[containedPartIndex] 12581 12582 # First build a new part object that is the same length as returnPart 12583 # but entirely hidden rests. 12584 # This is done by copying the part and removing unnecessary objects 12585 # including irrelevant variants 12586 # but saving relevant variants. 12587 for variantGroup in variantGroups: 12588 newPart = copy.deepcopy(returnPart) 12589 expressedVariantsExist = False 12590 for e in newPart.elements: 12591 eClasses = e.classes 12592 if 'Variant' in eClasses: 12593 elementGroups = e.groups 12594 if (not(variantGroup in elementGroups) 12595 or e.lengthType in ['elongation', 'deletion']): 12596 newPart.remove(e) 12597 else: 12598 expressedVariantsExist = True 12599 elif 'GeneralNote' in eClasses: 12600 nQuarterLength = e.duration.quarterLength 12601 nOffset = e.getOffsetBySite(newPart) 12602 newPart.remove(e) 12603 r = note.Rest() 12604 r.style.hideObjectOnPrint = True 12605 r.duration.quarterLength = nQuarterLength 12606 newPart.insert(nOffset, r) 12607 elif 'Measure' in eClasses: # Recurse if measure 12608 measureDuration = e.duration.quarterLength 12609 for n in e.notesAndRests: 12610 e.remove(n) 12611 r = note.Rest() 12612 r.duration.quarterLength = measureDuration 12613 r.style.hideObjectOnPrint = True 12614 e.insert(0.0, r) 12615 12616 e.style.hideObjectOnPrint = True 12617 12618 newPart.activateVariants(variantGroup, inPlace=True, matchBySpan=True) 12619 if expressedVariantsExist: 12620 returnObj.insert(0.0, newPart) 12621 12622 if inPlace: 12623 return 12624 else: 12625 return returnObj 12626 12627 def makeVariantBlocks(self): 12628 ''' 12629 from music21 import * 12630 ''' 12631 variantsToBeDone = self.variants.elements 12632 12633 for v in variantsToBeDone: 12634 startOffset = self.elementOffset(v) 12635 endOffset = v.replacementDuration + startOffset 12636 conflictingVariants = self.getElementsByOffset(offsetStart=startOffset, 12637 offsetEnd=endOffset, 12638 includeEndBoundary=False, 12639 mustFinishInSpan=False, 12640 mustBeginInSpan=True, 12641 classList=['Variant']) 12642 for cV in conflictingVariants: 12643 oldReplacementDuration = cV.replacementDuration 12644 if self.elementOffset(cV) == startOffset: 12645 continue # do nothing 12646 else: 12647 shiftOffset = self.elementOffset(cV) - startOffset 12648 r = note.Rest() 12649 r.duration.quarterLength = shiftOffset 12650 r.style.hideObjectOnPrint = True 12651 for el in cV._stream: 12652 oldOffset = el.getOffsetBySite(cV._stream) 12653 cV._stream.coreSetElementOffset(el, oldOffset + shiftOffset) 12654 cV.coreElementsChanged() 12655 cV.insert(0.0, r) 12656 cV.replacementDuration = oldReplacementDuration 12657 self.remove(cV) 12658 self.insert(startOffset, cV) 12659 variantsToBeDone.append(cV) 12660 12661 12662# ----------------------------------------------------------------------------- 12663 12664 12665class Voice(Stream): 12666 ''' 12667 A Stream subclass for declaring that all the music in the 12668 stream belongs to a certain "voice" for analysis or display 12669 purposes. 12670 12671 Note that both Finale's Layers and Voices as concepts are 12672 considered Voices here. 12673 12674 Voices have a sort order of 1 greater than time signatures 12675 ''' 12676 recursionType = 'elementsFirst' 12677 classSortOrder = 5 12678 12679 12680# ----------------------------------------------------------------------------- 12681 12682 12683class Measure(Stream): 12684 ''' 12685 A representation of a Measure organized as a Stream. 12686 12687 All properties of a Measure that are Music21 objects are found as part of 12688 the Stream's elements. 12689 12690 Measure number can be explicitly set with the `number` keyword: 12691 12692 >>> m4 = stream.Measure(number=4) 12693 >>> m4 12694 <music21.stream.Measure 4 offset=0.0> 12695 >>> m4.number 12696 4 12697 12698 If passed a single integer as an argument, assumes that this int 12699 is the measure number. 12700 12701 >>> m5 = stream.Measure(5) 12702 >>> m5 12703 <music21.stream.Measure 5 offset=0.0> 12704 12705 Number can also be a string if there is a suffix: 12706 12707 >>> m4 = stream.Measure(number='4a') 12708 >>> m4 12709 <music21.stream.Measure 4a offset=0.0> 12710 >>> m4.numberSuffix 12711 'a' 12712 12713 Though they have all the features of general streams, 12714 Measures have specific attributes that allow for setting their number 12715 and numberSuffix, keep track of whether they have a different clef or 12716 key or timeSignature than previous measures, allow for padding (and pickups), 12717 and can be found as a "measure slice" within a score and parts. 12718 ''' 12719 recursionType = 'elementsFirst' 12720 isMeasure = True 12721 12722 # define order to present names in documentation; use strings 12723 _DOC_ORDER = [''] 12724 # documentation for all attributes (not properties or methods) 12725 _DOC_ATTR = { 12726 'timeSignatureIsNew': ''' 12727 Boolean describing if the TimeSignature 12728 is different than the previous Measure.''', 12729 'clefIsNew': 'Boolean describing if the Clef is different than the previous Measure.', 12730 'keyIsNew': 'Boolean describing if KeySignature is different than the previous Measure.', 12731 'number': ''' 12732 A number representing the displayed or shown 12733 Measure number as presented in a written Score.''', 12734 'numberSuffix': ''' 12735 If a Measure number has a string annotation, such as "a" or similar, 12736 this string is stored here. Note that in MusicXML, such 12737 suffixes often appear as 12738 prefixes to measure numbers. In music21 (like most measure 12739 numbering systems), these 12740 numbers appear as suffixes.''', 12741 'layoutWidth': ''' 12742 A suggestion for layout width, though most rendering systems do not support 12743 this designation. Use :class:`~music21.layout.SystemLayout` 12744 objects instead.''', 12745 'paddingLeft': ''' 12746 defines empty space at the front of the measure for purposes of determining 12747 beat, etc for pickup/anacrusis bars. In 4/4, a 12748 measure with a one-beat pickup 12749 note will have a `paddingLeft` of 3.0. 12750 (The name comes from the CSS graphical term 12751 for the amount of padding on the left side of a region.)''', 12752 'paddingRight': ''' 12753 defines empty space at the end of the measure for purposes of determining 12754 whether or not a measure is filled. 12755 In 4/4, a piece beginning a one-beat pickup 12756 note will often have a final measure of three beats, instead of four. 12757 The final 12758 measure should have a `paddingRight` of 1.0. 12759 (The name comes from the CSS graphical term 12760 for the amount of padding on the right side of a region.)''', 12761 } 12762 12763 def __init__(self, *args, **keywords): 12764 if len(args) == 1 and isinstance(args[0], int) and 'number' not in keywords: 12765 keywords['number'] = args[0] 12766 args = () 12767 12768 super().__init__(*args, **keywords) 12769 12770 # clef and timeSignature is defined as a property below 12771 self.timeSignatureIsNew = False 12772 self.clefIsNew = False 12773 self.keyIsNew = False 12774 12775 self.filled = False 12776 12777 # padding: defining a context for offsets contained within this Measure 12778 # padding defines dead regions of offsets into the measure 12779 # the paddingLeft is used by TimeSignature objects to determine beat 12780 # position; paddingRight defines a QL from the end of the time signature 12781 # to the last valid offset 12782 # paddingLeft is used to define pickup/anacrusis bars 12783 self.paddingLeft = 0 12784 self.paddingRight = 0 12785 12786 self.numberSuffix = None # for measure 14a would be 'a' 12787 if 'number' in keywords: 12788 num = keywords['number'] 12789 if isinstance(num, str): 12790 realNum, suffix = common.getNumFromStr(num) 12791 self.number = int(realNum) 12792 if suffix: 12793 self.numberSuffix = suffix 12794 else: 12795 self.number = keywords['number'] 12796 else: 12797 self.number = 0 # 0 means undefined or pickup 12798 # we can request layout width, using the same units used 12799 # in layout.py for systems; most musicxml readers do not support this 12800 # on input 12801 self.layoutWidth = None 12802 12803 # def addRepeat(self): 12804 # # TODO: write 12805 # pass 12806 12807 # def addTimeDependentDirection(self, time, direction): 12808 # # TODO: write 12809 # pass 12810 12811 def measureNumberWithSuffix(self): 12812 ''' 12813 Return the measure `.number` with the `.numberSuffix` as a string. 12814 12815 >>> m = stream.Measure() 12816 >>> m.number = 4 12817 >>> m.numberSuffix = 'A' 12818 >>> m.measureNumberWithSuffix() 12819 '4A' 12820 12821 Test that it works as musicxml 12822 12823 >>> xml = musicxml.m21ToXml.GeneralObjectExporter().parse(m) 12824 >>> print(xml.decode('utf-8')) 12825 <?xml version="1.0"...?> 12826 ... 12827 <part id="..."> 12828 <!--========================= Measure 4 ==========================--> 12829 <measure number="4A"> 12830 ... 12831 12832 Test round tripping: 12833 12834 >>> s2 = converter.parseData(xml) 12835 >>> print(s2[stream.Measure].first().measureNumberWithSuffix()) 12836 4A 12837 12838 Note that we use print here because in parsing the data will become a unicode string. 12839 ''' 12840 if self.numberSuffix: 12841 return str(self.number) + self.numberSuffix 12842 else: 12843 return str(self.number) 12844 12845 def _reprInternal(self): 12846 return self.measureNumberWithSuffix() + f' offset={self.offset}' 12847 12848 # ------------------------------------------------------------------------- 12849 def mergeAttributes(self, other): 12850 ''' 12851 Given another Measure, configure all non-element attributes of this 12852 Measure with the attributes of the other Measure. No elements 12853 will be changed or copied. 12854 12855 This method is necessary because Measures, unlike some Streams, 12856 have attributes independent of any stored elements. 12857 12858 Overrides base.Music21Object.mergeAttributes 12859 12860 >>> m1 = stream.Measure() 12861 >>> m1.id = 'MyMeasure' 12862 >>> m1.clefIsNew = True 12863 >>> m1.number = 2 12864 >>> m1.numberSuffix = 'b' 12865 >>> m1.layoutWidth = 200 12866 12867 >>> m2 = stream.Measure() 12868 >>> m2.mergeAttributes(m1) 12869 >>> m2.layoutWidth 12870 200 12871 >>> m2.id 12872 'MyMeasure' 12873 >>> m2 12874 <music21.stream.Measure 2b offset=0.0> 12875 12876 Try with not another Measure... 12877 12878 >>> m3 = stream.Stream() 12879 >>> m3.id = 'hello' 12880 >>> m2.mergeAttributes(m3) 12881 >>> m2.id 12882 'hello' 12883 >>> m2.layoutWidth 12884 200 12885 ''' 12886 # calling bass class sets id, groups 12887 super().mergeAttributes(other) 12888 12889 for attr in ('timeSignatureIsNew', 'clefIsNew', 'keyIsNew', 'filled', 12890 'paddingLeft', 'paddingRight', 'number', 'numberSuffix', 'layoutWidth'): 12891 if hasattr(other, attr): 12892 setattr(self, attr, getattr(other, attr)) 12893 12894 # ------------------------------------------------------------------------- 12895 def makeNotation(self, 12896 inPlace=False, 12897 **subroutineKeywords): 12898 # noinspection PyShadowingNames 12899 ''' 12900 This method calls a sequence of Stream methods on this 12901 :class:`~music21.stream.Measure` to prepare notation. 12902 12903 If `inPlace` is True, this is done in-place; if 12904 `inPlace` is False, this returns a modified deep copy. 12905 12906 >>> m = stream.Measure() 12907 >>> n1 = note.Note('g#') 12908 >>> n2 = note.Note('g') 12909 >>> m.append([n1, n2]) 12910 >>> m.makeNotation(inPlace=True) 12911 >>> m.notes[1].pitch.accidental 12912 <music21.pitch.Accidental natural> 12913 ''' 12914 # environLocal.printDebug(['Measure.makeNotation']) 12915 # TODO: this probably needs to look to see what processes need to be done; 12916 # for example, existing beaming may be destroyed. 12917 12918 # do this before deepcopy... 12919 12920 # assuming we are not trying to get context of previous measure 12921 if not inPlace: # make a copy 12922 m = copy.deepcopy(self) 12923 else: 12924 m = self 12925 12926 srkCopy = subroutineKeywords.copy() 12927 12928 for illegalKey in ('meterStream', 'refStreamOrTimeRange', 'bestClef'): 12929 if illegalKey in srkCopy: 12930 del(srkCopy[illegalKey]) 12931 12932 m.makeAccidentals(searchKeySignatureByContext=True, inPlace=True, **srkCopy) 12933 # makeTies is for cross-bar associations, and cannot be used 12934 # at just the measure level 12935 # m.makeTies(meterStream, inPlace=True) 12936 12937 # must have a time signature before calling make beams 12938 if m.timeSignature is None: 12939 # get a time signature if not defined, searching the context if 12940 # necessary 12941 contextMeters = m.getTimeSignatures(searchContext=True, 12942 returnDefault=False) 12943 defaultMeters = m.getTimeSignatures(searchContext=False, 12944 returnDefault=True) 12945 if contextMeters: 12946 ts = contextMeters[0] 12947 else: 12948 try: 12949 ts = self.bestTimeSignature() 12950 except (StreamException, meter.MeterException): 12951 # there must be one here 12952 ts = defaultMeters[0] 12953 m.timeSignature = ts # a Stream; get the first element 12954 12955 # environLocal.printDebug(['have time signature', m.timeSignature]) 12956 if not m.streamStatus.beams: 12957 m.makeBeams(inPlace=True) 12958 if not m.streamStatus.tuplets: 12959 makeNotation.makeTupletBrackets(m, inPlace=True) 12960 12961 if not inPlace: 12962 return m 12963 else: 12964 return None 12965 12966 def barDurationProportion(self, barDuration=None): 12967 ''' 12968 Return a floating point value greater than 0 showing the proportion 12969 of the bar duration that is filled based on the highest time of 12970 all elements. 0.0 is empty, 1.0 is filled; 1.5 specifies of an 12971 overflow of half. 12972 12973 Bar duration refers to the duration of the Measure as suggested by 12974 the `TimeSignature`. This value cannot be determined without a `TimeSignature`. 12975 12976 An already-obtained Duration object can be supplied with the `barDuration` 12977 optional argument. 12978 12979 >>> import copy 12980 >>> m = stream.Measure() 12981 >>> m.timeSignature = meter.TimeSignature('3/4') 12982 >>> n = note.Note() 12983 >>> n.quarterLength = 1 12984 >>> m.append(copy.deepcopy(n)) 12985 >>> m.barDurationProportion() 12986 Fraction(1, 3) 12987 >>> m.append(copy.deepcopy(n)) 12988 >>> m.barDurationProportion() 12989 Fraction(2, 3) 12990 >>> m.append(copy.deepcopy(n)) 12991 >>> m.barDurationProportion() 12992 1.0 12993 >>> m.append(copy.deepcopy(n)) 12994 >>> m.barDurationProportion() 12995 Fraction(4, 3) 12996 ''' 12997 # passing a barDuration may save time in the lookup process 12998 if barDuration is None: 12999 barDuration = self.barDuration 13000 return opFrac(self.highestTime / barDuration.quarterLength) 13001 13002 def padAsAnacrusis(self, useGaps=True, useInitialRests=False): 13003 ''' 13004 Given an incompletely filled Measure, adjust the `paddingLeft` value to to 13005 represent contained events as shifted to fill the right-most duration of the bar. 13006 13007 Calling this method will overwrite any previously set `paddingLeft` value, 13008 based on the current TimeSignature-derived `barDuration` attribute. 13009 13010 >>> m = stream.Measure() 13011 >>> m.timeSignature = meter.TimeSignature('3/4') 13012 >>> n = note.Note() 13013 >>> n.quarterLength = 1.0 13014 >>> m.append(n) 13015 >>> m.padAsAnacrusis() 13016 >>> m.paddingLeft 13017 2.0 13018 13019 >>> m.timeSignature = meter.TimeSignature('5/4') 13020 >>> m.padAsAnacrusis() 13021 >>> m.paddingLeft 13022 4.0 13023 13024 13025 Empty space at the beginning of the measure will not be taken in account: 13026 13027 >>> m = stream.Measure() 13028 >>> m.timeSignature = meter.TimeSignature('3/4') 13029 >>> n = note.Note(type='quarter') 13030 >>> m.insert(2.0, n) 13031 >>> m.padAsAnacrusis() 13032 >>> m.paddingLeft 13033 0 13034 13035 If useInitialRests is True, then rests at the beginning of the measure 13036 are removed. This is especially useful for formats that don't give a 13037 way to specify a pickup measure (such as tinynotation) or software 13038 that generates incorrect opening measures. So, to fix the problem before, 13039 put a rest at the beginning and call useInitialRests: 13040 13041 >>> r = note.Rest(type='half') 13042 >>> m.insert(0, r) 13043 >>> m.padAsAnacrusis(useInitialRests=True) 13044 >>> m.paddingLeft 13045 2.0 13046 13047 And the rest is gone! 13048 13049 >>> m.show('text') 13050 {0.0} <music21.meter.TimeSignature 3/4> 13051 {0.0} <music21.note.Note C> 13052 13053 13054 Only initial rests count for useInitialRests: 13055 13056 >>> m = stream.Measure() 13057 >>> m.timeSignature = meter.TimeSignature('3/4') 13058 >>> m.append(note.Rest(type='eighth')) 13059 >>> m.append(note.Rest(type='eighth')) 13060 >>> m.append(note.Note('C4', type='quarter')) 13061 >>> m.append(note.Rest(type='eighth')) 13062 >>> m.append(note.Note('D4', type='eighth')) 13063 >>> m.show('text') 13064 {0.0} <music21.meter.TimeSignature 3/4> 13065 {0.0} <music21.note.Rest eighth> 13066 {0.5} <music21.note.Rest eighth> 13067 {1.0} <music21.note.Note C> 13068 {2.0} <music21.note.Rest eighth> 13069 {2.5} <music21.note.Note D> 13070 >>> m.padAsAnacrusis(useInitialRests=True) 13071 >>> m.paddingLeft 13072 1.0 13073 >>> m.show('text') 13074 {0.0} <music21.meter.TimeSignature 3/4> 13075 {0.0} <music21.note.Note C> 13076 {1.0} <music21.note.Rest eighth> 13077 {1.5} <music21.note.Note D> 13078 ''' 13079 if useInitialRests: 13080 removeList = [] 13081 for gn in self.getElementsByClass('GeneralNote'): 13082 if not isinstance(gn, note.Rest): 13083 break 13084 removeList.append(gn) 13085 if removeList: 13086 self.remove(removeList, shiftOffsets=True) 13087 13088 # note: may need to set paddingLeft to 0 before examining 13089 13090 # bar duration is that suggested by time signature; it may 13091 # may not be the same as Stream duration, which is based on contents 13092 barDuration = self.barDuration 13093 proportion = self.barDurationProportion(barDuration=barDuration) 13094 if proportion < 1: 13095 # get 1 complement 13096 proportionShift = 1 - proportion 13097 self.paddingLeft = opFrac(barDuration.quarterLength * proportionShift) 13098 13099 # shift = barDuration.quarterLength * proportionShift 13100 # environLocal.printDebug(['got anacrusis shift:', shift, 13101 # barDuration.quarterLength, proportion]) 13102 # this will shift all elements 13103 # self.shiftElements(shift, classFilterList=(note.GeneralNote,)) 13104 else: 13105 pass 13106 # environLocal.printDebug(['padAsAnacrusis() called; however, 13107 # no anacrusis shift necessary:', barDuration.quarterLength, proportion]) 13108 13109 # -------------------------------------------------------------------------- 13110 @property 13111 def barDuration(self): 13112 ''' 13113 Return the bar duration, or the Duration specified by the TimeSignature, 13114 regardless of what elements are found in this Measure or the highest time. 13115 TimeSignature is found first within the Measure, 13116 or within a context based search. 13117 13118 To get the duration of the total length of elements, just use the 13119 `.duration` property. 13120 13121 Here we create a 3/4 measure and "over-stuff" it with five quarter notes. 13122 `barDuration` still gives a duration of 3.0, or a dotted quarter note, 13123 while `.duration` gives a whole note tied to a quarter. 13124 13125 13126 >>> m = stream.Measure() 13127 >>> m.timeSignature = meter.TimeSignature('3/4') 13128 >>> m.barDuration 13129 <music21.duration.Duration 3.0> 13130 >>> m.repeatAppend(note.Note(type='quarter'), 5) 13131 >>> m.barDuration 13132 <music21.duration.Duration 3.0> 13133 >>> m.duration 13134 <music21.duration.Duration 5.0> 13135 13136 The objects returned by `barDuration` and `duration` are 13137 full :class:`~music21.duration.Duration` 13138 objects, will all the relevant properties: 13139 13140 >>> m.barDuration.fullName 13141 'Dotted Half' 13142 >>> m.duration.fullName 13143 'Whole tied to Quarter (5 total QL)' 13144 ''' 13145 # TODO: it is possible that this should be cached or exposed as a method 13146 # as this search may take some time. 13147 if self.timeSignature is not None: 13148 ts = self.timeSignature 13149 else: # do a context-based search 13150 tsStream = self.getTimeSignatures(searchContext=True, 13151 returnDefault=False, 13152 sortByCreationTime=True) 13153 if not tsStream: 13154 try: 13155 ts = self.bestTimeSignature() 13156 except exceptions21.Music21Exception: 13157 return duration.Duration(self.highestTime) 13158 13159 # raise StreamException( 13160 # 'cannot determine bar duration without a time signature reference') 13161 else: # it is the first found 13162 ts = tsStream[0] 13163 return ts.barDuration 13164 13165 # -------------------------------------------------------------------------- 13166 # Music21Objects are stored in the Stream's elements list 13167 # properties are provided to store and access these attribute 13168 13169 def bestTimeSignature(self): 13170 ''' 13171 Given a Measure with elements in it, 13172 get a TimeSignature that contains all elements. 13173 Calls meter.bestTimeSignature(self) 13174 13175 Note: this does not yet accommodate triplets. 13176 13177 13178 We create a simple stream that should be in 3/4 13179 13180 13181 >>> s = converter.parse('C4 D4 E8 F8', format='tinyNotation', makeNotation=False) 13182 >>> m = stream.Measure() 13183 >>> for el in s: 13184 ... m.insert(el.offset, el) 13185 13186 But there is no TimeSignature! 13187 13188 >>> m.show('text') 13189 {0.0} <music21.note.Note C> 13190 {1.0} <music21.note.Note D> 13191 {2.0} <music21.note.Note E> 13192 {2.5} <music21.note.Note F> 13193 13194 So, we get the best Time Signature and put it in the Stream. 13195 13196 >>> ts = m.bestTimeSignature() 13197 >>> ts 13198 <music21.meter.TimeSignature 3/4> 13199 >>> m.timeSignature = ts 13200 >>> m.show('text') 13201 {0.0} <music21.meter.TimeSignature 3/4> 13202 {0.0} <music21.note.Note C> 13203 {1.0} <music21.note.Note D> 13204 {2.0} <music21.note.Note E> 13205 {2.5} <music21.note.Note F> 13206 13207 For further details about complex time signatures, etc. 13208 see `meter.bestTimeSignature()` 13209 13210 ''' 13211 return meter.bestTimeSignature(self) 13212 13213 def _getLeftBarline(self): 13214 barList = [] 13215 # directly access _elements, as do not want to get any bars 13216 # in _endElements 13217 for e in self._elements: 13218 if isinstance(e, bar.Barline): # take the first 13219 if self.elementOffset(e) == 0.0: 13220 barList.append(e) 13221 break 13222 if not barList: 13223 return None 13224 else: 13225 return barList[0] 13226 13227 def _setLeftBarline(self, barlineObj): 13228 insert = True 13229 if isinstance(barlineObj, str): 13230 barlineObj = bar.Barline(barlineObj) 13231 barlineObj.location = 'left' 13232 elif barlineObj is None: # assume removal 13233 insert = False 13234 else: # assume an Barline object 13235 barlineObj.location = 'left' 13236 13237 oldLeftBarline = self._getLeftBarline() 13238 if oldLeftBarline is not None: 13239 # environLocal.printDebug(['_setLeftBarline()', 'removing left barline']) 13240 junk = self.pop(self.index(oldLeftBarline)) 13241 if insert: 13242 # environLocal.printDebug(['_setLeftBarline()', 13243 # 'inserting new left barline', barlineObj]) 13244 self.insert(0, barlineObj) 13245 13246 leftBarline = property(_getLeftBarline, 13247 _setLeftBarline, 13248 doc=''' 13249 Get or set the left barline, or the Barline object 13250 found at offset zero of the Measure. Can be set either with a string 13251 representing barline style or a bar.Barline() object or None. 13252 Note that not all bars have 13253 barline objects here -- regular barlines don't need them. 13254 ''') 13255 13256 def _getRightBarline(self): 13257 # TODO: Move to Stream or make setting .rightBarline, etc. on Stream raise an exception... 13258 # look on _endElements 13259 barList = [] 13260 for e in self._endElements: 13261 if isinstance(e, bar.Barline): # take the first 13262 barList.append(e) 13263 break 13264 # barList = self.getElementsByClass(bar.Barline) 13265 if not barList: # do this before searching for barQL 13266 return None 13267 else: 13268 return barList[0] 13269 13270 def _setRightBarline(self, barlineObj): 13271 insert = True 13272 if isinstance(barlineObj, str): 13273 barlineObj = bar.Barline(barlineObj) 13274 barlineObj.location = 'right' 13275 elif barlineObj is None: # assume removal 13276 insert = False 13277 else: # assume an Barline object 13278 barlineObj.location = 'right' 13279 13280 # if a repeat, setup direction if not assigned 13281 if barlineObj is not None and isinstance(barlineObj, bar.Repeat): 13282 # environLocal.printDebug(['got barline obj w/ direction', barlineObj.direction]) 13283 if barlineObj.direction in ['start', None]: 13284 barlineObj.direction = 'end' 13285 oldRightBarline = self._getRightBarline() 13286 13287 if oldRightBarline is not None: 13288 # environLocal.printDebug(['_setRightBarline()', 'removing right barline']) 13289 junk = self.pop(self.index(oldRightBarline)) 13290 # insert into _endElements 13291 if insert: 13292 self.storeAtEnd(barlineObj) 13293 13294 # environLocal.printDebug(['post _setRightBarline', barlineObj, 13295 # 'len of elements highest', len(self._endElements)]) 13296 13297 rightBarline = property(_getRightBarline, 13298 _setRightBarline, 13299 doc=''' 13300 Get or set the right barline, or the Barline object 13301 found at the offset equal to the bar duration. 13302 13303 >>> b = bar.Barline('final') 13304 >>> m = stream.Measure() 13305 >>> print(m.rightBarline) 13306 None 13307 >>> m.rightBarline = b 13308 >>> m.rightBarline.type 13309 'final' 13310 13311 13312 A string can also be used instead: 13313 13314 >>> c = converter.parse('tinynotation: 3/8 C8 D E F G A B4.') 13315 >>> c.measure(1).rightBarline = 'light-light' 13316 >>> c.measure(3).rightBarline = 'light-heavy' 13317 >>> #_DOCS_SHOW c.show() 13318 13319 .. image:: images/stream_barline_demo.* 13320 :width: 211 13321 13322 OMIT_FROM_DOCS 13323 13324 .measure currently isn't the same as the 13325 original measure... 13326 13327 ''') 13328 13329 13330class Part(Stream): 13331 ''' 13332 A Stream subclass for designating music that is considered a single part. 13333 13334 When put into a Score object, Part objects are all collected in the `Score.parts` 13335 call. Otherwise they mostly work like generic Streams. 13336 13337 Generally the hierarchy goes: Score > Part > Measure > Voice, but you are not 13338 required to stick to this. 13339 13340 Part groupings (piano braces, etc.) are found in the :ref:`moduleLayout` module 13341 in the :class:`~music21.layout.StaffGroup` Spanner object. 13342 13343 OMIT_FROM_DOCS 13344 Check that this is True and works for everything before suggesting that it works! 13345 13346 May be enclosed in a staff (for instance, 2nd and 3rd trombone 13347 on a single staff), may enclose staves (piano treble and piano bass), 13348 or may not enclose or be enclosed by a staff (in which case, it 13349 assumes that this part fits on one staff and shares it with no other 13350 part 13351 ''' 13352 recursionType = 'flatten' 13353 13354 # _DOC_ATTR = { 13355 # } 13356 13357 def __init__(self, *args, **keywords): 13358 super().__init__(*args, **keywords) 13359 self._partName = None 13360 self._partAbbreviation = None 13361 13362 def _getPartName(self): 13363 if self._partName is not None: 13364 return self._partName 13365 elif '_partName' in self._cache: 13366 return self._cache['_partName'] 13367 else: 13368 pn = None 13369 for e in self.recurse().getElementsByClass('Instrument'): 13370 pn = e.partName 13371 if pn is None: 13372 pn = e.instrumentName 13373 if pn is not None: 13374 break 13375 self._cache['_partName'] = pn 13376 return pn 13377 13378 def _setPartName(self, newName): 13379 self._partName = newName 13380 13381 partName = property(_getPartName, _setPartName, doc=''' 13382 Gets or sets a string representing the name of this part 13383 as a whole (not counting instrument changes, etc.). 13384 13385 It can be set explicitly (or set on parsing) or it 13386 can take its name from the first :class:`~music21.instrument.Instrument` object 13387 encountered in the stream (or within a substream), 13388 first checking its .partName, then checking its .instrumentName 13389 13390 Can also return None. 13391 13392 >>> p = stream.Part() 13393 >>> p.partName is None 13394 True 13395 >>> cl = instrument.Clarinet() 13396 >>> p.insert(0, cl) 13397 >>> p.partName 13398 'Clarinet' 13399 >>> p.remove(cl) 13400 >>> p.partName is None 13401 True 13402 >>> p.insert(0, instrument.Flute()) 13403 >>> p.partName 13404 'Flute' 13405 >>> p.partName = 'Reed 1' 13406 >>> p.partName 13407 'Reed 1' 13408 13409 Note that changing an instrument's .partName or .instrumentName while it 13410 is already in the Stream will not automatically update this unless 13411 .coreElementsChanged() is called or this Stream's elements are otherwise altered. 13412 This is because the value is cached so that O(n) searches through the Stream 13413 do not need to be done every time. 13414 ''') 13415 13416 def _getPartAbbreviation(self): 13417 if self._partAbbreviation is not None: 13418 return self._partAbbreviation 13419 elif '_partAbbreviation' in self._cache: 13420 return self._cache['_partAbbreviation'] 13421 else: 13422 pn = None 13423 for e in self.recurse().getElementsByClass('Instrument'): 13424 pn = e.partAbbreviation 13425 if pn is None: 13426 pn = e.instrumentAbbreviation 13427 if pn is not None: 13428 break 13429 self._cache['_partAbbreviation'] = pn 13430 return pn 13431 13432 def _setPartAbbreviation(self, newName): 13433 self._partAbbreviation = newName 13434 13435 partAbbreviation = property(_getPartAbbreviation, _setPartAbbreviation, doc=''' 13436 Gets or sets a string representing the abbreviated name of this part 13437 as a whole (not counting instrument changes, etc.). 13438 13439 It can be set explicitly (or set on parsing) or it 13440 can take its name from the first :class:`~music21.instrument.Instrument` object 13441 encountered in the stream (or within a substream), 13442 first checking its .partAbbreviation, then checking its .instrumentAbbreviation 13443 13444 Can also return None. 13445 13446 >>> p = stream.Part() 13447 >>> p.partAbbreviation is None 13448 True 13449 >>> cl = instrument.Clarinet() 13450 >>> p.insert(0, cl) 13451 >>> p.partAbbreviation 13452 'Cl' 13453 >>> p.remove(cl) 13454 >>> p.partAbbreviation is None 13455 True 13456 >>> p.insert(0, instrument.Flute()) 13457 >>> p.partAbbreviation 13458 'Fl' 13459 >>> p.partAbbreviation = 'Rd 1' 13460 >>> p.partAbbreviation 13461 'Rd 1' 13462 13463 Note that changing an instrument's .partAbbreviation or .instrumentAbbreviation while it 13464 is already in the Stream will not automatically update this unless 13465 .coreElementsChanged() is called or this Stream's elements are otherwise altered. 13466 This is because the value is cached so that O(n) searches through the Stream 13467 do not need to be done every time. 13468 ''') 13469 13470 def makeAccidentals( 13471 self, 13472 *, 13473 alteredPitches=None, 13474 cautionaryPitchClass=True, 13475 cautionaryAll=False, 13476 inPlace=False, 13477 overrideStatus=False, 13478 cautionaryNotImmediateRepeat=True, 13479 tiePitchSet=None, 13480 ): 13481 ''' 13482 This overridden method of Stream.makeAccidentals 13483 provides the management of passing pitches from 13484 a past Measure to each new measure for processing. 13485 13486 Changed in v.7 -- `inPlace` defaults False 13487 ''' 13488 if not inPlace: # make a copy 13489 returnObj = self.coreCopyAsDerivation('makeAccidentals') 13490 else: 13491 returnObj = self 13492 # process make accidentals for each measure 13493 measureStream = returnObj.getElementsByClass('Measure') 13494 makeNotation.makeAccidentalsInMeasureStream( 13495 measureStream, 13496 alteredPitches=alteredPitches, 13497 cautionaryPitchClass=cautionaryPitchClass, 13498 cautionaryAll=cautionaryAll, 13499 overrideStatus=overrideStatus, 13500 cautionaryNotImmediateRepeat=cautionaryNotImmediateRepeat, 13501 tiePitchSet=tiePitchSet, 13502 ) 13503 if not inPlace: 13504 return returnObj 13505 else: # in place 13506 return None 13507 13508 13509class PartStaff(Part): 13510 ''' 13511 A Part subclass for designating music that is 13512 represented on a single staff but may only be one 13513 of many staves for a single part. 13514 ''' 13515 13516 13517# class Performer(Stream): 13518# ''' 13519# A Stream subclass for designating music to be performed by a 13520# single Performer. Should only be used when a single performer 13521# performs on multiple parts. E.g. Bass Drum and Triangle on separate 13522# staves performed by one player. 13523# 13524# a Part + changes of Instrument is fine for designating most cases 13525# where a player changes instrument in a piece. A part plus staves 13526# with individual instrument changes could also be a way of designating 13527# music that is performed by a single performer (see, for instance 13528# the Piano doubling Celesta part in Lukas Foss's Time Cycle). The 13529# Performer Stream-subclass could be useful for analyses of, for instance, 13530# how 5 percussionists chose to play a piece originally designated for 4 13531# (or 6) percussionists in the score. 13532# ''' 13533# # NOTE: not yet implemented 13534# pass 13535 13536class System(Stream): 13537 ''' 13538 Totally optional and used only in OMR and Capella: a designation that all the 13539 music in this Stream belongs in a single system. 13540 13541 The system object has two attributes, systemNumber (which number is it) 13542 and systemNumbering which says at what point the numbering of 13543 systems resets. It can be either "Score" (default), "Opus", or "Page". 13544 ''' 13545 systemNumber = 0 13546 systemNumbering = 'Score' # or Page; when do system numbers reset? 13547 13548 13549class Score(Stream): 13550 ''' 13551 A Stream subclass for handling multi-part music. 13552 13553 Almost totally optional (the largest containing Stream in a piece could be 13554 a generic Stream, or a Part, or a Staff). And Scores can be 13555 embedded in other Scores (in fact, our original thought was to call 13556 this class a Fragment because of this possibility of continuous 13557 embedding; though it's probably better to embed a Score in an Opus), 13558 but we figure that many people will like calling the largest 13559 container a Score and that this will become a standard. 13560 ''' 13561 recursionType = 'elementsOnly' 13562 13563 @property 13564 def parts(self): 13565 ''' 13566 Return all :class:`~music21.stream.Part` objects in a :class:`~music21.stream.Score`. 13567 13568 It filters out all other things that might be in a Score object, such as Metadata 13569 returning just the Parts. 13570 13571 13572 >>> s = corpus.parse('bach/bwv66.6') 13573 >>> s.parts 13574 <music21.stream.iterator.StreamIterator for Score:0x104af3a58 @:0> 13575 >>> len(s.parts) 13576 4 13577 13578 OMIT_FROM_DOCS 13579 13580 Ensure that getting from cache still will reset the iteration. 13581 13582 >>> for i, p in enumerate(s.parts): 13583 ... print(i, p) 13584 ... break 13585 0 <music21.stream.Part Soprano> 13586 13587 >>> for i, p in enumerate(s.parts): 13588 ... print(i, p) 13589 ... break 13590 0 <music21.stream.Part Soprano> 13591 ''' 13592 # return self.getElementsByClass('Part') 13593 if 'parts' not in self._cache or self._cache['parts'] is None: 13594 partIterator = self.getElementsByClass('Part') 13595 partIterator.overrideDerivation = 'parts' 13596 self._cache['parts'] = partIterator 13597 return self._cache['parts'] 13598 13599 def measures(self, 13600 numberStart, 13601 numberEnd, 13602 collect=('Clef', 'TimeSignature', 'Instrument', 'KeySignature'), 13603 gatherSpanners=GatherSpanners.ALL, 13604 indicesNotNumbers=False): 13605 # noinspection PyShadowingNames 13606 ''' 13607 This method overrides the :meth:`~music21.stream.Stream.measures` 13608 method on Stream. This creates a new Score stream that has the same measure 13609 range for all Parts. 13610 13611 The `collect` argument is a list of classes that will be collected; see 13612 Stream.measures() 13613 13614 >>> s = corpus.parse('bwv66.6') 13615 >>> post = s.measures(3, 5) # range is inclusive, i.e., [3, 5] 13616 >>> len(post.parts) 13617 4 13618 >>> len(post.parts[0].getElementsByClass('Measure')) 13619 3 13620 >>> len(post.parts[1].getElementsByClass('Measure')) 13621 3 13622 ''' 13623 post = self.__class__() 13624 # this calls on Music21Object, transfers groups, id if not id(self) 13625 post.mergeAttributes(self) 13626 # note that this will strip all objects that are not Parts 13627 for p in self.parts: 13628 # insert all at zero 13629 measuredPart = p.measures(numberStart, 13630 numberEnd, 13631 collect, 13632 gatherSpanners=gatherSpanners, 13633 indicesNotNumbers=indicesNotNumbers) 13634 post.insert(0, measuredPart) 13635 # must manually add any spanners; do not need to add .flatten(), 13636 # as Stream.measures will handle lower level 13637 if gatherSpanners: 13638 spStream = self.spanners 13639 for sp in spStream: 13640 post.insert(0, sp) 13641 13642 post.derivation.client = post 13643 post.derivation.origin = self 13644 post.derivation.method = 'measures' 13645 return post 13646 13647 def measure(self, 13648 measureNumber, 13649 collect=('Clef', 'TimeSignature', 'Instrument', 'KeySignature'), 13650 gatherSpanners=True, 13651 indicesNotNumbers=False): 13652 ''' 13653 Given a measure number (or measure index, if indicesNotNumbers is True) 13654 return another Score object which contains multiple parts but each of which has only a 13655 single :class:`~music21.stream.Measure` object if the 13656 Measure number exists, otherwise returns a score with parts that are empty. 13657 13658 This method overrides the :meth:`~music21.stream.Stream.measure` method on Stream to 13659 allow for finding a single "measure slice" within parts: 13660 13661 13662 >>> bachIn = corpus.parse('bach/bwv324.xml') 13663 >>> excerpt = bachIn.measure(2) 13664 >>> excerpt 13665 <music21.stream.Score 0x10322b5f8> 13666 >>> len(excerpt.parts) 13667 4 13668 >>> excerpt.parts[0].show('text') 13669 {0.0} <music21.instrument.Instrument 'P1: Soprano: '> 13670 {0.0} <music21.clef.TrebleClef> 13671 {0.0} <music21.key.Key of e minor> 13672 {0.0} <music21.meter.TimeSignature 4/4> 13673 {0.0} <music21.stream.Measure 2 offset=0.0> 13674 {0.0} <music21.note.Note B> 13675 {1.0} <music21.note.Note B> 13676 {2.0} <music21.note.Note B> 13677 {3.0} <music21.note.Note B> 13678 13679 Note that the parts created have all the meta-information outside the measure 13680 unless this information appears in the measure itself at the beginning: 13681 13682 >>> bachIn.measure(1).parts[0].show('text') 13683 {0.0} <music21.instrument.Instrument 'P1: Soprano: '> 13684 {0.0} <music21.stream.Measure 1 offset=0.0> 13685 {0.0} <music21.clef.TrebleClef> 13686 {0.0} <music21.key.Key of e minor> 13687 {0.0} <music21.meter.TimeSignature 4/4> 13688 {0.0} <music21.layout.SystemLayout> 13689 {0.0} <music21.note.Note B> 13690 {2.0} <music21.note.Note D> 13691 13692 This way the original measure objects can be returned without being altered. 13693 13694 The final measure slice of the piece can be obtained with index -1. Example: 13695 quickly get the last chord of the piece, without needing to run .chordify() 13696 on the whole piece: 13697 13698 >>> excerpt = bachIn.measure(-1) 13699 >>> excerptChords = excerpt.chordify() 13700 >>> excerptChords.show('text') 13701 {0.0} <music21.instrument.Instrument 'P1: Soprano: '> 13702 {0.0} <music21.clef.TrebleClef> 13703 {0.0} <music21.key.Key of e minor> 13704 {0.0} <music21.meter.TimeSignature 4/4> 13705 {0.0} <music21.stream.Measure 9 offset=0.0> 13706 {0.0} <music21.chord.Chord E2 G3 B3 E4> 13707 {4.0} <music21.bar.Barline type=final> 13708 13709 >>> lastChord = excerptChords.recurse().getElementsByClass('Chord').last() 13710 >>> lastChord 13711 <music21.chord.Chord E2 G3 B3 E4> 13712 13713 Note that we still do a .getElementsByClass('Chord') since many pieces end 13714 with nothing but a rest... 13715 ''' 13716 if measureNumber < 0: 13717 indicesNotNumbers = True 13718 13719 startMeasureNumber = measureNumber 13720 endMeasureNumber = measureNumber 13721 if indicesNotNumbers: 13722 endMeasureNumber += 1 13723 if startMeasureNumber == -1: 13724 endMeasureNumber = None 13725 13726 post = self.__class__() 13727 # this calls on Music21Object, transfers id, groups 13728 post.mergeAttributes(self) 13729 # note that this will strip all objects that are not Parts 13730 for p in self.getElementsByClass('Part'): 13731 # insert all at zero 13732 mStream = p.measures(startMeasureNumber, 13733 endMeasureNumber, 13734 collect=collect, 13735 gatherSpanners=gatherSpanners, 13736 indicesNotNumbers=indicesNotNumbers) 13737 post.insert(0, mStream) 13738 13739 if gatherSpanners: 13740 spStream = self.spanners 13741 for sp in spStream: 13742 post.insert(0, sp) 13743 13744 post.derivation.client = post 13745 post.derivation.origin = self 13746 post.derivation.method = 'measure' 13747 13748 return post 13749 13750 def expandRepeats(self): 13751 ''' 13752 Expand all repeats, as well as all repeat indications 13753 given by text expressions such as D.C. al Segno. 13754 13755 This method always returns a new Stream, with deepcopies 13756 of all contained elements at all level. 13757 ''' 13758 post = self.cloneEmpty(derivationMethod='expandRepeats') 13759 # this calls on Music21Object, transfers id, groups 13760 post.mergeAttributes(self) 13761 13762 # get all things in the score that are not Parts 13763 for e in self.iter().getElementsNotOfClass('Part'): 13764 eNew = copy.deepcopy(e) # assume that this is needed 13765 post.insert(self.elementOffset(e), eNew) 13766 13767 for p in self.getElementsByClass('Part'): 13768 # get spanners at highest level, not by Part 13769 post.insert(0, p.expandRepeats(copySpanners=False)) 13770 13771 # spannerBundle = spanner.SpannerBundle(list(post.flatten().spanners)) 13772 spannerBundle = post.spannerBundle # use property 13773 # iterate over complete semi flat (need containers); find 13774 # all new/old pairs 13775 for e in post.recurse(includeSelf=False): 13776 # update based on last id, new object 13777 if e.sites.hasSpannerSite(): 13778 origin = e.derivation.origin 13779 if origin is not None and e.derivation.method == '__deepcopy__': 13780 spannerBundle.replaceSpannedElement(origin, e) 13781 return post 13782 13783 def measureOffsetMap(self, classFilterList=None): 13784 ''' 13785 This Score method overrides the 13786 :meth:`~music21.stream.Stream.measureOffsetMap` method of Stream. 13787 This creates a map based on all contained Parts in this Score. 13788 Measures found in multiple Parts with the same offset will be 13789 appended to the same list. 13790 13791 If no parts are found in the score, then the normal 13792 :meth:`~music21.stream.Stream.measureOffsetMap` routine is called. 13793 13794 This method is smart and does not assume that all Parts 13795 have measures with identical offsets. 13796 ''' 13797 parts = self.iter().parts 13798 if not parts: 13799 return Stream.measureOffsetMap(self, classFilterList) 13800 # else: 13801 offsetMap = {} 13802 for p in parts: 13803 mapPartial = p.measureOffsetMap(classFilterList) 13804 # environLocal.printDebug(['mapPartial', mapPartial]) 13805 for k in mapPartial: 13806 if k not in offsetMap: 13807 offsetMap[k] = [] 13808 for m in mapPartial[k]: # get measures from partial 13809 if m not in offsetMap[k]: 13810 offsetMap[k].append(m) 13811 orderedOffsetMap = collections.OrderedDict(sorted(offsetMap.items(), key=lambda o: o[0])) 13812 return orderedOffsetMap 13813 13814 def sliceByGreatestDivisor(self, *, addTies=True, inPlace=False): 13815 ''' 13816 Slice all duration of all part by the minimum duration 13817 that can be summed to each concurrent duration. 13818 13819 Overrides method defined on Stream. 13820 ''' 13821 if not inPlace: # make a copy 13822 returnObj = self.coreCopyAsDerivation('sliceByGreatestDivisor') 13823 else: 13824 13825 returnObj = self 13826 13827 # find greatest divisor for each measure at a time 13828 # if no measures this will be zero 13829 mStream = returnObj.parts.first().getElementsByClass('Measure') 13830 mCount = len(mStream) 13831 if mCount == 0: 13832 mCount = 1 # treat as a single measure 13833 for i in range(mCount): # may be 1 13834 uniqueQuarterLengths = [] 13835 for p in returnObj.getElementsByClass('Part'): 13836 if p.hasMeasures(): 13837 m = p.getElementsByClass('Measure')[i] 13838 else: 13839 m = p # treat the entire part as one measure 13840 13841 # collect all unique quarter lengths 13842 for e in m.notesAndRests: 13843 # environLocal.printDebug(['examining e', i, e, e.quarterLength]) 13844 if e.quarterLength not in uniqueQuarterLengths: 13845 uniqueQuarterLengths.append(e.quarterLength) 13846 13847 # after ql for all parts, find divisor 13848 divisor = common.approximateGCD(uniqueQuarterLengths) 13849 # environLocal.printDebug(['Score.sliceByGreatestDivisor: 13850 # got divisor from unique ql:', divisor, uniqueQuarterLengths]) 13851 13852 for p in returnObj.getElementsByClass('Part'): 13853 # in place: already have a copy if nec 13854 # must do on measure at a time 13855 if p.hasMeasures(): 13856 m = p.getElementsByClass('Measure')[i] 13857 else: 13858 m = p # treat the entire part as one measure 13859 m.sliceByQuarterLengths(quarterLengthList=[divisor], 13860 target=None, 13861 addTies=addTies, 13862 inPlace=True) 13863 del mStream # cleanup Streams 13864 returnObj.coreElementsChanged() 13865 if not inPlace: 13866 return returnObj 13867 13868 def partsToVoices(self, 13869 voiceAllocation: Union[int, List[Union[List, int]]] = 2, 13870 permitOneVoicePerPart=False, 13871 setStems=True): 13872 # noinspection PyShadowingNames 13873 ''' 13874 Given a multi-part :class:`~music21.stream.Score`, 13875 return a new Score that combines parts into voices. 13876 13877 The `voiceAllocation` parameter sets the maximum number 13878 of voices per Part. 13879 13880 The `permitOneVoicePerPart` parameter, if True, will encode a 13881 single voice inside a single Part, rather than leaving it as 13882 a single Part alone, with no internal voices. 13883 13884 13885 >>> s = corpus.parse('bwv66.6') 13886 >>> len(s.flatten().notes) 13887 165 13888 >>> post = s.partsToVoices(voiceAllocation=4) 13889 >>> len(post.parts) 13890 1 13891 >>> len(post.parts.first().getElementsByClass('Measure').first().voices) 13892 4 13893 >>> len(post.flatten().notes) 13894 165 13895 13896 ''' 13897 sub = [] 13898 bundle = [] 13899 if common.isNum(voiceAllocation): 13900 voicesPerPart = voiceAllocation 13901 for pIndex, p in enumerate(self.parts): 13902 if pIndex % voicesPerPart == 0: 13903 sub = [] 13904 sub.append(p) 13905 else: 13906 sub.append(p) 13907 if pIndex % voicesPerPart == voicesPerPart - 1: 13908 bundle.append(sub) 13909 sub = [] 13910 if sub: # get last 13911 bundle.append(sub) 13912 # else, assume it is a list of groupings 13913 elif common.isIterable(voiceAllocation): 13914 voiceAllocation: List[Union[List, int]] 13915 for group in voiceAllocation: 13916 sub = [] 13917 # if a single entry 13918 if not common.isListLike(group): 13919 # group is a single index 13920 sub.append(self.parts[group]) 13921 else: 13922 for partId in group: 13923 sub.append(self.parts[partId]) 13924 bundle.append(sub) 13925 else: 13926 raise StreamException(f'incorrect voiceAllocation format: {voiceAllocation}') 13927 13928 # environLocal.printDebug(['partsToVoices() bundle:', bundle]) 13929 13930 s = self.cloneEmpty(derivationMethod='partsToVoices') 13931 s.metadata = self.metadata 13932 13933 for sub in bundle: # each sub contains parts 13934 if len(sub) == 1 and not permitOneVoicePerPart: 13935 # probably need to create a new part and measure 13936 s.insert(0, sub[0]) 13937 continue 13938 13939 pActive = Part() 13940 # iterate through each part 13941 for pIndex, p in enumerate(sub): 13942 # only check for measures once per part 13943 if pActive.hasMeasures(): 13944 hasMeasures = True 13945 else: 13946 hasMeasures = False 13947 13948 for mIndex, m in enumerate(p.getElementsByClass('Measure')): 13949 # environLocal.printDebug(['pIndex, p', pIndex, p, 13950 # 'mIndex, m', mIndex, m, 'hasMeasures', hasMeasures]) 13951 # only create measures if non already exist 13952 if not hasMeasures: 13953 # environLocal.printDebug(['creating measure']) 13954 mActive = Measure() 13955 # some attributes may be none 13956 # note: not copying here; and first part read will provide 13957 # attributes; possible other parts may have other attributes 13958 mActive.mergeAttributes(m) 13959 mActive.mergeElements(m, classFilterList=( 13960 'Barline', 'TimeSignature', 'Clef', 'KeySignature')) 13961 13962 # if m.timeSignature is not None: 13963 # mActive.timeSignature = m.timeSignature 13964 # if m.keySignature is not None: 13965 # mActive.keySignature = m.keySignature 13966 # if m.clef is not None: 13967 # mActive.clef = m.clef 13968 else: 13969 mActive = pActive.getElementsByClass('Measure')[mIndex] 13970 13971 # transfer elements into a voice 13972 v = Voice() 13973 v.id = pIndex 13974 # for now, just take notes, including rests 13975 for e in m.notesAndRests: # m.getElementsByClass(): 13976 if setStems and hasattr(e, 'stemDirection'): 13977 e.stemDirection = 'up' if pIndex % 2 == 0 else 'down' 13978 v.insert(e.getOffsetBySite(m), e) 13979 # insert voice in new measure 13980 # environLocal.printDebug(['inserting voice', v, v.id, 'into measure', mActive]) 13981 mActive.insert(0, v) 13982 # mActive.show('t') 13983 # only insert measure if new part does not already have measures 13984 if not hasMeasures: 13985 pActive.insert(m.getOffsetBySite(p), mActive) 13986 13987 s.insert(0, pActive) 13988 pActive = None 13989 return s 13990 13991 def implode(self): 13992 ''' 13993 Reduce a polyphonic work into two staves. 13994 13995 Currently, this is just a synonym for `partsToVoices` with 13996 `voiceAllocation = 2`, and `permitOneVoicePerPart = False`, 13997 but someday this will have better methods for finding identical 13998 parts, etc. 13999 ''' 14000 voiceAllocation = 2 14001 permitOneVoicePerPart = False 14002 14003 return self.partsToVoices( 14004 voiceAllocation=voiceAllocation, 14005 permitOneVoicePerPart=permitOneVoicePerPart 14006 ) 14007 14008 def flattenParts(self, classFilterList=('Note', 'Chord')): 14009 # noinspection PyShadowingNames 14010 ''' 14011 Given a Score, combine all Parts into a single Part 14012 with all elements found in each Measure of the Score. 14013 14014 The `classFilterList` can be used to specify which objects 14015 contained in Measures are transferred. 14016 14017 It also flattens all voices within a part. 14018 14019 Deprecated in v7. 14020 14021 >>> s = corpus.parse('bwv66.6') 14022 >>> len(s.parts) 14023 4 14024 >>> len(s.flatten().notes) 14025 165 14026 >>> post = s.flattenParts() 14027 >>> isinstance(post, stream.Part) 14028 True 14029 >>> len(post.flatten().notes) 14030 165 14031 ''' 14032 post = self.parts.first().template(fillWithRests=False, retainVoices=False) 14033 for i, m in enumerate(post.getElementsByClass('Measure')): 14034 for p in self.parts: 14035 mNew = copy.deepcopy(p.getElementsByClass('Measure')[i]).flatten() 14036 for e in mNew: 14037 match = False 14038 for cf in classFilterList: 14039 if cf in e.classes: 14040 match = True 14041 break 14042 if match: 14043 m.insert(e.getOffsetBySite(mNew), e) 14044 return post 14045 14046 def makeNotation(self, 14047 meterStream=None, 14048 refStreamOrTimeRange=None, 14049 inPlace=False, 14050 bestClef=False, 14051 **subroutineKeywords): 14052 ''' 14053 This method overrides the makeNotation method on Stream, 14054 such that a Score object with one or more Parts or Streams 14055 that may not contain well-formed notation may be transformed 14056 and replaced by well-formed notation. 14057 14058 If `inPlace` is True, this is done in-place; 14059 if `inPlace` is False, this returns a modified deep copy. 14060 ''' 14061 if inPlace: 14062 returnStream = self 14063 else: 14064 returnStream = self.coreCopyAsDerivation('makeNotation') 14065 returnStream.coreGatherMissingSpanners() # get spanners needed but not here! 14066 14067 # do not assume that we have parts here 14068 if self.hasPartLikeStreams(): 14069 for s in returnStream.getElementsByClass('Stream'): 14070 # process all component Streams inPlace 14071 s.makeNotation(meterStream=meterStream, 14072 refStreamOrTimeRange=refStreamOrTimeRange, 14073 inPlace=True, 14074 bestClef=bestClef, 14075 **subroutineKeywords) 14076 # note: while the local-streams have updated their caches, the 14077 # containing score has an out-of-date cache of flat. 14078 # thus, must call elements changed 14079 # but... since all we have done in this method is call coreGatherMissingSpanners() 14080 # and makeNotation(), neither of which are supposed to leave the stream 14081 # unusable (with an out-of-date cache), the original issue was likely deeper 14082 # no matter, let's just be extra cautious and run this here (Feb 2021 - JTW) 14083 returnStream.coreElementsChanged() 14084 else: # call the base method 14085 super(Score, returnStream).makeNotation(meterStream=meterStream, 14086 refStreamOrTimeRange=refStreamOrTimeRange, 14087 inPlace=True, 14088 bestClef=bestClef, 14089 **subroutineKeywords) 14090 14091 if inPlace: 14092 return None 14093 else: 14094 return returnStream 14095 14096 14097class Opus(Stream): 14098 ''' 14099 A Stream subclass for handling multi-work music encodings. 14100 Many ABC files, for example, define multiple works or parts within a single file. 14101 14102 Opus objects can contain multiple Score objects, or even other Opus objects! 14103 ''' 14104 recursionType = 'elementsOnly' 14105 14106 # TODO: get by title, possibly w/ regex 14107 14108 def getNumbers(self): 14109 ''' 14110 Return a list of all numbers defined in this Opus. 14111 14112 >>> o = corpus.parse('josquin/oVenusBant') 14113 >>> o.getNumbers() 14114 ['1', '2', '3'] 14115 ''' 14116 post = [] 14117 for s in self.getElementsByClass('Score'): 14118 post.append(s.metadata.number) 14119 return post 14120 14121 def getScoreByNumber(self, opusMatch): 14122 # noinspection PyShadowingNames 14123 ''' 14124 Get Score objects from this Stream by number. 14125 Performs title search using the 14126 :meth:`~music21.metadata.Metadata.search` method, 14127 and returns the first result. 14128 14129 >>> o = corpus.parse('josquin/oVenusBant') 14130 >>> o.getNumbers() 14131 ['1', '2', '3'] 14132 >>> s = o.getScoreByNumber(2) 14133 >>> s.metadata.title 14134 'O Venus bant' 14135 >>> s.metadata.alternativeTitle 14136 'Tenor' 14137 ''' 14138 for s in self.getElementsByClass('Score'): 14139 match, unused_field = s.metadata.search(opusMatch, 'number') 14140 if match: 14141 return s 14142 14143 # noinspection SpellCheckingInspection 14144 def getScoreByTitle(self, titleMatch): 14145 # noinspection SpellCheckingInspection, PyShadowingNames 14146 ''' 14147 Get Score objects from this Stream by a title. 14148 Performs title search using the :meth:`~music21.metadata.Metadata.search` method, 14149 and returns the first result. 14150 14151 >>> o = corpus.parse('essenFolksong/erk5') 14152 >>> s = o.getScoreByTitle('Vrienden, kommt alle gaere') 14153 >>> s.metadata.title 14154 'Vrienden, kommt alle gaere' 14155 14156 Regular expressions work fine 14157 14158 >>> s = o.getScoreByTitle('(.*)kommt(.*)') 14159 >>> s.metadata.title 14160 'Vrienden, kommt alle gaere' 14161 ''' 14162 for s in self.getElementsByClass('Score'): 14163 match, unused_field = s.metadata.search(titleMatch, 'title') 14164 if match: 14165 return s 14166 14167 @property 14168 def scores(self): 14169 ''' 14170 Return all :class:`~music21.stream.Score` objects 14171 in an iterator 14172 ''' 14173 return self.getElementsByClass('Score') # replacing with bare Score is not working. 14174 14175 def mergeScores(self): 14176 # noinspection PyShadowingNames 14177 ''' 14178 Some Opus objects represent numerous scores 14179 that are individual parts of the same work. 14180 This method will treat each contained Score as a Part, 14181 merging and returning a single Score with merged Metadata. 14182 14183 >>> from music21 import corpus 14184 >>> o = corpus.parse('josquin/milleRegrets') 14185 >>> s = o.mergeScores() 14186 >>> s.metadata.title 14187 'Mille regrets' 14188 >>> len(s.parts) 14189 4 14190 ''' 14191 sNew = Score() 14192 mdNew = metadata.Metadata() 14193 14194 for s in self.scores: 14195 p = s.parts.first().makeNotation() # assuming only one part 14196 sNew.insert(0, p) 14197 14198 md = s.metadata 14199 # presently just getting the first of attributes encountered 14200 if md is not None: 14201 # environLocal.printDebug(['sub-score meta data', md, 14202 # 'md.composer', md.composer, 'md.title', md.title]) 14203 if md.title is not None and mdNew.title is None: 14204 mdNew.title = md.title 14205 if md.composer is not None and mdNew.composer is None: 14206 mdNew.composer = md.composer 14207 14208 sNew.insert(0, mdNew) 14209 return sNew 14210 14211 # ------------------------------------------------------------------------- 14212 def write(self, fmt=None, fp=None, **keywords): 14213 ''' 14214 Displays an object in a format provided by the `fmt` argument or, if not 14215 provided, the format set in the user's Environment. 14216 14217 This method overrides the behavior specified in 14218 :class:`~music21.base.Music21Object` for all formats besides explicit 14219 lily.x calls. 14220 14221 Individual files are written for each score; returns the last file written. 14222 14223 >>> sc1 = stream.Score() 14224 >>> sc2 = stream.Score() 14225 >>> o = stream.Opus() 14226 >>> o.append([sc1, sc2]) 14227 14228 #_DOCS_SHOW >>> o.write() 14229 #_DOCS_SHOW PosixPath('/some/temp/path-2.xml') 14230 ''' 14231 if not self.scores: 14232 return None 14233 14234 if fmt is not None and 'lily' in fmt: 14235 return Stream.write(self, fmt, fp, **keywords) 14236 elif common.runningUnderIPython(): 14237 return Stream.write(self, fmt, fp, **keywords) 14238 14239 delete = False 14240 if fp is None: 14241 if fmt is None: 14242 suffix = '.' + environLocal['writeFormat'] 14243 else: 14244 unused_format, suffix = common.findFormat(fmt) 14245 fp = environLocal.getTempFile(suffix=suffix, returnPathlib=False) 14246 # Mark for deletion, because it won't actually be used 14247 delete = True 14248 if isinstance(fp, str): 14249 fp = pathlib.Path(fp) 14250 14251 fpParent = fp.parent 14252 fpStem = fp.stem 14253 fpSuffix = '.'.join(fp.suffixes) 14254 14255 post = [] 14256 placesRequired = math.ceil(math.log10(len(self.scores))) 14257 for i, s in enumerate(self.scores): 14258 # if i = 9, num = 10, take log10(11) so the result is strictly greater than 1.0 14259 placesConsumed = math.ceil(math.log10(i + 2)) 14260 zeroesNeeded = placesRequired - placesConsumed 14261 zeroes = '0' * zeroesNeeded 14262 scoreName = fpStem + '-' + zeroes + str(i + 1) + fpSuffix 14263 fpToUse = fpParent / scoreName 14264 fpReturned = s.write(fmt=fmt, fp=fpToUse, **keywords) 14265 environLocal.printDebug(f'Component {s} written to {fpReturned}') 14266 post.append(fpReturned) 14267 14268 if delete: 14269 os.remove(fp) 14270 14271 return post[-1] if post else None 14272 14273 def show(self, fmt=None, app=None, **keywords): 14274 ''' 14275 Show an Opus file. 14276 14277 This method overrides the behavior specified in 14278 :class:`~music21.base.Music21Object` for all 14279 formats besides explicit lily.x calls. or when running under IPython notebook. 14280 ''' 14281 if fmt is not None and 'lily' in fmt: 14282 return Stream.show(self, fmt, app, **keywords) 14283 elif common.runningUnderIPython(): 14284 return Stream.show(self, fmt, app, **keywords) 14285 else: 14286 for s in self.scores: 14287 s.show(fmt=fmt, app=app, **keywords) 14288 14289 14290# ----------------------------------------------------------------------------- 14291# Special stream sub-classes that are instantiated as hidden attributes within 14292# other Music21Objects (i.e., Spanner and Variant). 14293 14294 14295class SpannerStorage(Stream): 14296 ''' 14297 For advanced use. This Stream subclass is only used 14298 inside of a Spanner object to provide object storage 14299 of connected elements (things the Spanner spans). 14300 14301 This subclass name can be used to search in an 14302 object's .sites and find any and all 14303 locations that are SpannerStorage objects. 14304 14305 A `spannerParent` keyword argument must be 14306 provided by the Spanner in creation. 14307 14308 TODO v7: rename spannerParent to client. 14309 ''' 14310 14311 def __init__(self, *arguments, **keywords): 14312 # No longer need store as weakref since Py2.3 and better references 14313 self.spannerParent = None 14314 if 'spannerParent' in keywords: 14315 self.spannerParent = keywords['spannerParent'] 14316 del keywords['spannerParent'] 14317 super().__init__(*arguments, **keywords) 14318 14319 # must provide a keyword argument with a reference to the spanner 14320 # parent could name spannerContainer or other? 14321 14322 # environLocal.printDebug('keywords', keywords) 14323 14324 # NOTE: for serialization, this will need to properly tag 14325 # the spanner parent by updating the scaffolding code. 14326 def coreSelfActiveSite(self, el): 14327 ''' 14328 Never set activeSite to spannerStorage 14329 ''' 14330 pass 14331 14332 def coreStoreAtEnd(self, element, setActiveSite=True): # pragma: no cover 14333 raise StreamException('SpannerStorage cannot store at end.') 14334 14335 def replace(self, 14336 target: base.Music21Object, 14337 replacement: base.Music21Object, 14338 *, 14339 recurse: bool = False, 14340 allDerived: bool = True) -> None: 14341 ''' 14342 Overrides :meth:`~music21.stream.Stream.replace` in order to check first 14343 whether `replacement` already exists in `self`. If so, delete `target` from 14344 `self` and return; otherwise call the superclass method. 14345 14346 New in v7. 14347 ''' 14348 # Does not perform a recursive search, but shouldn't need to 14349 if replacement in self: 14350 self.remove(target) 14351 return 14352 super().replace(target, replacement, recurse=recurse, allDerived=allDerived) 14353 14354 14355class VariantStorage(Stream): 14356 ''' 14357 For advanced use. This Stream subclass is only 14358 used inside of a Variant object to provide object 14359 storage of connected elements (things the Variant 14360 defines). 14361 14362 This subclass name can be used to search in an 14363 object's .sites and find any and all 14364 locations that are VariantStorage objects. 14365 14366 A `variantParent` keyword argument must be provided 14367 by the Variant in creation. 14368 14369 # TODO v7: rename variantParent to client 14370 ''' 14371 14372 def __init__(self, *arguments, **keywords): 14373 super().__init__(*arguments, **keywords) 14374 # must provide a keyword argument with a reference to the variant 14375 # parent 14376 self.variantParent = None 14377 if 'variantParent' in keywords: 14378 self.variantParent = keywords['variantParent'] 14379 14380 14381# ----------------------------------------------------------------------------- 14382 14383 14384# ----------------------------------------------------------------------------- 14385 14386 14387class Test(unittest.TestCase): 14388 ''' 14389 Note: most Stream tests are found in stream.tests 14390 ''' 14391 14392 def testCopyAndDeepcopy(self): 14393 '''Test copying all objects defined in this module 14394 ''' 14395 for part in sys.modules[self.__module__].__dict__: 14396 if part.startswith('_') or part.startswith('__'): 14397 continue 14398 elif part in ['Test', 'TestExternal']: 14399 continue 14400 elif callable(part): # pragma: no cover 14401 # environLocal.printDebug(['testing copying on', part]) 14402 # noinspection PyTypeChecker 14403 obj = getattr(self.__module__, part)() 14404 a = copy.copy(obj) 14405 b = copy.deepcopy(obj) 14406 self.assertNotEqual(a, obj) 14407 self.assertNotEqual(b, obj) 14408 14409 14410# ----------------------------------------------------------------------------- 14411# define presented order in documentation 14412_DOC_ORDER = [Stream, Measure, Part, Score, Opus, Voice] 14413 14414 14415if __name__ == '__main__': 14416 import music21 14417 music21.mainTest(Test) 14418