1# -*- coding: utf-8 -*- 2# ----------------------------------------------------------------------------- 3# Name: meter.py 4# Purpose: Classes for meters 5# 6# Authors: Christopher Ariza 7# Michael Scott Cuthbert 8# 9# Copyright: Copyright © 2009-2012, 2015, 2021 Michael Scott Cuthbert 10# and the music21 Project 11# License: BSD, see license.txt 12# ----------------------------------------------------------------------------- 13''' 14This module defines the :class:`~music21.meter.TimeSignature` object, 15as well as component objects for defining nested metrical structures, 16:class:`~music21.meter.MeterTerminal` and :class:`~music21.meter.MeterSequence` objects. 17''' 18import copy 19import fractions 20from typing import Optional 21 22from music21 import base 23from music21 import beam 24from music21 import common 25from music21 import defaults 26from music21 import duration 27from music21 import environment 28from music21 import style 29from music21.exceptions21 import MeterException, TimeSignatureException 30 31from music21.common.enums import MeterDivision 32from music21.common.numberTools import opFrac 33from music21.meter.tools import slashToTuple, proportionToFraction 34from music21.meter.core import MeterSequence 35 36environLocal = environment.Environment('meter') 37 38 39# ----------------------------------------------------------------------------- 40 41# also [pow(2,x) for x in range(8)] 42MIN_DENOMINATOR_TYPE = '128th' 43 44# store a module-level dictionary of partitioned meter sequences used 45# for setting default accent weights; store as needed 46_meterSequenceAccentArchetypes = {} 47 48 49def bestTimeSignature(meas: 'music21.stream.Stream') -> 'music21.meter.TimeSignature': 50 # noinspection PyShadowingNames 51 ''' 52 Given a Measure (or any Stream) with elements in it, get a TimeSignature that contains all 53 elements. 54 55 Note: this does not yet accommodate triplets. 56 57 >>> s = converter.parse('tinynotation: C4 D4 E8 F8').flatten().notes 58 >>> m = stream.Measure() 59 >>> for el in s: 60 ... m.insert(el.offset, el) 61 >>> ts = meter.bestTimeSignature(m) 62 >>> ts 63 <music21.meter.TimeSignature 3/4> 64 65 >>> s2 = converter.parse('tinynotation: C8. D16 E8 F8. G16 A8').flatten().notes 66 >>> m2 = stream.Measure() 67 >>> for el in s2: 68 ... m2.insert(el.offset, el) 69 >>> ts2 = meter.bestTimeSignature(m2) 70 >>> ts2 71 <music21.meter.TimeSignature 6/8> 72 73 >>> s3 = converter.parse('C2 D2 E2', format='tinyNotation').flatten().notes 74 >>> m3 = stream.Measure() 75 >>> for el in s3: 76 ... m3.insert(el.offset, el) 77 >>> ts3 = meter.bestTimeSignature(m3) 78 >>> ts3 79 <music21.meter.TimeSignature 3/2> 80 81 >>> s4 = converter.parse('C8. D16 E8 F8. G16 A8 C4. D4.', format='tinyNotation').flatten().notes 82 >>> m4 = stream.Measure() 83 >>> for el in s4: 84 ... m4.insert(el.offset, el) 85 >>> ts4 = meter.bestTimeSignature(m4) 86 >>> ts4 87 <music21.meter.TimeSignature 12/8> 88 89 >>> s5 = converter.parse('C4 D2 E4 F2', format='tinyNotation').flatten().notes 90 >>> m5 = stream.Measure() 91 >>> for el in s5: 92 ... m5.insert(el.offset, el) 93 >>> ts5 = meter.bestTimeSignature(m5) 94 >>> ts5 95 <music21.meter.TimeSignature 6/4> 96 97 >>> s6 = converter.parse('C4 D16.', format='tinyNotation').flatten().notes 98 >>> m6 = stream.Measure() 99 >>> for el in s6: 100 ... m6.insert(el.offset, el) 101 >>> ts6 = meter.bestTimeSignature(m6) 102 >>> ts6 103 <music21.meter.TimeSignature 11/32> 104 105 106 Complex durations (arose in han2.abc, number 445) 107 108 >>> m7 = stream.Measure() 109 >>> m7.append(note.Note('D', quarterLength=3.5)) 110 >>> m7.append(note.Note('E', quarterLength=5.5)) 111 >>> ts7 = meter.bestTimeSignature(m7) 112 >>> ts7 113 <music21.meter.TimeSignature 9/4> 114 ''' 115 minDurQL = 4 # smallest denominator; start with a whole note 116 # find sum of all durations in quarter length 117 # find if there are any dotted durations 118 minDurDots = 0 119 sumDurQL = opFrac(meas.duration.quarterLength) 120 # beatStrAvg = 0 121 # beatStrAvg += e.beatStrength 122 numerator = 0 123 denominator = 1 124 125 for e in meas.recurse().notesAndRests: 126 if e.quarterLength == 0.0: 127 continue # case of grace durations 128 if (e.quarterLength < minDurQL 129 and not isinstance(opFrac(e.quarterLength), fractions.Fraction)): 130 # no non-power2 signatures 131 minDurQL = e.quarterLength 132 minDurDots = e.duration.dots 133 134 # first, we need to evenly divide min dur into total 135 minDurTest = minDurQL 136 if isinstance(sumDurQL, fractions.Fraction): 137 # not a power of two -- some tuplets, etc. 138 numerator = sumDurQL.numerator 139 denominator = sumDurQL.denominator 140 else: 141 i = 10 142 while i > 0: 143 partsFloor = int(sumDurQL / minDurTest) 144 partsReal = opFrac(sumDurQL / minDurTest) 145 if (partsFloor == partsReal 146 or minDurTest <= duration.typeToDuration[MIN_DENOMINATOR_TYPE]): 147 break 148 # need to break down minDur until we can get a match 149 else: 150 minDurTest = minDurTest / (2 * common.dotMultiplier(minDurDots)) 151 i -= 1 152 153 # see if we can get a type for the denominator 154 # if we do not have a match; we need to break down this value 155 match = False 156 durationMinLimit = duration.typeToDuration[MIN_DENOMINATOR_TYPE] 157 i = 10 158 while i > 0: 159 if minDurTest < durationMinLimit: 160 minDurTest = durationMinLimit 161 break 162 try: 163 dType, match = duration.quarterLengthToClosestType(minDurTest) 164 except ZeroDivisionError: 165 raise MeterException('Cannot find a good match for this measure') 166 167 if match or dType == MIN_DENOMINATOR_TYPE: 168 break 169 minDurTest = minDurTest / (2 * common.dotMultiplier(minDurDots)) 170 i -= 1 171 172 minDurQL = minDurTest 173 dType, match = duration.quarterLengthToClosestType(minDurQL) 174 if not match: # cant find a type for a denominator 175 raise MeterException(f'cannot find a type for denominator {minDurQL}') 176 177 # denominator is the numerical representation of the min type 178 # e.g., quarter is 4, whole is 1 179 for num, typeName in duration.typeFromNumDict.items(): 180 if typeName == dType: 181 if num >= 1: 182 num = int(num) 183 denominator = num 184 break 185 # numerator is the count of min parts in the sum 186 multiplier = 1 187 while i > 0: 188 numerator = multiplier * sumDurQL / minDurQL 189 if numerator == int(numerator): 190 break 191 multiplier *= 2 192 i -= 1 193 194 numerator = int(numerator) 195 denominator *= multiplier 196 # simplifies to "simplest terms," with 4 in denominator, before testing beat strengths 197 gcdValue = common.euclidGCD(numerator, denominator) 198 numerator = numerator // gcdValue 199 denominator = denominator // gcdValue 200 201 # simplifies rare time signatures like 16/16 and 1/1 to 4/4 202 if numerator == denominator and numerator not in [2, 4]: 203 numerator = 4 204 denominator = 4 205 elif numerator != denominator and denominator == 1: 206 numerator *= 4 207 denominator *= 4 208 elif numerator != denominator and denominator == 2: 209 numerator *= 2 210 denominator *= 2 211 212 # a fairly accurate test of whether 3/4 or 6/8 is more appropriate (see doctests) 213 if numerator == 3 and denominator == 4: 214 ts1 = TimeSignature('3/4') 215 ts2 = TimeSignature('6/8') 216 str1 = ts1.averageBeatStrength(meas) 217 str2 = ts2.averageBeatStrength(meas) 218 219 if str1 <= str2: 220 return ts2 221 else: 222 return ts1 223 224 # tries three time signatures if "simplest" time signature is 6/4 or 3/2 225 elif numerator == 6 and denominator == 4: 226 ts1 = TimeSignature('6/4') 227 ts2 = TimeSignature('12/8') 228 ts3 = TimeSignature('3/2') 229 str1 = ts1.averageBeatStrength(meas) 230 str2 = ts2.averageBeatStrength(meas) 231 str3 = ts3.averageBeatStrength(meas) 232 m = max(str1, str2, str3) 233 if m == str1: 234 return ts1 235 elif m == str3: 236 return ts3 237 else: 238 return ts2 239 240 # environLocal.printDebug(['n/d', numerator, denominator]) 241 else: 242 ts = TimeSignature() 243 ts.loadRatio(numerator, denominator) 244 return ts 245 246 247# ----------------------------------------------------------------------------- 248 249 250class TimeSignature(base.Music21Object): 251 r''' 252 The `TimeSignature` object represents time signatures in musical scores 253 (4/4, 3/8, 2/4+5/16, Cut, etc.). 254 255 `TimeSignatures` should be present in the first `Measure` of each `Part` 256 that they apply to. Alternatively you can put the time signature at the 257 front of a `Part` or at the beginning of a `Score` and they will work 258 within music21 but they won't necessarily display properly in musicxml, 259 lilypond, etc. So best is to create structures like this: 260 261 >>> s = stream.Score() 262 >>> p = stream.Part() 263 >>> m1 = stream.Measure() 264 >>> ts = meter.TimeSignature('3/4') 265 >>> m1.insert(0, ts) 266 >>> m1.insert(0, note.Note('C#3', type='half')) 267 >>> n = note.Note('D3', type='quarter') # we will need this later 268 >>> m1.insert(1.0, n) 269 >>> m1.number = 1 270 >>> p.insert(0, m1) 271 >>> s.insert(0, p) 272 >>> s.show('t') 273 {0.0} <music21.stream.Part ...> 274 {0.0} <music21.stream.Measure 1 offset=0.0> 275 {0.0} <music21.meter.TimeSignature 3/4> 276 {0.0} <music21.note.Note C#> 277 {1.0} <music21.note.Note D> 278 279 Basic operations on a TimeSignature object are designed to be very simple. 280 281 >>> ts.ratioString 282 '3/4' 283 284 >>> ts.numerator 285 3 286 287 >>> ts.beatCount 288 3 289 290 >>> ts.beatCountName 291 'Triple' 292 293 >>> ts.beatDuration.quarterLength 294 1.0 295 296 As an alternative to putting a `TimeSignature` in a Stream at a specific 297 position (offset), it can be assigned to a special property in Measure that 298 positions the TimeSignature at the start of a Measure. Notice that when we 299 `show()` the Measure (or if we iterate through it), the TimeSignature 300 appears as if it's in the measure itself: 301 302 >>> m2 = stream.Measure() 303 >>> m2.number = 2 304 >>> ts2 = meter.TimeSignature('2/4') 305 >>> m2.timeSignature = ts2 306 >>> m2.append(note.Note('E3', type='half')) 307 >>> p.append(m2) 308 >>> s.show('text') 309 {0.0} <music21.stream.Part ...> 310 {0.0} <music21.stream.Measure 1 offset=0.0> 311 {0.0} <music21.meter.TimeSignature 3/4> 312 {0.0} <music21.note.Note C#> 313 {1.0} <music21.note.Note D> 314 {2.0} <music21.stream.Measure 2 offset=2.0> 315 {0.0} <music21.meter.TimeSignature 2/4> 316 {0.0} <music21.note.Note E> 317 318 Once a Note has a local TimeSignature, a Note can get its beat position and 319 other meter-specific parameters. Remember `n`, our quarter note at offset 320 2.0 of `m1`, a 3/4 measure? Let's get its beat: 321 322 >>> n.beat 323 2.0 324 325 This feature is more useful if there are more beats: 326 327 >>> m3 = stream.Measure() 328 >>> m3.timeSignature = meter.TimeSignature('3/4') 329 >>> eighth = note.Note(type='eighth') 330 >>> m3.repeatAppend(eighth, 6) 331 >>> [thisNote.beatStr for thisNote in m3.notes] 332 ['1', '1 1/2', '2', '2 1/2', '3', '3 1/2'] 333 334 Now lets change its measure's TimeSignature and see what happens: 335 336 >>> sixEight = meter.TimeSignature('6/8') 337 >>> m3.timeSignature = sixEight 338 >>> [thisNote.beatStr for thisNote in m3.notes] 339 ['1', '1 1/3', '1 2/3', '2', '2 1/3', '2 2/3'] 340 341 TimeSignature('6/8') defaults to fast 6/8: 342 343 >>> sixEight.beatCount 344 2 345 346 >>> sixEight.beatDuration.quarterLength 347 1.5 348 349 >>> sixEight.beatDivisionCountName 350 'Compound' 351 352 Let's make it slow 6/8 instead: 353 354 >>> sixEight.beatCount = 6 355 >>> sixEight.beatDuration.quarterLength 356 0.5 357 358 >>> sixEight.beatDivisionCountName 359 'Simple' 360 361 Now let's look at the `beatStr` for each of the notes in `m3`: 362 363 >>> [thisNote.beatStr for thisNote in m3.notes] 364 ['1', '2', '3', '4', '5', '6'] 365 366 As of v7., 3/8 also defaults to fast 3/8, that is, one beat: 367 368 >>> meter.TimeSignature('3/8').beatCount 369 1 370 371 `TimeSignatures` can also use symbols instead of numbers 372 373 >>> tsCommon = meter.TimeSignature('c') # or common 374 >>> tsCommon.beatCount 375 4 376 >>> tsCommon.denominator 377 4 378 379 >>> tsCommon.symbol 380 'common' 381 382 >>> tsCut = meter.TimeSignature('cut') 383 >>> tsCut.beatCount 384 2 385 >>> tsCut.denominator 386 2 387 388 >>> tsCut.symbol 389 'cut' 390 391 For other time signatures, the symbol is '' (not set) or 'normal' 392 393 >>> sixEight.symbol 394 '' 395 396 397 For complete details on using this object, see 398 :ref:`User's Guide Chapter 14: Time Signatures <usersGuide_14_timeSignatures>` and 399 :ref:`User's Guide Chapter 55: Advanced Meter <usersGuide_55_advancedMeter>` and 400 401 402 That's it for the simple aspects of `TimeSignature` objects. You know 403 enough to get started now! 404 405 Under the hood, they're extremely powerful. For musicians, TimeSignatures 406 do (at least) three different things: 407 408 * They define where the beats in the measure are and how many there are. 409 410 * They indicate how the notes should be beamed 411 412 * They give a sense of how much accent or weight each note gets, which 413 also defines which are important notes and which might be ornaments. 414 415 These three aspects of `TimeSignatures` are controlled by the 416 :attr:`~music21.meter.TimeSignature.beatSequence`, 417 :attr:`~music21.meter.TimeSignature.beamSequence`, and 418 :attr:`~music21.meter.TimeSignature.accentSequence` properties of the 419 `TimeSignature`. Each of them is an independent 420 :class:`~music21.meter.MeterSequence` element which might have nested 421 properties (e.g., a 11/16 meter might be beamed as {1/4+1/4+{1/8+1/16}}), 422 so if you want to change how beats are calculated or beams are generated 423 you'll want to learn more about `meter.MeterSequence` objects. 424 425 There's a fourth `MeterSequence` object inside a TimeSignature, and that is 426 the :attr:`~music21.meter.TimeSignature.displaySequence`. That determines 427 how the `TimeSignature` should actually look on paper. Normally this 428 `MeterSequence` is pretty simple. In '4/4' it's usually just '4/4'. But 429 if you have the '11/16' time above, you may want to have it displayed as 430 '2/4+3/16' or '11/16 (2/4+3/16)'. Or you might want the written 431 TimeSignature to contradict what the notes imply. All this can be done 432 with .displaySequence. 433 ''' 434 _styleClass = style.TextStyle 435 classSortOrder = 4 436 437 _DOC_ATTR = { 438 'beatSequence': 'A :class:`~music21.meter.MeterSequence` governing beat partitioning.', 439 'beamSequence': 'A :class:`~music21.meter.MeterSequence` governing automatic beaming.', 440 'accentSequence': 'A :class:`~music21.meter.MeterSequence` governing accent partitioning.', 441 'displaySequence': ''' 442 A :class:`~music21.meter.MeterSequence` governing the display of the TimeSignature.''', 443 'symbol': ''' 444 A string representation of how to display the TimeSignature. 445 can be "common", "cut", "single-number" (i.e., 446 no denominator), or "normal" or "".''', 447 'symbolizeDenominator': ''' 448 If set to `True` (default is `False`) then the denominator 449 will be displayed as a symbol rather than 450 a number. Hindemith uses this in his scores. 451 Finale and other MusicXML readers do not support this 452 so do not expect proper output yet.''', 453 } 454 455 def __init__(self, value: str = '4/4', divisions=None): 456 super().__init__() 457 458 if value is None: 459 value = f'{defaults.meterNumerator}/{defaults.meterDenominatorBeatType}' 460 461 self._overriddenBarDuration = None 462 self.symbol = '' 463 self.displaySequence: Optional[MeterSequence] = None 464 self.beatSequence: Optional[MeterSequence] = None 465 self.accentSequence: Optional[MeterSequence] = None 466 self.beamSequence: Optional[MeterSequence] = None 467 self.symbolizeDenominator = False 468 469 self.resetValues(value, divisions) 470 471 def _reprInternal(self): 472 return self.ratioString 473 474 def resetValues(self, value: str = '4/4', divisions=None): 475 ''' 476 reset all values according to a new value and optionally, the number of 477 divisions. 478 ''' 479 self.symbol = '' # common, cut, single-number, normal 480 481 # a parameter to determine if the denominator is represented 482 # as either a symbol (a note) or as a number 483 self.symbolizeDenominator = False 484 485 self._overriddenBarDuration = None 486 487 # creates MeterSequence data representations 488 # creates .displaySequence, .beamSequence, .beatSequence, .accentSequence 489 self.load(value, divisions) 490 491 def load(self, value: str, divisions=None): 492 ''' 493 Load up a TimeSignature with a string value. 494 495 >>> ts = meter.TimeSignature() 496 >>> ts.load('4/4') 497 >>> ts 498 <music21.meter.TimeSignature 4/4> 499 500 >>> ts.load('c') 501 >>> ts.symbol 502 'common' 503 504 505 >>> ts.load('2/4+3/8') 506 >>> ts 507 <music21.meter.TimeSignature 2/4+3/8> 508 509 510 >>> ts.load('fast 6/8') 511 >>> ts.beatCount 512 2 513 >>> ts.load('slow 6/8') 514 >>> ts.beatCount 515 6 516 517 Loading destroys all preexisting internal representations 518 ''' 519 # create parallel MeterSequence objects to provide all data 520 # these all refer to the same .numerator/.denominator 521 # relationship 522 523 # used for drawing the time signature symbol 524 # this is the only one that can be unlinked 525 if value.lower() in ('common', 'c'): 526 value = '4/4' 527 self.symbol = 'common' 528 elif value.lower() in ('cut', 'allabreve'): 529 # allaBreve is the capella version 530 value = '2/2' 531 self.symbol = 'cut' 532 533 self.displaySequence = MeterSequence(value) 534 535 # get simple representation; presently, only slashToTuple 536 # supports the fast/slow indication 537 numerator, denominator, division = slashToTuple(value) 538 if division == MeterDivision.NONE: 539 if numerator % 3 == 0 and denominator >= 8: 540 division = MeterDivision.FAST 541 elif numerator == 3: 542 division = MeterDivision.SLOW 543 favorCompound = (division != MeterDivision.SLOW) 544 545 # used for beaming 546 self.beamSequence = MeterSequence(value, divisions) 547 # used for getting beat divisions 548 self.beatSequence = MeterSequence(value, divisions) 549 550 # accentSequence is used for setting one level of accents 551 self.accentSequence = MeterSequence(value, divisions) 552 553 if divisions is None: # set default beam partitions 554 # beam is not adjust by tempo indication 555 self._setDefaultBeamPartitions() 556 self._setDefaultBeatPartitions(favorCompound=favorCompound) 557 558 # for some summed meters default accent weights are difficult 559 # to obtain 560 try: 561 self._setDefaultAccentWeights(3) # set partitions based on beat 562 except MeterException: 563 environLocal.printDebug(['cannot set default accents for:', self]) 564 565 def loadRatio(self, numerator, denominator, divisions=None): 566 ''' 567 Change the numerator and denominator, like ratioString, but with 568 optional divisions and without resetting other parameters. 569 570 DEPRECATED in v7. -- call .ratioString or .load with 571 value = f'{numerator}/{denominator}' 572 ''' 573 value = f'{numerator}/{denominator}' 574 self.load(value, divisions) 575 576 @property 577 def ratioString(self): 578 ''' 579 Returns or sets a simple string representing the time signature ratio. 580 581 >>> threeFour = meter.TimeSignature('3/4') 582 >>> threeFour.ratioString 583 '3/4' 584 585 It can also be set to load a new one, but '.load()' is better... 586 587 >>> threeFour.ratioString = '5/8' # now this variable name is dumb! 588 >>> threeFour.numerator 589 5 590 >>> threeFour.denominator 591 8 592 593 >>> complexTime = meter.TimeSignature('2/4+3/8') 594 >>> complexTime.ratioString 595 '2/4+3/8' 596 597 For advanced users, getting the ratioString is the equivalent of 598 :attr:`~music21.meter.core.MeterSequence.partitionDisplay` on the displaySequence: 599 600 >>> complexTime.displaySequence.partitionDisplay 601 '2/4+3/8' 602 ''' 603 return self.displaySequence.partitionDisplay 604 605 @ratioString.setter 606 def ratioString(self, newRatioString): 607 self.resetValues(newRatioString) 608 609 def ratioEqual(self, other): 610 ''' 611 A basic form of comparison; does not determine if any internal structures are equal; o 612 only outermost ratio. 613 ''' 614 if other is None: 615 return False 616 if (other.numerator == self.numerator 617 and other.denominator == self.denominator): 618 return True 619 else: 620 return False 621 622 # -------------------------------------------------------------------------- 623 # properties 624 625 @property 626 def numerator(self): 627 ''' 628 Return the numerator of the TimeSignature as a number. 629 630 Can set the numerator for a simple TimeSignature. 631 To set the numerator of a complex TimeSignature, change beatCount. 632 633 (for complex TimeSignatures, note that this comes from the .beamSequence 634 of the TimeSignature) 635 636 637 >>> ts = meter.TimeSignature('3/4') 638 >>> ts.numerator 639 3 640 >>> ts.numerator = 5 641 >>> ts 642 <music21.meter.TimeSignature 5/4> 643 644 645 In this case, the TimeSignature is silently being converted to 9/8 646 to get a single digit numerator: 647 648 >>> ts = meter.TimeSignature('2/4+5/8') 649 >>> ts.numerator 650 9 651 652 Setting a summed time signature's numerator will change to a 653 simple time signature 654 655 >>> ts.numerator = 11 656 >>> ts 657 <music21.meter.TimeSignature 11/8> 658 ''' 659 return self.beamSequence.numerator 660 661 @numerator.setter 662 def numerator(self, value): 663 denominator = self.denominator 664 newRatioString = str(value) + '/' + str(denominator) 665 self.resetValues(newRatioString) 666 667 668 @property 669 def denominator(self): 670 ''' 671 Return the denominator of the TimeSignature as a number or set it. 672 673 (for complex TimeSignatures, note that this comes from the .beamSequence 674 of the TimeSignature) 675 676 >>> ts = meter.TimeSignature('3/4') 677 >>> ts.denominator 678 4 679 >>> ts.denominator = 8 680 >>> ts.ratioString 681 '3/8' 682 683 684 In this following case, the TimeSignature is silently being converted to 9/8 685 to get a single digit denominator: 686 687 >>> ts = meter.TimeSignature('2/4+5/8') 688 >>> ts.denominator 689 8 690 ''' 691 return self.beamSequence.denominator 692 693 @denominator.setter 694 def denominator(self, value): 695 numeratorValue = self.numerator 696 newRatioString = str(numeratorValue) + '/' + str(value) 697 self.resetValues(newRatioString) 698 699 @property 700 def barDuration(self) -> duration.Duration: 701 ''' 702 Return a :class:`~music21.duration.Duration` object equal to the 703 total length of this TimeSignature. 704 705 >>> ts = meter.TimeSignature('5/16') 706 >>> ts.barDuration 707 <music21.duration.Duration 1.25> 708 709 >>> ts2 = meter.TimeSignature('3/8') 710 >>> d = ts2.barDuration 711 >>> d.type 712 'quarter' 713 >>> d.dots 714 1 715 >>> d.quarterLength 716 1.5 717 718 This can be overridden to create different representations 719 or to contradict the meter. 720 721 >>> d2 = duration.Duration(1.75) 722 >>> ts2.barDuration = d2 723 >>> ts2.barDuration 724 <music21.duration.Duration 1.75> 725 ''' 726 727 if self._overriddenBarDuration: 728 return self._overriddenBarDuration 729 else: 730 # could come from self.beamSequence, self.accentSequence, 731 # self.displaySequence, self.accentSequence 732 return self.beamSequence.duration 733 734 @barDuration.setter 735 def barDuration(self, value: duration.Duration): 736 self._overriddenBarDuration = value 737 738 @property 739 def beatLengthToQuarterLengthRatio(self): 740 ''' 741 Returns 4.0 / denominator... seems a bit silly... 742 743 >>> a = meter.TimeSignature('3/2') 744 >>> a.beatLengthToQuarterLengthRatio 745 2.0 746 ''' 747 return 4 / self.denominator 748 749 @property 750 def quarterLengthToBeatLengthRatio(self): 751 ''' 752 Returns denominator/4.0... seems a bit silly... 753 ''' 754 return self.denominator / 4.0 755 756 # -------------------------------------------------------------------------- 757 # meter classifications used for classifying meters such as 758 # duple triple, etc. 759 760 @property 761 def beatCount(self): 762 ''' 763 Return or set the count of beat units, or the number of beats in this TimeSignature. 764 765 When setting beat units, one level of sub-partitions is automatically defined. 766 Users can specify beat count values as integers or as lists of durations. 767 For more precise configuration of the beat MeterSequence, 768 manipulate the .beatSequence attribute directly. 769 770 >>> ts = meter.TimeSignature('3/4') 771 >>> ts.beatCount 772 3 773 >>> ts.beatDuration.quarterLength 774 1.0 775 >>> ts.beatCount = [1, 1, 1, 1, 1, 1] 776 >>> ts.beatCount 777 6 778 >>> ts.beatDuration.quarterLength 779 0.5 780 781 Setting a beat-count directly is a simple, high-level way to configure the beatSequence. 782 Note that his may not configure lower level partitions correctly, 783 and will raise an error if the provided beat count is not supported by the 784 overall duration of the .beatSequence MeterSequence. 785 786 >>> ts = meter.TimeSignature('6/8') 787 >>> ts.beatCount # default is 2 beats 788 2 789 >>> ts.beatSequence 790 <music21.meter.core.MeterSequence {{1/8+1/8+1/8}+{1/8+1/8+1/8}}> 791 >>> ts.beatDivisionCountName 792 'Compound' 793 >>> ts.beatCount = 6 794 >>> ts.beatSequence 795 <music21.meter.core.MeterSequence 796 {{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}}> 797 >>> ts.beatDivisionCountName 798 'Simple' 799 >>> ts.beatCount = 123 800 Traceback (most recent call last): 801 music21.exceptions21.TimeSignatureException: cannot partition beat with provided value: 123 802 803 >>> ts = meter.TimeSignature('3/4') 804 >>> ts.beatCount = 6 805 >>> ts.beatDuration.quarterLength 806 0.5 807 ''' 808 # the default is for the beat to be defined by the first, not zero, 809 # level partition. 810 return len(self.beatSequence) 811 812 @beatCount.setter 813 def beatCount(self, value): 814 try: 815 self.beatSequence.partition(value) 816 except MeterException: 817 raise TimeSignatureException(f'cannot partition beat with provided value: {value}') 818 # create subdivisions using default parameters 819 if len(self.beatSequence) > 1: # if partitioned 820 self.beatSequence.subdividePartitionsEqual() 821 822 @property 823 def beatCountName(self): 824 ''' 825 Return the beat count name, or the name given for the number of beat units. 826 For example, 2/4 is duple; 9/4 is triple. 827 828 >>> ts = meter.TimeSignature('3/4') 829 >>> ts.beatCountName 830 'Triple' 831 832 >>> ts = meter.TimeSignature('6/8') 833 >>> ts.beatCountName 834 'Duple' 835 ''' 836 return self.beatSequence.partitionStr 837 838 @property 839 def beatDuration(self) -> duration.Duration: 840 ''' 841 Return a :class:`~music21.duration.Duration` object equal to the beat unit 842 of this Time Signature, if and only if this TimeSignature has a uniform beat unit. 843 844 Otherwise raises an exception in v7.1 but will change to returning NaN 845 soon fasterwards. 846 847 >>> ts = meter.TimeSignature('3/4') 848 >>> ts.beatDuration 849 <music21.duration.Duration 1.0> 850 >>> ts = meter.TimeSignature('6/8') 851 >>> ts.beatDuration 852 <music21.duration.Duration 1.5> 853 854 >>> ts = meter.TimeSignature('7/8') 855 >>> ts.beatDuration 856 <music21.duration.Duration 0.5> 857 858 >>> ts = meter.TimeSignature('3/8') 859 >>> ts.beatDuration 860 <music21.duration.Duration 1.5> 861 >>> ts.beatCount = 3 862 >>> ts.beatDuration 863 <music21.duration.Duration 0.5> 864 865 Cannot do this because of asymmetry 866 867 >>> ts = meter.TimeSignature('2/4+3/16') 868 >>> ts.beatDuration 869 Traceback (most recent call last): 870 music21.exceptions21.TimeSignatureException: non-uniform beat unit: [2.0, 0.75] 871 872 Changed in v7. -- return NaN rather than raising Exception in property. 873 ''' 874 post = [] 875 for ms in self.beatSequence: 876 post.append(ms.duration.quarterLength) 877 if len(set(post)) == 1: 878 return self.beatSequence[0].duration # all are the same 879 else: 880 raise TimeSignatureException(f'non-uniform beat unit: {post}') 881 882 @property 883 def beatDivisionCount(self): 884 ''' 885 Return the count of background beat units found within one beat, 886 or the number of subdivisions in the beat unit in this TimeSignature. 887 888 >>> ts = meter.TimeSignature('3/4') 889 >>> ts.beatDivisionCount 890 2 891 892 >>> ts = meter.TimeSignature('6/8') 893 >>> ts.beatDivisionCount 894 3 895 896 >>> ts = meter.TimeSignature('15/8') 897 >>> ts.beatDivisionCount 898 3 899 900 >>> ts = meter.TimeSignature('3/8') 901 >>> ts.beatDivisionCount 902 1 903 904 >>> ts = meter.TimeSignature('13/8', 13) 905 >>> ts.beatDivisionCount 906 1 907 908 Changed in v7. -- return 1 instead of a TimeSignatureException. 909 ''' 910 # first, find if there is more than one beat and if all beats are uniformly partitioned 911 post = [] 912 if len(self.beatSequence) == 1: 913 return 1 914 915 # need to see if first-level subdivisions are partitioned 916 if not isinstance(self.beatSequence[0], MeterSequence): 917 return 1 918 919 # getting length here gives number of subdivisions 920 for ms in self.beatSequence: 921 post.append(len(ms)) 922 923 # convert this to a set; if length is 1, then all beats are uniform 924 if len(set(post)) == 1: 925 return len(self.beatSequence[0]) # all are the same 926 else: 927 return 1 928 929 @property 930 def beatDivisionCountName(self): 931 ''' 932 Return the beat count name, or the name given for the number of beat units. 933 For example, 2/4 is duple; 9/4 is triple. 934 935 >>> ts = meter.TimeSignature('3/4') 936 >>> ts.beatDivisionCountName 937 'Simple' 938 939 >>> ts = meter.TimeSignature('6/8') 940 >>> ts.beatDivisionCountName 941 'Compound' 942 943 Rare cases of 5-beat divisions return 'Other', like this 10/8 divided into 944 5/8 + 5/8 with no further subdivisions: 945 946 >>> ts = meter.TimeSignature('10/8') 947 >>> ts.beatSequence.partition(2) 948 >>> ts.beatSequence 949 <music21.meter.core.MeterSequence {5/8+5/8}> 950 >>> for i, mt in enumerate(ts.beatSequence): 951 ... ts.beatSequence[i] = mt.subdivideByCount(5) 952 >>> ts.beatSequence 953 <music21.meter.core.MeterSequence {{1/8+1/8+1/8+1/8+1/8}+{1/8+1/8+1/8+1/8+1/8}}> 954 >>> ts.beatDivisionCountName 955 'Other' 956 ''' 957 beatDivision = self.beatDivisionCount 958 if beatDivision == 2: 959 return 'Simple' 960 elif beatDivision == 3: 961 return 'Compound' 962 else: 963 return 'Other' 964 965 @property 966 def beatDivisionDurations(self): 967 ''' 968 Return the beat division, or the durations that make up one beat, 969 as a list of :class:`~music21.duration.Duration` objects, if and only if 970 the TimeSignature has a uniform beat division for all beats. 971 972 >>> ts = meter.TimeSignature('3/4') 973 >>> ts.beatDivisionDurations 974 [<music21.duration.Duration 0.5>, 975 <music21.duration.Duration 0.5>] 976 977 >>> ts = meter.TimeSignature('6/8') 978 >>> ts.beatDivisionDurations 979 [<music21.duration.Duration 0.5>, 980 <music21.duration.Duration 0.5>, 981 <music21.duration.Duration 0.5>] 982 983 Value returned of non-uniform beat divisions will change at any time 984 after v7.1 to avoid raising an exception. 985 ''' 986 post = [] 987 if len(self.beatSequence) == 1: 988 raise TimeSignatureException( 989 'cannot determine beat division for a non-partitioned beat') 990 for mt in self.beatSequence: 991 for subMt in mt: 992 post.append(subMt.duration.quarterLength) 993 if len(set(post)) == 1: # all the same 994 out = [] 995 for subMt in self.beatSequence[0]: 996 out.append(subMt.duration) 997 return out 998 else: 999 raise TimeSignatureException(f'non uniform beat division: {post}') 1000 1001 @property 1002 def beatSubDivisionDurations(self): 1003 ''' 1004 Return a subdivision of the beat division, or a list 1005 of :class:`~music21.duration.Duration` objects representing each beat division 1006 divided by two. 1007 1008 >>> ts = meter.TimeSignature('3/4') 1009 >>> ts.beatSubDivisionDurations 1010 [<music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>, 1011 <music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>] 1012 1013 >>> ts = meter.TimeSignature('6/8') 1014 >>> ts.beatSubDivisionDurations 1015 [<music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>, 1016 <music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>, 1017 <music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>] 1018 ''' 1019 post = [] 1020 src = self.beatDivisionDurations 1021 for d in src: 1022 # this is too slow... TODO: fix, but make sure all durations are unique. 1023 post.append(d.augmentOrDiminish(0.5)) 1024 post.append(d.augmentOrDiminish(0.5)) 1025 return post 1026 1027 @property 1028 def classification(self): 1029 ''' 1030 Return the classification of this TimeSignature, 1031 such as Simple Triple or Compound Quadruple. 1032 1033 >>> ts = meter.TimeSignature('3/4') 1034 >>> ts.classification 1035 'Simple Triple' 1036 >>> ts = meter.TimeSignature('6/8') 1037 >>> ts.classification 1038 'Compound Duple' 1039 >>> ts = meter.TimeSignature('4/32') 1040 >>> ts.classification 1041 'Simple Quadruple' 1042 ''' 1043 return f'{self.beatDivisionCountName} {self.beatCountName}' 1044 1045 @property 1046 def summedNumerator(self) -> bool: 1047 if self.displaySequence is None: 1048 return False 1049 return self.displaySequence.summedNumerator 1050 1051 @summedNumerator.setter 1052 def summedNumerator(self, value: bool): 1053 if self.displaySequence is not None: 1054 self.displaySequence.summedNumerator = value 1055 1056 # -------------------------------------------------------------------------- 1057 # private methods -- most to be put into the various sequences. 1058 1059 def _setDefaultBeatPartitions(self, *, favorCompound=True): 1060 '''Set default beat partitions based on numerator and denominator. 1061 1062 >>> ts = meter.TimeSignature('3/4') 1063 >>> len(ts.beatSequence) # first, not zeroth, level stores beat 1064 3 1065 >>> ts = meter.TimeSignature('6/8') 1066 >>> len(ts.beatSequence) 1067 2 1068 >>> ts = meter.TimeSignature('slow 6/8') 1069 >>> len(ts.beatSequence) 1070 6 1071 1072 Changed in v7 -- favorCompound is keyword only 1073 ''' 1074 # if a non-compound meter has been given, as in 1075 # not 3+1/4; just 5/4 1076 if len(self.displaySequence) == 1: 1077 # create toplevel partitions 1078 if self.numerator == 2: # duple meters 1079 self.beatSequence.partition(2) 1080 elif self.numerator == 6 and favorCompound: # duple meters 1081 self.beatSequence.partition(2) 1082 elif self.numerator == 3 and favorCompound: # 3/8, 3/16, but not 3/4 1083 self.beatSequence.partition(1) 1084 elif self.numerator == 3: # triple meters 1085 self.beatSequence.partition([1, 1, 1]) 1086 elif self.numerator == 9 and favorCompound: # triple meters 1087 self.beatSequence.partition([3, 3, 3]) 1088 elif self.numerator == 4: # quadruple meters 1089 self.beatSequence.partition(4) 1090 elif self.numerator == 12 and favorCompound: 1091 self.beatSequence.partition(4) 1092 elif self.numerator >= 15 and self.numerator % 3 == 0 and favorCompound: 1093 # quintuple meters and above. 1094 num_triples = self.numerator // 3 1095 self.beatSequence.partition([3] * num_triples) 1096 # skip 6 numerators; covered above 1097 else: # case of odd meters: 11, 13 1098 self.beatSequence.partition(self.numerator) 1099 1100 # if a complex meter has been given 1101 else: # partition by display 1102 # TODO: remove partitionByMeterSequence usage. 1103 self.beatSequence.partition(self.displaySequence) 1104 1105 # create subdivisions, and thus define compound/simple distinction 1106 if len(self.beatSequence) > 1: # if partitioned 1107 try: 1108 self.beatSequence.subdividePartitionsEqual() 1109 except MeterException: 1110 if self.denominator >= 128: 1111 pass # do not raise an exception for unable to subdivide smaller than 128 1112 1113 def _setDefaultBeamPartitions(self): 1114 ''' 1115 This sets default beam partitions when partitionRequest is None. 1116 ''' 1117 # beam short measures of 8ths, 16ths, or 32nds all together 1118 if self.beamSequence.summedNumerator: 1119 pass # do not mess with a numerator such as (3+2)/8 1120 elif self.denominator == 8 and self.numerator in (1, 2, 3): 1121 pass # doing nothing will beam all together 1122 elif self.denominator == 16 and self.numerator in range(1, 6): 1123 # 1 - 5 -- beam all together 1124 pass 1125 elif self.denominator == 32 and self.numerator in range(1, 12): 1126 # 1 - 11 -- beam all together. 1127 pass 1128 1129 # more general, based only on numerator 1130 elif self.numerator in (2, 3, 4): 1131 self.beamSequence.partition(self.numerator) 1132 # if denominator is 4, subdivide each partition 1133 if self.denominator == 4: 1134 for i in range(len(self.beamSequence)): # subdivide each beat in 2 1135 self.beamSequence[i] = self.beamSequence[i].subdivide(2) 1136 elif self.numerator == 5: 1137 default = [2, 3] 1138 self.beamSequence.partition(default) 1139 # if denominator is 4, subdivide each partition 1140 if self.denominator == 4: 1141 for i in range(len(self.beamSequence)): # subdivide each beat in 2 1142 self.beamSequence[i] = self.beamSequence[i].subdivide(default[i]) 1143 1144 elif self.numerator == 7: 1145 self.beamSequence.partition(3) # divide into three groups 1146 1147 elif self.numerator in [6, 9, 12, 15, 18, 21]: 1148 self.beamSequence.partition([3] * int(self.numerator / 3)) 1149 else: 1150 pass # doing nothing will beam all together 1151 # environLocal.printDebug('default beam partitions set to: %s' % self.beamSequence) 1152 1153 def _setDefaultAccentWeights(self, depth=3): 1154 ''' 1155 This sets default accent weights based on common hierarchical notions for meters; 1156 each beat is given a weight, as defined by the top level count of self.beatSequence 1157 1158 >>> ts1 = meter.TimeSignature('4/4') 1159 >>> ts1._setDefaultAccentWeights(4) 1160 >>> [mt.weight for mt in ts1.accentSequence] 1161 [1.0, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625, 1162 0.5, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625] 1163 1164 >>> ts2 = meter.TimeSignature('3/4') 1165 >>> ts2._setDefaultAccentWeights(4) 1166 >>> [mt.weight for mt in ts2.accentSequence] 1167 [1.0, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625, 1168 0.5, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625, 1169 0.5, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625] 1170 1171 >>> ts2._setDefaultAccentWeights(3) # lower depth 1172 >>> [mt.weight for mt in ts2.accentSequence] 1173 [1.0, 0.125, 0.25, 0.125, 0.5, 0.125, 0.25, 0.125, 0.5, 0.125, 0.25, 0.125] 1174 1175 ''' 1176 # NOTE: this is a performance critical method 1177 1178 # create a scratch MeterSequence for structure 1179 tsStr = f'{self.numerator}/{self.denominator}' 1180 if self.beatSequence.isUniformPartition(): 1181 firstPartitionForm = len(self.beatSequence) 1182 cacheKey = (tsStr, firstPartitionForm, depth) 1183 else: # derive from meter sequence 1184 firstPartitionForm = self.beatSequence 1185 cacheKey = None # cannot cache based on beat form 1186 1187 # environLocal.printDebug(['_setDefaultAccentWeights(): firstPartitionForm set to', 1188 # firstPartitionForm, 'self.beatSequence: ', self.beatSequence, tsStr]) 1189 # using cacheKey speeds up TS creation from 2300 microseconds to 500microseconds 1190 try: 1191 self.accentSequence = copy.deepcopy( 1192 _meterSequenceAccentArchetypes[cacheKey] 1193 ) 1194 # environLocal.printDebug(['using stored accent archetype:']) 1195 except KeyError: 1196 # environLocal.printDebug(['creating a new accent archetype']) 1197 ms = MeterSequence(tsStr) 1198 # key operation here 1199 # div count needs to be the number of top-level beat divisions 1200 ms.subdivideNestedHierarchy(depth, 1201 firstPartitionForm=firstPartitionForm) 1202 1203 # provide a partition for each flat division 1204 accentCount = len(ms.flat) 1205 # environLocal.printDebug(['got accentCount', accentCount, 'ms: ', ms]) 1206 divStep = self.barDuration.quarterLength / accentCount 1207 weightInts = [0] * accentCount # weights as integer/depth counts 1208 for i in range(accentCount): 1209 ql = i * divStep 1210 weightInts[i] = ms.offsetToDepth(ql, align='quantize', index=i) 1211 1212 maxInt = max(weightInts) 1213 weightValues = {} # reference dictionary 1214 # minimum value, something like 1/16., to be multiplied by powers of 2 1215 weightValueMin = 1 / pow(2, maxInt - 1) 1216 for x in range(maxInt): 1217 # multiply base value (0.125) by 1, 2, 4 1218 # there is never a 0 integer weight, so add 1 to dictionary 1219 weightValues[x + 1] = weightValueMin * pow(2, x) 1220 1221 # set weights on accent partitions 1222 self.accentSequence.partition([1] * accentCount) 1223 for i in range(accentCount): 1224 # get values from weightValues dictionary 1225 self.accentSequence[i].weight = weightValues[weightInts[i]] 1226 1227 if cacheKey is not None: 1228 _meterSequenceAccentArchetypes[cacheKey] = copy.deepcopy(self.accentSequence) 1229 1230 # -------------------------------------------------------------------------- 1231 # access data for other processing 1232 def getBeams(self, srcList, measureStartOffset=0.0): 1233 ''' 1234 Given a qLen position and an iterable of Music21Objects, return a list of Beams objects. 1235 1236 The iterable can be a list (of elements) or a Stream (preferably flat) 1237 or a :class:`~music21.stream.iterator.StreamIterator` from which Durations 1238 and information about note vs. rest will be 1239 extracted. 1240 1241 Objects are assumed to be adjoining; offsets are not used, except for 1242 measureStartOffset() 1243 1244 Must process a list/Stream at time, because we cannot tell when a beam ends 1245 unless we see the context of adjoining durations. 1246 1247 1248 >>> a = meter.TimeSignature('2/4', 2) 1249 >>> a.beamSequence[0] = a.beamSequence[0].subdivide(2) 1250 >>> a.beamSequence[1] = a.beamSequence[1].subdivide(2) 1251 >>> a.beamSequence 1252 <music21.meter.core.MeterSequence {{1/8+1/8}+{1/8+1/8}}> 1253 >>> b = [note.Note(type='16th') for _ in range(8)] 1254 >>> c = a.getBeams(b) 1255 >>> len(c) == len(b) 1256 True 1257 >>> print(c) 1258 [<music21.beam.Beams <music21.beam.Beam 1/start>/<music21.beam.Beam 2/start>>, 1259 <music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/stop>>, 1260 <music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/start>>, 1261 <music21.beam.Beams <music21.beam.Beam 1/stop>/<music21.beam.Beam 2/stop>>, 1262 <music21.beam.Beams <music21.beam.Beam 1/start>/<music21.beam.Beam 2/start>>, 1263 <music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/stop>>, 1264 <music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/start>>, 1265 <music21.beam.Beams <music21.beam.Beam 1/stop>/<music21.beam.Beam 2/stop>>] 1266 1267 >>> a = meter.TimeSignature('6/8') 1268 >>> b = [note.Note(type='eighth') for _ in range(6)] 1269 >>> c = a.getBeams(b) 1270 >>> print(c) 1271 [<music21.beam.Beams <music21.beam.Beam 1/start>>, 1272 <music21.beam.Beams <music21.beam.Beam 1/continue>>, 1273 <music21.beam.Beams <music21.beam.Beam 1/stop>>, 1274 <music21.beam.Beams <music21.beam.Beam 1/start>>, 1275 <music21.beam.Beams <music21.beam.Beam 1/continue>>, 1276 <music21.beam.Beams <music21.beam.Beam 1/stop>>] 1277 1278 >>> fourFour = meter.TimeSignature('4/4') 1279 >>> nList = [note.Note(type=d) for d in ('eighth', 'quarter', 'eighth', 1280 ... 'eighth', 'quarter', 'eighth')] 1281 >>> beamList = fourFour.getBeams(nList) 1282 >>> print(beamList) 1283 [None, None, None, None, None, None] 1284 1285 Pickup measure support included by taking in an additional measureStartOffset argument. 1286 1287 >>> twoTwo = meter.TimeSignature('2/2') 1288 >>> nList = [note.Note(type='eighth') for _ in range(5)] 1289 >>> beamList = twoTwo.getBeams(nList, measureStartOffset=1.5) 1290 >>> print(beamList) 1291 [None, 1292 <music21.beam.Beams <music21.beam.Beam 1/start>>, 1293 <music21.beam.Beams <music21.beam.Beam 1/continue>>, 1294 <music21.beam.Beams <music21.beam.Beam 1/continue>>, 1295 <music21.beam.Beams <music21.beam.Beam 1/stop>>] 1296 1297 Fixed in v.7 -- incomplete final measures in 6/8: 1298 1299 >>> sixEight = meter.TimeSignature('6/8') 1300 >>> nList = [note.Note(type='quarter'), note.Note(type='eighth'), note.Note(type='eighth')] 1301 >>> beamList = sixEight.getBeams(nList) 1302 >>> print(beamList) 1303 [None, None, None] 1304 1305 And Measure objects with :attr:`~music21.stream.Measure.paddingRight` set: 1306 1307 >>> twoFour = meter.TimeSignature('2/4') 1308 >>> m = stream.Measure([note.Note(type='eighth') for _ in range(3)]) 1309 >>> m.paddingRight = 0.5 1310 >>> twoFour.getBeams(m) 1311 [<music21.beam.Beams <music21.beam.Beam 1/start>>, 1312 <music21.beam.Beams <music21.beam.Beam 1/stop>>, 1313 None] 1314 ''' 1315 from music21 import stream 1316 if isinstance(srcList, stream.Stream): 1317 srcStream = srcList 1318 srcList = list(srcList) # do not change to [srcList] 1319 elif srcList and isinstance(srcList[0], base.Music21Object): 1320 # make into a stream to get proper offsets: 1321 # for eventually removing measureStartOffset 1322 srcStream = stream.Measure() 1323 srcStream.append(srcList) 1324 else: 1325 return [] 1326 1327 if len(srcList) <= 1: 1328 return [None for _ in srcList] 1329 1330 beamsList = beam.Beams.naiveBeams(srcList) # hold maximum Beams objects, all with type None 1331 beamsList = beam.Beams.removeSandwichedUnbeamables(beamsList) 1332 1333 def fixBeamsOneElementDepth(i, el, depth): 1334 beams = beamsList[i] 1335 if beams is None: 1336 return 1337 1338 beamNumber = depth + 1 1339 # see if there is a component defined for this beam number 1340 # if not, continue 1341 if beamNumber not in beams.getNumbers(): 1342 return 1343 1344 dur = el.duration 1345 pos = el.offset + measureStartOffset 1346 1347 start = opFrac(pos) 1348 end = opFrac(pos + dur.quarterLength) 1349 startNext = end 1350 1351 isLast = (i == len(srcList) - 1) 1352 isFirst = (i == 0) 1353 1354 beamNext = beamsList[i + 1] if not isLast else None 1355 beamPrevious = beamsList[i - 1] if not isFirst else None 1356 1357 # get an archetype of the MeterSequence for this level 1358 # level is depth, starting at zero 1359 archetype = self.beamSequence.getLevel(depth) 1360 # span is the quarter note duration points for each partition 1361 # at this level 1362 archetypeSpanStart, archetypeSpanEnd = archetype.offsetToSpan(start) 1363 # environLocal.printDebug(['at level, got archetype span', depth, 1364 # archetypeSpan]) 1365 1366 if beamNext is None: # last note or before a non-beamable note (half, whole, etc.) 1367 archetypeSpanNextStart = 0.0 1368 else: 1369 archetypeSpanNextStart = archetype.offsetToSpan(startNext)[0] 1370 1371 # watch for a special case where a duration completely fills 1372 # the archetype; this generally should not be beamed 1373 # same if beamPrevious is None and beamNumber == 1 (quarter-eighth in 6/8) 1374 if end == archetypeSpanEnd and ( 1375 start == archetypeSpanStart 1376 or (beamPrevious is None and beamNumber == 1) 1377 ): 1378 # increment position and continue loop 1379 beamsList[i] = None # replace with None! 1380 return 1381 1382 # determine beamType 1383 # if first w/o pickup, always start 1384 if isFirst and measureStartOffset == 0: 1385 beamType = 'start' 1386 # get a partial beam if we cannot continue this 1387 if (beamNext is None 1388 or beamNumber not in beamNext.getNumbers()): 1389 beamType = 'partial-right' 1390 1391 # if last in complete measure or not in a measure, always stop 1392 elif isLast and (not srcStream.isMeasure or srcStream.paddingRight == 0.0): 1393 beamType = 'stop' 1394 # get a partial beam if we cannot form a beam 1395 if (beamPrevious is None 1396 or beamNumber not in beamPrevious.getNumbers()): 1397 # environLocal.warn(['triggering partial left where a stop normally falls']) 1398 beamType = 'partial-left' 1399 1400 # here on we know that it is neither the first nor last 1401 1402 # if last beam was not defined, we need to either 1403 # start or have a partial left beam 1404 # or, if beam number was not active in last beams 1405 elif beamPrevious is None or beamNumber not in beamPrevious.getNumbers(): 1406 if beamNumber == 1 and beamNext is None: 1407 beamsList[i] = None 1408 return 1409 elif beamNext is None and beamNumber > 1: 1410 beamType = 'partial-left' 1411 1412 elif startNext >= archetypeSpanEnd: 1413 # case of where we need a partial left: 1414 # if the next start value is outside of this span (or at the 1415 # the greater boundary of this span), and we did not have a 1416 # beam or beam number in the previous beam 1417 1418 # first note in pickup measures might also get 'partial-left' 1419 # here, but this gets fixed in sanitize partial beams 1420 1421 # may need to check: beamNext is not None and 1422 # beamNumber in beamNext.getNumbers() 1423 # note: it is critical that we check archetypeSpan here 1424 # not archetypeSpanNext 1425 # environLocal.printDebug(['matching partial left']) 1426 beamType = 'partial-left' 1427 elif beamNext is None or beamNumber not in beamNext.getNumbers(): 1428 beamType = 'partial-right' 1429 else: 1430 beamType = 'start' 1431 1432 # last beams was active, last beamNumber was active, 1433 # and it was stopped or was a partial-left 1434 elif (beamPrevious is not None 1435 and beamNumber in beamPrevious.getNumbers() 1436 and beamPrevious.getTypeByNumber(beamNumber) in ['stop', 'partial-left']): 1437 if beamNext is not None: 1438 beamType = 'start' if beamNumber in beamNext.getNumbers() else 'partial-right' 1439 1440 # last note had beams but stopped, next note cannot be beamed to 1441 # was active, last beamNumber was active, 1442 # and it was stopped or was a partial-left 1443 else: 1444 beamType = 'partial-left' # will be deleted later in the script 1445 1446 # if no beam is defined next (we know this already) 1447 # then must stop 1448 elif (beamNext is None 1449 or beamNumber not in beamNext.getNumbers()): 1450 beamType = 'stop' 1451 1452 # the last cases are when to stop, or when to continue 1453 # when we know we have a beam next 1454 1455 # we continue if the next beam is in the same beaming archetype 1456 # as this one. 1457 # if endNext is outside of the archetype span, 1458 # not sure what to do 1459 elif startNext < archetypeSpanEnd: 1460 # environLocal.printDebug(['continue match: dur.type, startNext, archetypeSpan', 1461 # dur.type, startNext, archetypeSpan]) 1462 beamType = 'continue' 1463 1464 # we stop if the next beam is not in the same beaming archetype 1465 # and (as shown above) a valid beam number is not previous 1466 elif startNext >= archetypeSpanNextStart: 1467 beamType = 'stop' 1468 1469 else: 1470 raise TimeSignatureException('cannot match beamType') 1471 1472 # debugging information displays: 1473 # if beamPrevious is not None: 1474 # environLocal.printDebug(['beamPrevious', beamPrevious, 1475 # 'beamPrevious.getNumbers()', beamPrevious.getNumbers(), 1476 # 'beamPrevious.getByNumber(beamNumber).type']) 1477 # 1478 # if beamNumber in beamPrevious.getNumbers(): 1479 # environLocal.printDebug(['beamPrevious type', 1480 # beamPrevious.getByNumber(beamNumber).type]) 1481 1482 # environLocal.printDebug(['beamNumber, start, archetypeSpan, beamType', 1483 # beamNumber, start, dur.type, archetypeSpan, beamType]) 1484 1485 beams.setByNumber(beamNumber, beamType) 1486 1487 # environLocal.printDebug(['beamsList', beamsList]) 1488 # iter over each beams line, from top to bottom (1 through 5) 1489 for outer_depth in range(len(beam.beamableDurationTypes)): 1490 # increment to count from 1 not 0 1491 # assume we are always starting at offset w/n this meter (Jose) 1492 for outer_i, outer_el in enumerate(srcList): 1493 fixBeamsOneElementDepth(outer_i, outer_el, outer_depth) 1494 1495 beamsList = beam.Beams.sanitizePartialBeams(beamsList) 1496 beamsList = beam.Beams.mergeConnectingPartialBeams(beamsList) 1497 1498 return beamsList 1499 1500 def setDisplay(self, value, partitionRequest=None): 1501 ''' 1502 Set an independent display value for a meter. 1503 1504 >>> a = meter.TimeSignature() 1505 >>> a.load('3/4') 1506 >>> a.setDisplay('2/8+2/8+2/8') 1507 >>> a.displaySequence 1508 <music21.meter.core.MeterSequence {2/8+2/8+2/8}> 1509 >>> a.beamSequence 1510 <music21.meter.core.MeterSequence {{1/8+1/8}+{1/8+1/8}+{1/8+1/8}}> 1511 >>> a.beatSequence # a single top-level partition is default for beat 1512 <music21.meter.core.MeterSequence {{1/8+1/8}+{1/8+1/8}+{1/8+1/8}}> 1513 >>> a.setDisplay('3/4') 1514 >>> a.displaySequence 1515 <music21.meter.core.MeterSequence {3/4}> 1516 ''' 1517 if isinstance(value, MeterSequence): # can set to an existing MeterSequence 1518 # must make a copy 1519 self.displaySequence = copy.deepcopy(value) 1520 else: 1521 # create a new object; it will not be linked 1522 self.displaySequence = MeterSequence(value, partitionRequest) 1523 1524 def getAccent(self, qLenPos: float) -> bool: 1525 ''' 1526 Return True or False if the qLenPos is at the start of an accent 1527 division. 1528 1529 >>> a = meter.TimeSignature('3/4', 3) 1530 >>> a.accentSequence.partition([2, 1]) 1531 >>> a.accentSequence 1532 <music21.meter.core.MeterSequence {2/4+1/4}> 1533 >>> a.getAccent(0.0) 1534 True 1535 >>> a.getAccent(1.0) 1536 False 1537 >>> a.getAccent(2.0) 1538 True 1539 ''' 1540 pos = 0 1541 qLenPos = opFrac(qLenPos) 1542 for i in range(len(self.accentSequence)): 1543 if pos == qLenPos: 1544 return True 1545 pos += self.accentSequence[i].duration.quarterLength 1546 return False 1547 1548 def setAccentWeight(self, weightList, level=0): 1549 '''Set accent weight, or floating point scalars, for the accent MeterSequence. 1550 Provide a list of values; if this list is shorter than the length of the MeterSequence, 1551 it will be looped; if this list is longer, only the first relevant value will be used. 1552 1553 If the accent MeterSequence is subdivided, the level of depth to set is given by the 1554 optional level argument. 1555 1556 1557 >>> a = meter.TimeSignature('4/4', 4) 1558 >>> len(a.accentSequence) 1559 4 1560 >>> a.setAccentWeight([0.8, 0.2]) 1561 >>> a.getAccentWeight(0) 1562 0.8... 1563 >>> a.getAccentWeight(0.5) 1564 0.8... 1565 >>> a.getAccentWeight(1) 1566 0.2... 1567 >>> a.getAccentWeight(2.5) 1568 0.8... 1569 >>> a.getAccentWeight(3.5) 1570 0.2... 1571 ''' 1572 if not common.isListLike(weightList): 1573 weightList = [weightList] 1574 1575 msLevel = self.accentSequence.getLevel(level) 1576 for i in range(len(msLevel)): 1577 msLevel[i].weight = weightList[i % len(weightList)] 1578 1579 def averageBeatStrength(self, streamIn, notesOnly=True): 1580 ''' 1581 returns a float of the average beat strength of all objects (or if notesOnly is True 1582 [default] only the notes) in the `Stream` specified as streamIn. 1583 1584 1585 >>> s = converter.parse('C4 D4 E8 F8', format='tinyNotation').flatten().notes.stream() 1586 >>> sixEight = meter.TimeSignature('6/8') 1587 >>> sixEight.averageBeatStrength(s) 1588 0.4375 1589 >>> threeFour = meter.TimeSignature('3/4') 1590 >>> threeFour.averageBeatStrength(s) 1591 0.5625 1592 1593 If `notesOnly` is `False` then test objects will give added 1594 weight to the beginning of the measure: 1595 1596 >>> sixEight.averageBeatStrength(s, notesOnly=False) 1597 0.4375 1598 >>> s.insert(0.0, clef.TrebleClef()) 1599 >>> s.insert(0.0, clef.BassClef()) 1600 >>> sixEight.averageBeatStrength(s, notesOnly=False) 1601 0.625 1602 ''' 1603 if notesOnly is True: 1604 streamIn = streamIn.notes 1605 1606 totalWeight = 0.0 1607 totalObjects = len(streamIn) 1608 if totalObjects == 0: 1609 return 0.0 # or raise exception? add doc test 1610 for el in streamIn: 1611 elWeight = self.getAccentWeight( 1612 self.getMeasureOffsetOrMeterModulusOffset(el), 1613 forcePositionMatch=True, permitMeterModulus=False) 1614 totalWeight += elWeight 1615 return totalWeight / totalObjects 1616 1617 def getMeasureOffsetOrMeterModulusOffset(self, el): 1618 ''' 1619 Return the measure offset based on a Measure, if it exists, 1620 otherwise based on meter modulus of the TimeSignature. 1621 1622 >>> m = stream.Measure() 1623 >>> ts1 = meter.TimeSignature('3/4') 1624 >>> m.insert(0, ts1) 1625 >>> n1 = note.Note() 1626 >>> m.insert(2, n1) 1627 >>> ts1.getMeasureOffsetOrMeterModulusOffset(n1) 1628 2.0 1629 1630 Exceeding the range of the Measure gets a modulus 1631 1632 >>> n2 = note.Note() 1633 >>> m.insert(4.0, n2) 1634 >>> ts1.getMeasureOffsetOrMeterModulusOffset(n2) 1635 1.0 1636 1637 Can be applied to Notes in a Stream with a TimeSignature. 1638 1639 >>> ts2 = meter.TimeSignature('5/4') 1640 >>> s2 = stream.Stream() 1641 >>> s2.insert(0, ts2) 1642 >>> n3 = note.Note() 1643 >>> s2.insert(3, n3) 1644 >>> ts2.getMeasureOffsetOrMeterModulusOffset(n3) 1645 3.0 1646 1647 >>> n4 = note.Note() 1648 >>> s2.insert(5, n4) 1649 >>> ts2.getMeasureOffsetOrMeterModulusOffset(n4) 1650 0.0 1651 ''' 1652 mOffset = el._getMeasureOffset() # TODO(msc): expose this method and remove private 1653 tsMeasureOffset = self._getMeasureOffset(includeMeasurePadding=False) 1654 if (mOffset + tsMeasureOffset) < self.barDuration.quarterLength: 1655 return mOffset 1656 else: 1657 # must get offset relative to not just start of Stream, but the last 1658 # time signature 1659 post = ((mOffset - tsMeasureOffset) % self.barDuration.quarterLength) 1660 # environLocal.printDebug(['result', post]) 1661 return post 1662 1663 def getAccentWeight(self, qLenPos, level=0, forcePositionMatch=False, 1664 permitMeterModulus=False): 1665 '''Given a qLenPos, return an accent level. In general, accents are assumed to 1666 define only a first-level weight. 1667 1668 If `forcePositionMatch` is True, an accent will only be returned if the 1669 provided qLenPos is a near exact match to the provided quarter length. Otherwise, 1670 half of the minimum quarter length will be provided. 1671 1672 If `permitMeterModulus` is True, quarter length positions greater than 1673 the duration of the Meter will be accepted as the modulus of the total meter duration. 1674 1675 1676 >>> ts1 = meter.TimeSignature('3/4') 1677 >>> [ts1.getAccentWeight(x) for x in range(3)] 1678 [1.0, 0.5, 0.5] 1679 1680 1681 Returns an error... 1682 1683 >>> [ts1.getAccentWeight(x) for x in range(6)] 1684 Traceback (most recent call last): 1685 music21.exceptions21.MeterException: cannot access from qLenPos 3.0 1686 where total duration is 3.0 1687 1688 ...unless permitMeterModulus is employed 1689 1690 >>> [ts1.getAccentWeight(x, permitMeterModulus=True) for x in range(6)] 1691 [1.0, 0.5, 0.5, 1.0, 0.5, 0.5] 1692 1693 ''' 1694 qLenPos = opFrac(qLenPos) 1695 # might store this weight every time it is set, rather than 1696 # getting it here 1697 minWeight = min( 1698 [mt.weight for mt in self.accentSequence]) * 0.5 1699 msLevel = self.accentSequence.getLevel(level) 1700 1701 if permitMeterModulus: 1702 environLocal.printDebug( 1703 [' self.duration.quarterLength', self.duration.quarterLength, 1704 'self.barDuration.quarterLength', self.barDuration.quarterLength]) 1705 qLenPos = qLenPos % self.barDuration.quarterLength 1706 1707 if forcePositionMatch: 1708 # only return values for qLen positions that are at the start 1709 # of a span; for those that are not, we need to return a minWeight 1710 localSpan = msLevel.offsetToSpan(qLenPos, 1711 permitMeterModulus=permitMeterModulus) 1712 1713 if qLenPos != localSpan[0]: 1714 return minWeight 1715 return msLevel[msLevel.offsetToIndex(qLenPos)].weight 1716 1717 def getBeat(self, offset): 1718 ''' 1719 Given an offset (quarterLength position), get the beat, where beats count from 1 1720 1721 If you want a fractional number for the beat, see `getBeatProportion`. 1722 1723 TODO: In v7 -- getBeat will probably do what getBeatProportion does now... 1724 but just with 1 added to it. 1725 1726 >>> a = meter.TimeSignature('3/4', 3) 1727 >>> a.getBeat(0) 1728 1 1729 >>> a.getBeat(2.5) 1730 3 1731 >>> a.beatSequence.partition(['3/8', '3/8']) 1732 >>> a.getBeat(2.5) 1733 2 1734 ''' 1735 return self.beatSequence.offsetToIndex(offset) + 1 1736 1737 def getBeatOffsets(self): 1738 '''Return offset positions in a list for the start of each beat, 1739 assuming this object is found at offset zero. 1740 1741 >>> a = meter.TimeSignature('3/4') 1742 >>> a.getBeatOffsets() 1743 [0.0, 1.0, 2.0] 1744 >>> a = meter.TimeSignature('6/8') 1745 >>> a.getBeatOffsets() 1746 [0.0, 1.5] 1747 ''' 1748 post = [] 1749 post.append(0.0) 1750 if len(self.beatSequence) == 1: 1751 return post 1752 else: 1753 endOffset = self.barDuration.quarterLength 1754 o = 0.0 1755 for ms in self.beatSequence: 1756 o = opFrac(o + ms.duration.quarterLength) 1757 if o >= endOffset: 1758 return post # do not add offset for end of bar 1759 post.append(o) 1760 1761 def getBeatDuration(self, qLenPos): 1762 ''' 1763 Returns a :class:`~music21.duration.Duration` 1764 object representing the length of the beat 1765 found at qLenPos. For most standard 1766 meters, you can give qLenPos = 0 1767 and get the length of any beat in 1768 the TimeSignature; but the simpler 1769 :attr:`music21.meter.TimeSignature.beatDuration` parameter, 1770 will do that for you just as well. 1771 1772 The advantage of this method is that 1773 it will work for asymmetrical meters, as the second 1774 example shows. 1775 1776 1777 Ex. 1: beat duration for 3/4 is always 1.0 1778 no matter where in the meter you query. 1779 1780 1781 >>> ts1 = meter.TimeSignature('3/4') 1782 >>> ts1.getBeatDuration(0.5) 1783 <music21.duration.Duration 1.0> 1784 >>> ts1.getBeatDuration(2.5) 1785 <music21.duration.Duration 1.0> 1786 1787 1788 Ex. 2: same for 6/8: 1789 1790 1791 >>> ts2 = meter.TimeSignature('6/8') 1792 >>> ts2.getBeatDuration(2.5) 1793 <music21.duration.Duration 1.5> 1794 1795 1796 Ex. 3: but for a compound meter of 3/8 + 2/8, 1797 where you ask for the beat duration 1798 will determine the length of the beat: 1799 1800 1801 >>> ts3 = meter.TimeSignature('3/8+2/8') # will partition as 2 beat 1802 >>> ts3.getBeatDuration(0.5) 1803 <music21.duration.Duration 1.5> 1804 >>> ts3.getBeatDuration(1.5) 1805 <music21.duration.Duration 1.0> 1806 ''' 1807 return self.beatSequence[self.beatSequence.offsetToIndex(qLenPos)].duration 1808 1809 def getOffsetFromBeat(self, beat): 1810 ''' 1811 Given a beat value, convert into an offset position. 1812 1813 1814 >>> ts1 = meter.TimeSignature('3/4') 1815 >>> ts1.getOffsetFromBeat(1) 1816 0.0 1817 >>> ts1.getOffsetFromBeat(2) 1818 1.0 1819 >>> ts1.getOffsetFromBeat(3) 1820 2.0 1821 >>> ts1.getOffsetFromBeat(3.5) 1822 2.5 1823 >>> ts1.getOffsetFromBeat(3.25) 1824 2.25 1825 1826 >>> from fractions import Fraction 1827 >>> ts1.getOffsetFromBeat(Fraction(8, 3)) # 2.66666 1828 Fraction(5, 3) 1829 1830 1831 >>> ts1 = meter.TimeSignature('6/8') 1832 >>> ts1.getOffsetFromBeat(1) 1833 0.0 1834 >>> ts1.getOffsetFromBeat(2) 1835 1.5 1836 >>> ts1.getOffsetFromBeat(2.33) 1837 2.0 1838 >>> ts1.getOffsetFromBeat(2.5) # will be + 0.5 * 1.5 1839 2.25 1840 >>> ts1.getOffsetFromBeat(2.66) 1841 2.5 1842 1843 1844 Works for asymmetrical meters as well: 1845 1846 1847 >>> ts3 = meter.TimeSignature('3/8+2/8') # will partition as 2 beat 1848 >>> ts3.getOffsetFromBeat(1) 1849 0.0 1850 >>> ts3.getOffsetFromBeat(2) 1851 1.5 1852 >>> ts3.getOffsetFromBeat(1.66) 1853 1.0 1854 >>> ts3.getOffsetFromBeat(2.5) 1855 2.0 1856 1857 1858 Let's try this on a real piece, a 4/4 chorale with a one beat pickup. Here we get the 1859 normal offset from the active TimeSignature but we subtract out the pickup length which 1860 is in a `Measure`'s :attr:`~music21.stream.Measure.paddingLeft` property. 1861 1862 >>> c = corpus.parse('bwv1.6') 1863 >>> for m in c.parts.first().getElementsByClass('Measure'): 1864 ... ts = m.timeSignature or m.getContextByClass('TimeSignature') 1865 ... print('%s %s' % (m.number, ts.getOffsetFromBeat(4.5) - m.paddingLeft)) 1866 0 0.5 1867 1 3.5 1868 2 3.5 1869 ... 1870 ''' 1871 # divide into integer and floating point components 1872 beatInt, beatFraction = divmod(beat, 1) 1873 beatInt = int(beatInt) # convert to integer 1874 1875 # resolve 0.33 to 0.3333333 (actually Fraction(1, 3). ) 1876 beatFraction = common.addFloatPrecision(beatFraction) 1877 1878 if beatInt - 1 > len(self.beatSequence) - 1: 1879 raise TimeSignatureException( 1880 'requested beat value (%s) not found in beat partitions (%s) of ts %s' % ( 1881 beatInt, self.beatSequence, self)) 1882 # get a duration object for the beat; will translate into quarterLength 1883 # beat int counts from 1; subtract 1 to get index 1884 beatDur = self.beatSequence[beatInt - 1].duration 1885 oStart, unused_oEnd = self.beatSequence.getLevelSpan()[beatInt - 1] 1886 post = opFrac(oStart + (beatDur.quarterLength * beatFraction)) 1887 return post 1888 1889 def getBeatProgress(self, qLenPos): 1890 ''' 1891 Given a quarterLength position, get the beat, 1892 where beats count from 1, and return the the 1893 amount of qLen into this beat the supplied qLenPos 1894 is. 1895 1896 >>> a = meter.TimeSignature('3/4', 3) 1897 >>> a.getBeatProgress(0) 1898 (1, 0) 1899 >>> a.getBeatProgress(0.75) 1900 (1, 0.75) 1901 >>> a.getBeatProgress(1.0) 1902 (2, 0.0) 1903 >>> a.getBeatProgress(2.5) 1904 (3, 0.5) 1905 1906 1907 Works for specifically partitioned meters too: 1908 1909 >>> a.beatSequence.partition(['3/8', '3/8']) 1910 >>> a.getBeatProgress(2.5) 1911 (2, 1.0) 1912 ''' 1913 beatIndex = self.beatSequence.offsetToIndex(qLenPos) 1914 start, unused_end = self.beatSequence.offsetToSpan(qLenPos) 1915 return beatIndex + 1, qLenPos - start 1916 1917 def getBeatProportion(self, qLenPos): 1918 ''' 1919 Given a quarter length position into the meter, return a numerical progress 1920 through the beat (where beats count from one) with a floating-point or fractional value 1921 between 0 and 1 appended to this value that gives the proportional progress into the beat. 1922 1923 For faster, integer values, use simply `.getBeat()` 1924 1925 >>> ts1 = meter.TimeSignature('3/4') 1926 >>> ts1.getBeatProportion(0.0) 1927 1.0 1928 >>> ts1.getBeatProportion(0.5) 1929 1.5 1930 >>> ts1.getBeatProportion(1.0) 1931 2.0 1932 1933 >>> ts3 = meter.TimeSignature('3/8+2/8') # will partition as 2 beat 1934 >>> ts3.getBeatProportion(0.75) 1935 1.5 1936 >>> ts3.getBeatProportion(2.0) 1937 2.5 1938 ''' 1939 beatIndex = self.beatSequence.offsetToIndex(qLenPos) 1940 start, end = self.beatSequence.offsetToSpan(qLenPos) 1941 totalRange = end - start 1942 progress = qLenPos - start # how far in QL 1943 return opFrac(beatIndex + 1 + (progress / totalRange)) 1944 1945 def getBeatProportionStr(self, qLenPos): 1946 '''Return a string presentation of the beat. 1947 1948 >>> ts1 = meter.TimeSignature('3/4') 1949 >>> ts1.getBeatProportionStr(0.0) 1950 '1' 1951 >>> ts1.getBeatProportionStr(0.5) 1952 '1 1/2' 1953 >>> ts1.getBeatProportionStr(1.0) 1954 '2' 1955 >>> ts3 = meter.TimeSignature('3/8+2/8') # will partition as 2 beat 1956 >>> ts3.getBeatProportionStr(0.75) 1957 '1 1/2' 1958 >>> ts3.getBeatProportionStr(2) 1959 '2 1/2' 1960 1961 >>> ts4 = meter.TimeSignature('6/8') # will partition as 2 beat 1962 ''' 1963 beatIndex = int(self.beatSequence.offsetToIndex(qLenPos)) 1964 start, end = self.beatSequence.offsetToSpan(qLenPos) 1965 totalRange = end - start 1966 progress = qLenPos - start # how far in QL 1967 1968 if (progress / totalRange) == 0.0: 1969 post = f'{beatIndex + 1}' # just show beat 1970 else: 1971 a, b = proportionToFraction(progress / totalRange) 1972 post = f'{beatIndex + 1} {a}/{b}' # just show beat 1973 return post 1974 1975 def getBeatDepth(self, qLenPos, align='quantize'): 1976 '''Return the number of levels of beat partitioning given a QL into the TimeSignature. 1977 Note that by default beat partitioning always has a single, top-level partition. 1978 1979 The `align` parameter is passed to the :meth:`~music21.meter.MeterSequence.offsetToDepth` 1980 method, and can be used to find depths based on start position overlaps. 1981 1982 >>> a = meter.TimeSignature('3/4', 3) 1983 >>> a.getBeatDepth(0) 1984 1 1985 >>> a.getBeatDepth(1) 1986 1 1987 >>> a.getBeatDepth(2) 1988 1 1989 1990 >>> b = meter.TimeSignature('3/4', 1) 1991 >>> b.beatSequence[0] = b.beatSequence[0].subdivide(3) 1992 >>> b.beatSequence[0][0] = b.beatSequence[0][0].subdivide(2) 1993 >>> b.beatSequence[0][1] = b.beatSequence[0][1].subdivide(2) 1994 >>> b.beatSequence[0][2] = b.beatSequence[0][2].subdivide(2) 1995 >>> b.getBeatDepth(0) 1996 3 1997 >>> b.getBeatDepth(0.5) 1998 1 1999 >>> b.getBeatDepth(1) 2000 2 2001 ''' 2002 return self.beatSequence.offsetToDepth(qLenPos, align) 2003 2004 2005# ----------------------------------------------------------------------------- 2006class SenzaMisuraTimeSignature(base.Music21Object): 2007 ''' 2008 A SenzaMisuraTimeSignature represents the absence of a TimeSignature 2009 2010 It is NOT a TimeSignature subclass, only because it has none of the attributes 2011 of a TimeSignature. 2012 2013 >>> smts = meter.SenzaMisuraTimeSignature('0') 2014 >>> smts.text 2015 '0' 2016 >>> smts 2017 <music21.meter.SenzaMisuraTimeSignature 0> 2018 ''' 2019 2020 def __init__(self, text=None): 2021 super().__init__() 2022 self.text = text 2023 2024 def _reprInternal(self): 2025 if self.text is None: 2026 return '' 2027 else: 2028 return str(self.text) 2029 2030 2031# TODO: Implement or delete... 2032 2033# class NonPowerOfTwoTimeSignature(TimeSignature): 2034# pass 2035# class AutoAdjustTimeSignature(TimeSignature): 2036# automatically adjusts to fit its measure context. 2037 2038 2039# ----------------------------------------------------------------------------- 2040# define presented order in documentation 2041_DOC_ORDER = [TimeSignature] 2042 2043 2044if __name__ == '__main__': 2045 import music21 2046 music21.mainTest() 2047