1# -*- coding: utf-8 -*- 2# ------------------------------------------------------------------------------ 3# Name: instrument.py 4# Purpose: Class for basic instrument information 5# 6# Authors: Neena Parikh 7# Christopher Ariza 8# Michael Scott Cuthbert 9# Jose Cabal-Ugaz 10# Ben Houge 11# Mark Gotham 12# 13# Copyright: Copyright © 2009-2012, 17, 20 Michael Scott Cuthbert and the music21 Project 14# License: BSD, see license.txt 15# ------------------------------------------------------------------------------ 16''' 17This module represents instruments through objects that contain general information 18such as Metadata for instrument names, classifications, transpositions and default 19MIDI program numbers. It also contains information specific to each instrument 20or instrument family, such as string pitches, etc. Information about instrumental 21ensembles is also included here though it may later be separated out into its own 22ensemble.py module. 23''' 24import copy 25import unittest 26import sys 27from collections import OrderedDict 28from typing import Optional 29 30from music21 import base 31from music21 import common 32from music21 import interval 33from music21 import note 34from music21 import pitch 35from music21 import stream 36from music21.tree.trees import OffsetTree 37 38from music21.exceptions21 import InstrumentException 39 40from music21 import environment 41_MOD = 'instrument' 42environLocal = environment.Environment(_MOD) 43StreamType = stream.StreamType 44 45 46def unbundleInstruments(streamIn: StreamType, *, inPlace=False) -> Optional[StreamType]: 47 # noinspection PyShadowingNames 48 ''' 49 takes a :class:`~music21.stream.Stream` that has :class:`~music21.note.NotRest` objects 50 and moves their `.storedInstrument` attributes to a new Stream (unless inPlace=True) 51 52 >>> up1 = note.Unpitched() 53 >>> up1.storedInstrument = instrument.BassDrum() 54 >>> up2 = note.Unpitched() 55 >>> up2.storedInstrument = instrument.Cowbell() 56 >>> s = stream.Stream() 57 >>> s.append(up1) 58 >>> s.append(up2) 59 >>> s2 = instrument.unbundleInstruments(s) 60 >>> s2.show('text') 61 {0.0} <music21.instrument.BassDrum 'Bass Drum'> 62 {0.0} <music21.note.Unpitched object at 0x...> 63 {1.0} <music21.instrument.Cowbell 'Cowbell'> 64 {1.0} <music21.note.Unpitched object at 0x...> 65 ''' 66 if inPlace is True: 67 s = streamIn 68 else: 69 s = streamIn.coreCopyAsDerivation('unbundleInstruments') 70 71 for thisObj in s: 72 if isinstance(thisObj, note.NotRest): 73 # eventually also unbundle each note of chord, but need new voices 74 i = thisObj.storedInstrument 75 if i is not None: 76 off = thisObj.offset 77 s.insert(off, i) 78 79 if inPlace is False: 80 return s 81 82 83def bundleInstruments(streamIn: stream.Stream, *, inPlace=False) -> Optional[stream.Stream]: 84 # noinspection PyShadowingNames 85 ''' 86 >>> up1 = note.Unpitched() 87 >>> up1.storedInstrument = instrument.BassDrum() 88 >>> upUnknownInstrument = note.Unpitched() 89 90 >>> up2 = note.Unpitched() 91 >>> up2.storedInstrument = instrument.Cowbell() 92 >>> s = stream.Stream() 93 >>> s.append(up1) 94 >>> s.append(upUnknownInstrument) 95 >>> s.append(up2) 96 >>> s2 = instrument.unbundleInstruments(s) 97 >>> s3 = instrument.bundleInstruments(s2) 98 >>> for test in s3: 99 ... print(test.storedInstrument) 100 Bass Drum 101 Bass Drum 102 Cowbell 103 104 ''' 105 if inPlace is True: 106 s = streamIn 107 else: 108 s = streamIn.coreCopyAsDerivation('bundleInstruments') 109 110 lastInstrument = None 111 112 for thisObj in s: 113 if 'Instrument' in thisObj.classes: 114 lastInstrument = thisObj 115 s.remove(thisObj) 116 elif isinstance(thisObj, note.NotRest): 117 thisObj.storedInstrument = lastInstrument 118 119 if inPlace is False: 120 return s 121 122 123class Instrument(base.Music21Object): 124 ''' 125 Base class for all musical instruments. Designed 126 for subclassing, though usually a more specific 127 instrument class (such as StringInstrument) would 128 be better to subclass. 129 130 Some defined attributes for instruments include: 131 132 * partId 133 * partName 134 * partAbbreviation 135 * instrumentId 136 * instrumentName 137 * instrumentAbbreviation 138 * midiProgram (0-indexed) 139 * midiChannel (0-indexed) 140 * lowestNote (a note object or a string for _written_ pitch) 141 * highestNote (a note object or a string for _written_ pitch) 142 * transposition (an interval object) 143 * inGMPercMap (bool -- if it uses the GM percussion map) 144 * soundfontFn (filepath to a sound font, optional) 145 ''' 146 classSortOrder = -25 147 148 def __init__(self, instrumentName=None): 149 super().__init__() 150 151 self.partId = None 152 self._partIdIsRandom = False 153 154 self.partName = None 155 self.partAbbreviation = None 156 157 self.printPartName = None # True = yes, False = no, None = let others decide 158 self.printPartAbbreviation = None 159 160 self.instrumentId: Optional[str] = None # apply to midi and instrument 161 self._instrumentIdIsRandom = False 162 163 self.instrumentName = instrumentName 164 self.instrumentAbbreviation = None 165 self.midiProgram = None # 0-indexed 166 self.midiChannel = None # 0-indexed 167 self.instrumentSound = None 168 169 self.lowestNote = None 170 self.highestNote = None 171 172 # define interval to go from written to sounding 173 self.transposition: Optional[interval.Interval] = None 174 175 self.inGMPercMap = False 176 self.soundfontFn = None # if defined... 177 178 def __str__(self): 179 msg = [] 180 if self.partId is not None: 181 msg.append(f'{self.partId}: ') 182 if self.partName is not None: 183 msg.append(f'{self.partName}: ') 184 if self.instrumentName is not None: 185 msg.append(self.instrumentName) 186 return ''.join(msg) 187 188 def _reprInternal(self): 189 return repr(str(self)) 190 191 def __deepcopy__(self, memo=None): 192 new = common.defaultDeepcopy(self, memo) 193 if self._partIdIsRandom: 194 new.partIdRandomize() 195 if self._instrumentIdIsRandom: 196 new.instrumentIdRandomize() 197 return new 198 199 def bestName(self): 200 ''' 201 Find a viable name, looking first at instrument, then part, then 202 abbreviations. 203 ''' 204 if self.partName is not None: 205 return self.partName 206 elif self.partAbbreviation is not None: 207 return self.partAbbreviation 208 elif self.instrumentName is not None: 209 return self.instrumentName 210 elif self.instrumentAbbreviation is not None: 211 return self.instrumentAbbreviation 212 else: 213 return None 214 215 def partIdRandomize(self): 216 ''' 217 Force a unique id by using an MD5 218 ''' 219 idNew = f'P{common.getMd5()}' 220 # environLocal.printDebug(['incrementing instrument from', 221 # self.partId, 'to', idNew]) 222 self.partId = idNew 223 self._partIdIsRandom = True 224 225 def instrumentIdRandomize(self): 226 ''' 227 Force a unique id by using an MD5 228 ''' 229 idNew = f'I{common.getMd5()}' 230 # environLocal.printDebug(['incrementing instrument from', 231 # self.partId, 'to', idNew]) 232 self.instrumentId = idNew 233 self._instrumentIdIsRandom = True 234 235 # the empty list as default is actually CORRECT! 236 # noinspection PyDefaultArgument 237 238 def autoAssignMidiChannel(self, usedChannels=[]): # pylint: disable=dangerous-default-value 239 ''' 240 Assign an unused midi channel given a list of 241 used channels. 242 243 assigns the number to self.midiChannel and returns 244 it as an int. 245 246 Note that midi channel 10 (9 in music21) is special, and 247 thus is skipped. 248 249 Currently only 16 channels are used. 250 251 Note that the reused "usedChannels=[]" in the 252 signature is NOT a mistake, but necessary for 253 the case where there needs to be a global list. 254 255 >>> used = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11] 256 >>> i = instrument.Violin() 257 >>> i.autoAssignMidiChannel(used) 258 12 259 >>> i.midiChannel 260 12 261 262 Unpitched percussion will be set to 9, so long as it's not in the filter list: 263 264 >>> used = [0] 265 >>> i = instrument.Maracas() 266 >>> i.autoAssignMidiChannel(used) 267 9 268 >>> i.midiChannel 269 9 270 271 >>> used = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 272 >>> i = instrument.Woodblock() 273 >>> i.autoAssignMidiChannel(used) 274 11 275 >>> i.midiChannel 276 11 277 278 OMIT_FROM_DOCS 279 280 >>> used2 = range(16) 281 >>> i = instrument.Instrument() 282 >>> i.autoAssignMidiChannel(used2) 283 Traceback (most recent call last): 284 music21.exceptions21.InstrumentException: we are out of midi channels! help! 285 ''' 286 # NOTE: this is used in musicxml output, not in midi output 287 maxMidi = 16 288 channelFilter = [] 289 for e in usedChannels: 290 if e is not None: 291 channelFilter.append(e) 292 293 if not channelFilter: 294 self.midiChannel = 0 295 return self.midiChannel 296 elif len(channelFilter) >= maxMidi: 297 raise InstrumentException('we are out of midi channels! help!') 298 elif 'UnpitchedPercussion' in self.classes and 9 not in usedChannels: 299 self.midiChannel = 9 300 return self.midiChannel 301 else: 302 for ch in range(maxMidi): 303 if ch in channelFilter: 304 continue 305 elif ch % 16 == 9: 306 continue # skip 10 / percussion for now 307 else: 308 self.midiChannel = ch 309 return self.midiChannel 310 return 0 311 # raise InstrumentException('we are out of midi channels and this ' + 312 # 'was not already detected PROGRAM BUG!') 313 314 315# ------------------------------------------------------------------------------ 316 317class KeyboardInstrument(Instrument): 318 319 def __init__(self): 320 super().__init__() 321 self.instrumentName = 'Keyboard' 322 self.instrumentAbbreviation = 'Kb' 323 self.instrumentSound = 'keyboard.piano' 324 325 326class Piano(KeyboardInstrument): 327 ''' 328 329 >>> p = instrument.Piano() 330 >>> p.instrumentName 331 'Piano' 332 >>> p.midiProgram 333 0 334 ''' 335 336 def __init__(self): 337 super().__init__() 338 339 self.instrumentName = 'Piano' 340 self.instrumentAbbreviation = 'Pno' 341 self.midiProgram = 0 342 343 self.lowestNote = pitch.Pitch('A0') 344 self.highestNote = pitch.Pitch('C8') 345 346 347class Harpsichord(KeyboardInstrument): 348 def __init__(self): 349 super().__init__() 350 351 self.instrumentName = 'Harpsichord' 352 self.instrumentAbbreviation = 'Hpschd' 353 self.midiProgram = 6 354 self.instrumentSound = 'keyboard.harpsichord' 355 356 self.lowestNote = pitch.Pitch('F1') 357 self.highestNote = pitch.Pitch('F6') 358 359 360class Clavichord(KeyboardInstrument): 361 def __init__(self): 362 super().__init__() 363 364 self.instrumentName = 'Clavichord' 365 self.instrumentAbbreviation = 'Clv' 366 self.midiProgram = 7 367 self.instrumentSound = 'keyboard.clavichord' 368 369 # TODO: self.lowestNote = pitch.Pitch('') 370 # TODO: self.highestNote = pitch.Pitch('') 371 372 373class Celesta(KeyboardInstrument): 374 def __init__(self): 375 super().__init__() 376 377 self.instrumentName = 'Celesta' 378 self.instrumentAbbreviation = 'Clst' 379 self.midiProgram = 8 380 self.instrumentSound = 'keyboard.celesta' 381 382 383class Sampler(KeyboardInstrument): 384 def __init__(self): 385 super().__init__() 386 387 self.instrumentName = 'Sampler' 388 self.instrumentAbbreviation = 'Samp' 389 self.midiProgram = 55 390 391 392class ElectricPiano(Piano): 393 ''' 394 395 >>> p = instrument.ElectricPiano() 396 >>> p.instrumentName 397 'Electric Piano' 398 >>> p.midiProgram 399 2 400 ''' 401 def __init__(self): 402 super().__init__() 403 404 self.instrumentName = 'Electric Piano' 405 self.instrumentAbbreviation = 'E.Pno' 406 self.midiProgram = 2 407 408 409# ------------------------------------------------------------------------------ 410 411 412class Organ(Instrument): 413 def __init__(self): 414 super().__init__() 415 self.instrumentName = 'Organ' 416 self.midiProgram = 19 417 self.instrumentSound = 'keyboard.organ' 418 419 420class PipeOrgan(Organ): 421 def __init__(self): 422 super().__init__() 423 424 self.instrumentName = 'Pipe Organ' 425 self.instrumentAbbreviation = 'P Org' 426 self.midiProgram = 19 427 self.instrumentSound = 'keyboard.organ.pipe' 428 self.lowestNote = pitch.Pitch('C2') 429 self.highestNote = pitch.Pitch('C6') 430 431 432class ElectricOrgan(Organ): 433 def __init__(self): 434 super().__init__() 435 436 self.instrumentName = 'Electric Organ' 437 self.instrumentAbbreviation = 'Elec Org' 438 self.midiProgram = 16 439 440 self.lowestNote = pitch.Pitch('C2') 441 self.highestNote = pitch.Pitch('C6') 442 443 444class ReedOrgan(Organ): 445 def __init__(self): 446 super().__init__() 447 448 self.instrumentName = 'Reed Organ' 449 # TODO self.instrumentAbbreviation = '' 450 self.midiProgram = 20 451 self.instrumentSound = 'keyboard.organ.reed' 452 453 self.lowestNote = pitch.Pitch('C2') 454 self.highestNote = pitch.Pitch('C6') 455 456 457class Accordion(Organ): 458 def __init__(self): 459 super().__init__() 460 461 self.instrumentName = 'Accordion' 462 self.instrumentAbbreviation = 'Acc' 463 self.midiProgram = 21 464 self.instrumentSound = 'keyboard.accordion' 465 466 self.lowestNote = pitch.Pitch('F3') 467 self.highestNote = pitch.Pitch('A6') 468 469 470class Harmonica(Instrument): 471 def __init__(self): 472 super().__init__() 473 474 self.instrumentName = 'Harmonica' 475 self.instrumentAbbreviation = 'Hmca' 476 self.midiProgram = 22 477 self.instrumentSound = 'wind.reed.harmonica' 478 479 self.lowestNote = pitch.Pitch('C3') 480 self.highestNote = pitch.Pitch('C6') 481 482 483# ----------------------------------------------------- 484class StringInstrument(Instrument): 485 486 def __init__(self): 487 super().__init__() 488 self._stringPitches = None 489 self._cachedPitches = None 490 self.instrumentName = 'StringInstrument' 491 self.instrumentAbbreviation = 'Str' 492 493 self.midiProgram = 48 494 495 def _getStringPitches(self): 496 if hasattr(self, '_cachedPitches') and self._cachedPitches is not None: 497 return self._cachedPitches 498 elif not hasattr(self, '_stringPitches'): 499 raise InstrumentException('cannot get stringPitches for these instruments') 500 else: 501 self._cachedPitches = [pitch.Pitch(x) for x in self._stringPitches] 502 return self._cachedPitches 503 504 def _setStringPitches(self, newPitches): 505 if newPitches and (hasattr(newPitches[0], 'step') or newPitches[0] is None): 506 # newPitches is pitchObjects or something 507 self._stringPitches = newPitches 508 self._cachedPitches = newPitches 509 else: 510 self._cachedPitches = None 511 self._stringPitches = newPitches 512 513 stringPitches = property(_getStringPitches, _setStringPitches, doc=''' 514 stringPitches is a property that stores a list of Pitches (or pitch names, 515 such as "C4") that represent the pitch of the open strings from lowest to 516 highest.[*] 517 518 519 520 >>> vln1 = instrument.Violin() 521 >>> [str(p) for p in vln1.stringPitches] 522 ['G3', 'D4', 'A4', 'E5'] 523 524 instrument.stringPitches are full pitch objects, not just names: 525 526 >>> [x.octave for x in vln1.stringPitches] 527 [3, 4, 4, 5] 528 529 Scordatura for Scelsi's violin concerto *Anahit*. 530 (N.B. that string to pitch conversion is happening automatically) 531 532 >>> vln1.stringPitches = ['G3', 'G4', 'B4', 'D4'] 533 534 (`[*]In some tuning methods such as reentrant tuning on the ukulele, 535 lute, or five-string banjo the order might not strictly be from lowest to 536 highest. The same would hold true for certain violin scordatura pieces, such 537 as some of Biber's *Mystery Sonatas*`) 538 ''') 539 540 541class Violin(StringInstrument): 542 def __init__(self): 543 super().__init__() 544 545 self.instrumentName = 'Violin' 546 self.instrumentAbbreviation = 'Vln' 547 self.midiProgram = 40 548 self.instrumentSound = 'strings.violin' 549 550 self.lowestNote = pitch.Pitch('G3') 551 self._stringPitches = ['G3', 'D4', 'A4', 'E5'] 552 553 554class Viola(StringInstrument): 555 def __init__(self): 556 super().__init__() 557 558 self.instrumentName = 'Viola' 559 self.instrumentAbbreviation = 'Vla' 560 self.midiProgram = 41 561 self.instrumentSound = 'strings.viola' 562 563 self.lowestNote = pitch.Pitch('C3') 564 self._stringPitches = ['C3', 'G3', 'D4', 'A4'] 565 566 567class Violoncello(StringInstrument): 568 def __init__(self): 569 super().__init__() 570 571 self.instrumentName = 'Violoncello' 572 self.instrumentAbbreviation = 'Vc' 573 self.midiProgram = 42 574 self.instrumentSound = 'strings.cello' 575 576 self.lowestNote = pitch.Pitch('C2') 577 self._stringPitches = ['C2', 'G2', 'D3', 'A3'] 578 579 580class Contrabass(StringInstrument): 581 ''' 582 For the Contrabass (or double bass), the stringPitches attribute refers to the sounding pitches 583 of each string; whereas the lowestNote attribute refers to the lowest written note. 584 ''' 585 586 def __init__(self): 587 super().__init__() 588 589 self.instrumentName = 'Contrabass' 590 self.instrumentAbbreviation = 'Cb' 591 self.midiProgram = 43 592 self.instrumentSound = 'strings.contrabass' 593 594 self.lowestNote = pitch.Pitch('E2') 595 self._stringPitches = ['E1', 'A1', 'D2', 'G2'] 596 self.transposition = interval.Interval('P-8') 597 598 599class Harp(StringInstrument): 600 def __init__(self): 601 super().__init__() 602 603 self.instrumentName = 'Harp' 604 self.instrumentAbbreviation = 'Hp' 605 self.midiProgram = 46 606 self.instrumentSound = 'pluck.harp' 607 608 self.lowestNote = pitch.Pitch('C1') 609 self.highestNote = pitch.Pitch('G#7') 610 611 612class Guitar(StringInstrument): 613 def __init__(self): 614 super().__init__() 615 616 self.instrumentName = 'Guitar' 617 self.instrumentAbbreviation = 'Gtr' 618 self.midiProgram = 24 # default -- Acoustic 619 self.instrumentSound = 'pluck.guitar' 620 621 self.lowestNote = pitch.Pitch('E2') 622 self._stringPitches = ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'] 623 624 625class AcousticGuitar(Guitar): 626 def __init__(self): 627 super().__init__() 628 629 self.instrumentName = 'Acoustic Guitar' 630 self.instrumentAbbreviation = 'Ac Gtr' 631 self.midiProgram = 24 632 self.instrumentSound = 'pluck.guitar.acoustic' 633 634 635class ElectricGuitar(Guitar): 636 def __init__(self): 637 super().__init__() 638 639 self.instrumentName = 'Electric Guitar' 640 self.instrumentAbbreviation = 'Elec Gtr' 641 self.midiProgram = 26 642 self.instrumentSound = 'pluck.guitar.electric' 643 644 645class AcousticBass(Guitar): 646 def __init__(self): 647 super().__init__() 648 649 self.instrumentName = 'Acoustic Bass' 650 self.instrumentAbbreviation = 'Ac b' 651 self.midiProgram = 32 652 self.instrumentSound = 'pluck.bass.acoustic' 653 654 self.lowestNote = pitch.Pitch('E1') 655 self._stringPitches = ['E1', 'A1', 'D2', 'G2'] 656 657 658class ElectricBass(Guitar): 659 def __init__(self): 660 super().__init__() 661 662 self.instrumentName = 'Electric Bass' 663 self.instrumentAbbreviation = 'Elec b' 664 self.midiProgram = 33 665 self.instrumentSound = 'pluck.bass.electric' 666 667 self.lowestNote = pitch.Pitch('E1') 668 self._stringPitches = ['E1', 'A1', 'D2', 'G2'] 669 670 671class FretlessBass(Guitar): 672 def __init__(self): 673 super().__init__() 674 675 self.instrumentName = 'Fretless Bass' 676 # TODO: self.instrumentAbbreviation = '' 677 self.midiProgram = 35 678 self.instrumentSound = 'pluck.bass.fretless' 679 680 self.lowestNote = pitch.Pitch('E1') 681 self._stringPitches = ['E1', 'A1', 'D2', 'G2'] 682 683 684class Mandolin(StringInstrument): 685 def __init__(self): 686 super().__init__() 687 688 self.instrumentName = 'Mandolin' 689 self.instrumentAbbreviation = 'Mdln' 690 self.instrumentSound = 'pluck.mandolin' 691 692 self.lowestNote = pitch.Pitch('G3') 693 self._stringPitches = ['G3', 'D4', 'A4', 'E5'] 694 695 696class Ukulele(StringInstrument): 697 def __init__(self): 698 super().__init__() 699 700 self.instrumentName = 'Ukulele' 701 self.instrumentAbbreviation = 'Uke' 702 self.instrumentSound = 'pluck.ukulele' 703 704 self.lowestNote = pitch.Pitch('C4') 705 self._stringPitches = ['G4', 'C4', 'E4', 'A4'] 706 707 708class Banjo(StringInstrument): 709 def __init__(self): 710 super().__init__() 711 712 self.instrumentName = 'Banjo' 713 self.instrumentAbbreviation = 'Bjo' 714 self.instrumentSound = 'pluck.banjo' 715 self.midiProgram = 105 716 717 self.lowestNote = pitch.Pitch('C3') 718 self._stringPitches = ['C3', 'G3', 'D4', 'A4'] 719 self.transposition = interval.Interval('P-8') 720 721 722class Lute(StringInstrument): 723 def __init__(self): 724 super().__init__() 725 726 self.instrumentName = 'Lute' 727 self.instrumentAbbreviation = 'Lte' 728 self.instrumentSound = 'pluck.lute' 729 self.midiProgram = 24 730 731 732class Sitar(StringInstrument): 733 def __init__(self): 734 super().__init__() 735 736 self.instrumentName = 'Sitar' 737 self.instrumentAbbreviation = 'Sit' 738 self.instrumentSound = 'pluck.sitar' 739 self.midiProgram = 104 740 741 742class Shamisen(StringInstrument): 743 def __init__(self): 744 super().__init__() 745 746 self.instrumentName = 'Shamisen' 747 # TODO: self.instrumentAbbreviation = '' 748 self.instrumentSound = 'pluck.shamisen' 749 self.midiProgram = 106 750 751 752class Koto(StringInstrument): 753 def __init__(self): 754 super().__init__() 755 756 self.instrumentName = 'Koto' 757 # TODO: self.instrumentAbbreviation = '' 758 self.instrumentSound = 'pluck.koto' 759 self.midiProgram = 107 760 761# ------------------------------------------------------------------------------ 762 763 764class WoodwindInstrument(Instrument): 765 def __init__(self): 766 super().__init__() 767 self.instrumentName = 'Woodwind' 768 self.instrumentAbbreviation = 'Ww' 769 770 771class Flute(WoodwindInstrument): 772 def __init__(self): 773 super().__init__() 774 775 self.instrumentName = 'Flute' 776 self.instrumentAbbreviation = 'Fl' 777 self.instrumentSound = 'wind.flutes.flute' 778 self.midiProgram = 73 779 780 self.lowestNote = pitch.Pitch('C4') # Occasionally (rarely) B3 781 782 783class Piccolo(Flute): 784 def __init__(self): 785 super().__init__() 786 787 self.instrumentName = 'Piccolo' 788 self.instrumentAbbreviation = 'Picc' 789 self.instrumentSound = 'wind.flutes.piccolo' 790 self.midiProgram = 72 791 792 self.lowestNote = pitch.Pitch('D4') # Occasionally (rarely) C4 793 self.transposition = interval.Interval('P8') 794 795 796class Recorder(Flute): 797 def __init__(self): 798 super().__init__() 799 800 self.instrumentName = 'Recorder' 801 self.instrumentAbbreviation = 'Rec' 802 self.instrumentSound = 'wind.flutes.recorder' 803 self.midiProgram = 74 804 805 self.lowestNote = pitch.Pitch('F4') 806 807 808class PanFlute(Flute): 809 def __init__(self): 810 super().__init__() 811 812 self.instrumentName = 'Pan Flute' 813 self.instrumentAbbreviation = 'P Fl' 814 self.instrumentSound = 'wind.flutes.panpipes' 815 self.midiProgram = 75 816 817 818class Shakuhachi(Flute): 819 def __init__(self): 820 super().__init__() 821 822 self.instrumentName = 'Shakuhachi' 823 self.instrumentAbbreviation = 'Shk Fl' 824 self.instrumentSound = 'wind.flutes.shakuhachi' 825 self.midiProgram = 77 826 827 828class Whistle(Flute): 829 def __init__(self): 830 super().__init__() 831 832 self.instrumentName = 'Whistle' 833 self.instrumentAbbreviation = 'Whs' 834 self.instrumentSound = 'wind.flutes.whistle' 835 self.inGMPercMap = True 836 self.percMapPitch = 71 837 self.midiProgram = 78 838 839 840class Ocarina(Flute): 841 def __init__(self): 842 super().__init__() 843 844 self.instrumentName = 'Ocarina' 845 self.instrumentAbbreviation = 'Oc' 846 self.instrumentSound = 'wind.flutes.ocarina' 847 self.midiProgram = 79 848 849 850class Oboe(WoodwindInstrument): 851 def __init__(self): 852 super().__init__() 853 854 self.instrumentName = 'Oboe' 855 self.instrumentAbbreviation = 'Ob' 856 self.instrumentSound = 'wind.reed.oboe' 857 self.midiProgram = 68 858 859 self.lowestNote = pitch.Pitch('B-3') 860 861 862class EnglishHorn(WoodwindInstrument): 863 def __init__(self): 864 super().__init__() 865 866 self.instrumentName = 'English Horn' 867 self.instrumentAbbreviation = 'Eng Hn' 868 self.instrumentSound = 'wind.reed.english-horn' 869 self.midiProgram = 69 870 871 self.lowestNote = pitch.Pitch('B3') 872 self.transposition = interval.Interval('P-5') 873 874 875class Clarinet(WoodwindInstrument): 876 def __init__(self): 877 super().__init__() 878 879 self.instrumentName = 'Clarinet' 880 self.instrumentAbbreviation = 'Cl' 881 self.instrumentSound = 'wind.reed.clarinet' 882 self.midiProgram = 71 883 884 self.lowestNote = pitch.Pitch('E3') 885 self.transposition = interval.Interval('M-2') 886 887 888class BassClarinet(Clarinet): 889 ''' 890 >>> bcl = instrument.BassClarinet() 891 >>> bcl.instrumentName 892 'Bass clarinet' 893 >>> bcl.midiProgram 894 71 895 >>> 'WoodwindInstrument' in bcl.classes 896 True 897 ''' 898 899 def __init__(self): 900 super().__init__() 901 902 self.instrumentName = 'Bass clarinet' 903 self.instrumentAbbreviation = 'Bs Cl' 904 self.instrumentSound = 'wind.reed.clarinet.bass' 905 906 self.lowestNote = pitch.Pitch('E-3') 907 self.transposition = interval.Interval('M-9') 908 909 910class Bassoon(WoodwindInstrument): 911 def __init__(self): 912 super().__init__() 913 914 self.instrumentName = 'Bassoon' 915 self.instrumentAbbreviation = 'Bsn' 916 self.instrumentSound = 'wind.reed.bassoon' 917 self.midiProgram = 70 918 919 self.lowestNote = pitch.Pitch('B-1') 920 921 922class Contrabassoon(Bassoon): 923 def __init__(self): 924 super().__init__() 925 926 self.instrumentName = 'Contrabassoon' 927 self.instrumentAbbreviation = 'C Bsn' 928 self.instrumentSound = 'wind.reed.bassoon' 929 self.midiProgram = 70 930 931 self.lowestNote = pitch.Pitch('B-1') 932 933 934class Saxophone(WoodwindInstrument): 935 def __init__(self): 936 super().__init__() 937 938 self.instrumentName = 'Saxophone' 939 self.instrumentAbbreviation = 'Sax' 940 self.instrumentSound = 'wind.reed.saxophone' 941 self.midiProgram = 65 942 943 self.lowestNote = pitch.Pitch('B-3') 944 945 946class SopranoSaxophone(Saxophone): 947 def __init__(self): 948 super().__init__() 949 950 self.instrumentName = 'Soprano Saxophone' 951 self.instrumentAbbreviation = 'S Sax' 952 self.instrumentSound = 'wind.reed.saxophone.soprano' 953 self.midiProgram = 64 954 955 self.transposition = interval.Interval('M-2') 956 957 958class AltoSaxophone(Saxophone): 959 def __init__(self): 960 super().__init__() 961 962 self.instrumentName = 'Alto Saxophone' 963 self.instrumentAbbreviation = 'A Sax' 964 self.instrumentSound = 'wind.reed.saxophone.alto' 965 self.midiProgram = 65 966 967 self.transposition = interval.Interval('M-6') 968 969 970class TenorSaxophone(Saxophone): 971 def __init__(self): 972 super().__init__() 973 974 self.instrumentName = 'Tenor Saxophone' 975 self.instrumentAbbreviation = 'T Sax' 976 self.instrumentSound = 'wind.reed.saxophone.tenor' 977 self.midiProgram = 66 978 979 self.transposition = interval.Interval('M-9') 980 981 982class BaritoneSaxophone(Saxophone): 983 def __init__(self): 984 super().__init__() 985 986 self.instrumentName = 'Baritone Saxophone' 987 self.instrumentAbbreviation = 'Bar Sax' 988 self.instrumentSound = 'wind.reed.saxophone.baritone' 989 self.midiProgram = 67 990 991 self.transposition = interval.Interval('M-13') 992 993 994class Bagpipes(WoodwindInstrument): 995 def __init__(self): 996 super().__init__() 997 998 self.instrumentName = 'Bagpipes' 999 self.instrumentAbbreviation = 'Bag' 1000 self.instrumentSound = 'wind.pipes.bagpipes' 1001 self.midiProgram = 109 1002 1003 1004class Shehnai(WoodwindInstrument): 1005 def __init__(self): 1006 super().__init__() 1007 1008 self.instrumentName = 'Shehnai' 1009 self.instrumentAbbreviation = 'Shn' 1010 # another spelling is 'Shehnai' 1011 self.instrumentSound = 'wind.reed.shenai' 1012 self.midiProgram = 111 1013 1014# ------------------------------------------------------------------------------ 1015 1016 1017class BrassInstrument(Instrument): 1018 def __init__(self): 1019 super().__init__() 1020 self.instrumentName = 'Brass' 1021 self.instrumentAbbreviation = 'Brs' 1022 self.midiProgram = 61 1023 1024 1025class Horn(BrassInstrument): 1026 ''' 1027 >>> hn = instrument.Horn() 1028 >>> hn.instrumentName 1029 'Horn' 1030 >>> hn.midiProgram 1031 60 1032 >>> 'BrassInstrument' in hn.classes 1033 True 1034 ''' 1035 1036 def __init__(self): 1037 super().__init__() 1038 1039 self.instrumentName = 'Horn' 1040 self.instrumentAbbreviation = 'Hn' 1041 self.instrumentSound = 'brass.french-horn' 1042 self.midiProgram = 60 1043 1044 self.lowestNote = pitch.Pitch('C2') 1045 self.transposition = interval.Interval('P-5') 1046 1047 1048class Trumpet(BrassInstrument): 1049 def __init__(self): 1050 super().__init__() 1051 1052 self.instrumentName = 'Trumpet' 1053 self.instrumentAbbreviation = 'Tpt' 1054 self.instrumentSound = 'brass.trumpet' 1055 self.midiProgram = 56 1056 1057 self.lowestNote = pitch.Pitch('F#3') 1058 self.transposition = interval.Interval('M-2') 1059 1060 1061class Trombone(BrassInstrument): 1062 def __init__(self): 1063 super().__init__() 1064 1065 self.instrumentName = 'Trombone' 1066 self.instrumentAbbreviation = 'Trb' 1067 self.instrumentSound = 'brass.trombone' 1068 self.midiProgram = 57 1069 1070 self.lowestNote = pitch.Pitch('E2') 1071 1072 1073class BassTrombone(Trombone): 1074 def __init__(self): 1075 super().__init__() 1076 1077 self.instrumentName = 'Bass Trombone' 1078 self.instrumentAbbreviation = 'BTrb' 1079 self.instrumentSound = 'brass.trombone.bass' 1080 1081 self.lowestNote = pitch.Pitch('B-1') 1082 1083 1084class Tuba(BrassInstrument): 1085 def __init__(self): 1086 super().__init__() 1087 1088 self.instrumentName = 'Tuba' 1089 self.instrumentAbbreviation = 'Tba' 1090 self.instrumentSound = 'brass.tuba' 1091 self.midiProgram = 58 1092 1093 self.lowestNote = pitch.Pitch('D1') 1094 1095 1096# ------------ 1097 1098class Percussion(Instrument): 1099 def __init__(self): 1100 super().__init__() 1101 self.inGMPercMap = False 1102 self.percMapPitch = None 1103 self.instrumentName = 'Percussion' 1104 self.instrumentAbbreviation = 'Perc' 1105 1106 1107class PitchedPercussion(Percussion): 1108 pass 1109 1110 1111class UnpitchedPercussion(Percussion): 1112 def __init__(self): 1113 super().__init__() 1114 self._modifier = None 1115 self._modifierToPercMapPitch = {} 1116 self.midiChannel = 9 # 0-indexed, i.e. MIDI channel 10 1117 1118 def _getModifier(self): 1119 return self._modifier 1120 1121 def _setModifier(self, modifier): 1122 modifier = modifier.lower().strip() 1123 # BEN: to-do, pull out hyphens, spaces, etc. 1124 1125 if self.inGMPercMap is True and modifier.lower() in self._modifierToPercMapPitch: 1126 self.percMapPitch = self._modifierToPercMapPitch[modifier.lower()] 1127 1128 # normalize modifiers... 1129 if self.percMapPitch in self._percMapPitchToModifier: 1130 modifier = self._percMapPitchToModifier[self.percMapPitch] 1131 1132 self._modifier = modifier 1133 1134 modifier = property(_getModifier, _setModifier, doc=''' 1135 Returns or sets the modifier for this instrument. A modifier could 1136 be something like "low-floor" for a TomTom or "rimshot" for a SnareDrum. 1137 1138 If the modifier is in the object's ._modifierToPercMapPitch dictionary 1139 then changing the modifier also changes the .percMapPitch for the object 1140 1141 1142 >>> bd = instrument.BongoDrums() 1143 >>> bd.modifier 1144 'high' 1145 1146 >>> bd.percMapPitch 1147 60 1148 >>> bd.modifier = 'low' 1149 >>> bd.percMapPitch 1150 61 1151 1152 Variations on modifiers can also be used and they get normalized: 1153 1154 >>> wb1 = instrument.Woodblock() 1155 >>> wb1.percMapPitch 1156 76 1157 >>> wb1.modifier = 'LO' 1158 >>> wb1.percMapPitch 1159 77 1160 >>> wb1.modifier # n.b. -- not LO 1161 'low' 1162 ''') 1163 1164 1165class Vibraphone(PitchedPercussion): 1166 def __init__(self): 1167 super().__init__() 1168 1169 self.instrumentName = 'Vibraphone' 1170 self.instrumentAbbreviation = 'Vbp' 1171 self.instrumentSound = 'pitched-percussion.vibraphone' 1172 self.midiProgram = 11 1173 1174 1175class Marimba(PitchedPercussion): 1176 def __init__(self): 1177 super().__init__() 1178 1179 self.instrumentName = 'Marimba' 1180 self.instrumentAbbreviation = 'Mar' 1181 self.instrumentSound = 'pitched-percussion.marimba' 1182 self.midiProgram = 12 1183 1184 1185class Xylophone(PitchedPercussion): 1186 def __init__(self): 1187 super().__init__() 1188 1189 self.instrumentName = 'Xylophone' 1190 self.instrumentAbbreviation = 'Xyl.' 1191 self.instrumentSound = 'pitched-percussion.xylophone' 1192 self.midiProgram = 13 1193 1194 1195class Glockenspiel(PitchedPercussion): 1196 def __init__(self): 1197 super().__init__() 1198 1199 self.instrumentName = 'Glockenspiel' 1200 self.instrumentAbbreviation = 'Gsp' 1201 self.instrumentSound = 'pitched-percussion.glockenspiel' 1202 self.midiProgram = 9 1203 1204 1205class ChurchBells(PitchedPercussion): 1206 def __init__(self): 1207 super().__init__() 1208 1209 self.instrumentName = 'Church Bells' 1210 self.instrumentAbbreviation = 'Bells' 1211 self.instrumentSound = 'metal.bells.church' 1212 self.midiProgram = 14 1213 1214 1215class TubularBells(PitchedPercussion): 1216 def __init__(self): 1217 super().__init__() 1218 1219 self.instrumentName = 'Tubular Bells' 1220 self.instrumentAbbreviation = 'Tbells' 1221 self.instrumentSound = 'pitched-percussion.tubular-bells' 1222 self.midiProgram = 14 1223 1224 1225class Gong(PitchedPercussion): 1226 def __init__(self): 1227 super().__init__() 1228 1229 self.instrumentName = 'Gong' 1230 self.instrumentAbbreviation = 'Gng' 1231 self.instrumentSound = 'metal.gong' 1232 1233 1234class Handbells(PitchedPercussion): 1235 def __init__(self): 1236 super().__init__() 1237 1238 self.instrumentName = 'Handbells' 1239 # TODO: self.instrumentAbbreviation = '' 1240 self.instrumentSound = 'pitched-percussion.handbells' 1241 1242 1243class Dulcimer(PitchedPercussion): 1244 def __init__(self): 1245 super().__init__() 1246 1247 self.instrumentName = 'Dulcimer' 1248 # TODO: self.instrumentAbbreviation = '' 1249 self.instrumentSound = 'pluck.dulcimer' 1250 self.midiProgram = 15 1251 1252 1253class SteelDrum(PitchedPercussion): 1254 def __init__(self): 1255 super().__init__() 1256 1257 self.instrumentName = 'Steel Drum' 1258 self.instrumentAbbreviation = 'St Dr' 1259 self.instrumentSound = 'metal.steel-drums' 1260 self.midiProgram = 114 1261 1262 1263class Timpani(PitchedPercussion): 1264 def __init__(self): 1265 super().__init__() 1266 1267 self.instrumentName = 'Timpani' 1268 self.instrumentAbbreviation = 'Timp' 1269 self.instrumentSound = 'drum.timpani' 1270 self.midiProgram = 47 1271 1272 1273class Kalimba(PitchedPercussion): 1274 def __init__(self): 1275 super().__init__() 1276 1277 self.instrumentName = 'Kalimba' 1278 self.instrumentAbbreviation = 'Kal' 1279 self.instrumentSound = 'pitched-percussion.kalimba' 1280 self.midiProgram = 108 1281 1282 1283class Woodblock(UnpitchedPercussion): 1284 def __init__(self): 1285 super().__init__() 1286 1287 self.instrumentName = 'Woodblock' 1288 self.instrumentAbbreviation = 'Wd Bl' 1289 self.instrumentSound = 'wood.wood-block' 1290 self.inGMPercMap = True 1291 self.midiProgram = 115 1292 1293 self._modifier = 'high' 1294 self._modifierToPercMapPitch = {'high': 76, 'low': 77, 'hi': 76, 'lo': 77} 1295 self._percMapPitchToModifier = {76: 'high', 77: 'low'} 1296 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1297 1298 1299class TempleBlock(UnpitchedPercussion): 1300 def __init__(self): 1301 super().__init__() 1302 1303 self.instrumentName = 'Temple Block' 1304 self.instrumentAbbreviation = 'Temp Bl' 1305 self.instrumentSound = 'wood.temple-block' 1306 1307 1308class Castanets(UnpitchedPercussion): 1309 def __init__(self): 1310 super().__init__() 1311 1312 self.instrumentName = 'Castanets' 1313 self.instrumentAbbreviation = 'Cas' 1314 self.instrumentSound = 'wood.castanets' 1315 1316 1317class Maracas(UnpitchedPercussion): 1318 def __init__(self): 1319 super().__init__() 1320 1321 self.instrumentName = 'Maracas' 1322 self.inGMPercMap = True 1323 self.percMapPitch = 70 1324 # TODO: self.instrumentAbbreviation = '' 1325 self.instrumentSound = 'rattle.maraca' 1326 1327 1328class Vibraslap(UnpitchedPercussion): 1329 def __init__(self): 1330 super().__init__() 1331 1332 self.instrumentName = 'Vibraslap' 1333 self.instrumentAbbreviation = 'Vbslp' 1334 self.instrumentSound = 'rattle.vibraslap' 1335 self.inGMPercMap = True 1336 self.percMapPitch = 58 1337 1338# BEN: Standardize Cymbals as plural 1339 1340 1341class Cymbals(UnpitchedPercussion): 1342 def __init__(self): 1343 super().__init__() 1344 self.instrumentName = 'Cymbals' 1345 self.instrumentAbbreviation = 'Cym' 1346 1347 1348class FingerCymbals(Cymbals): 1349 def __init__(self): 1350 super().__init__() 1351 1352 self.instrumentName = 'Finger Cymbals' 1353 self.instrumentAbbreviation = 'Fing Cym' 1354 self.instrumentSound = 'metal.cymbal.finger' 1355 1356 1357class CrashCymbals(Cymbals): 1358 def __init__(self): 1359 super().__init__() 1360 1361 self.instrumentName = 'Crash Cymbals' 1362 self.instrumentAbbreviation = 'Cym' 1363 self.instrumentSound = 'metal.cymbal.crash' 1364 self.inGMPercMap = True 1365 self._modifier = '1' 1366 1367 self._modifierToPercMapPitch = {'1': 49, 1368 '2': 57, 1369 } 1370 self._percMapPitchToModifier = {49: '1', 1371 57: '2', 1372 } 1373 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1374 1375 1376class SuspendedCymbal(Cymbals): 1377 def __init__(self): 1378 super().__init__() 1379 1380 self.instrumentName = 'Suspended Cymbal' 1381 # TODO: self.instrumentAbbreviation = '' 1382 self.instrumentSound = 'metal.cymbal.suspended' 1383 1384 1385class SizzleCymbal(Cymbals): 1386 def __init__(self): 1387 super().__init__() 1388 1389 self.instrumentName = 'Sizzle Cymbal' 1390 # TODO: self.instrumentAbbreviation = '' 1391 self.instrumentSound = 'metal.cymbal.sizzle' 1392 1393 1394class SplashCymbals(Cymbals): 1395 def __init__(self): 1396 super().__init__() 1397 1398 self.instrumentName = 'Splash Cymbals' 1399 # TODO: self.instrumentAbbreviation = '' 1400 self.instrumentSound = 'metal.cymbal.splash' 1401 1402 1403class RideCymbals(Cymbals): 1404 def __init__(self): 1405 super().__init__() 1406 1407 self.instrumentName = 'Ride Cymbals' 1408 # TODO: self.instrumentAbbreviation = '' 1409 self.instrumentSound = 'metal.cymbal.ride' 1410 1411 1412class HiHatCymbal(Cymbals): 1413 def __init__(self): 1414 super().__init__() 1415 1416 self.instrumentName = 'Hi-Hat Cymbal' 1417 self.instrumentSound = 'metal.hi-hat' 1418 self.inGMPercMap = True 1419 1420 self._modifier = 'pedal' 1421 1422 self._modifierToPercMapPitch = {'pedal': 44, 1423 'open': 46, 1424 'closed': 42, 1425 } 1426 self._percMapPitchToModifier = {44: 'pedal', 1427 46: 'open', 1428 42: 'closed', 1429 } 1430 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1431 1432 # TODO: self.instrumentAbbreviation = '' 1433 1434 1435class Triangle(UnpitchedPercussion): 1436 def __init__(self): 1437 super().__init__() 1438 1439 self.instrumentName = 'Triangle' 1440 self.instrumentAbbreviation = 'Tri' 1441 self.instrumentSound = 'metal.triangle' 1442 self.inGMPercMap = True 1443 self._modifier = 'open' 1444 1445 self._modifierToPercMapPitch = {'open': 81, 1446 'mute': 80, 1447 } 1448 self._percMapPitchToModifier = {80: 'mute', 1449 81: 'open', 1450 } 1451 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1452 1453 1454class Cowbell(UnpitchedPercussion): 1455 def __init__(self): 1456 super().__init__() 1457 1458 self.instrumentName = 'Cowbell' 1459 self.instrumentAbbreviation = 'Cwb' 1460 self.instrumentSound = 'metal.bells.cowbell' 1461 self.inGMPercMap = True 1462 self.percMapPitch = 56 1463 1464 1465class Agogo(UnpitchedPercussion): 1466 def __init__(self): 1467 super().__init__() 1468 1469 self.instrumentName = 'Agogo' 1470 # TODO: self.instrumentAbbreviation = '' 1471 self.instrumentSound = 'metal.bells.agogo' 1472 self.inGMPercMap = True 1473 self.percMapPitch = 67 1474 self.midiProgram = 113 1475 1476 1477class TamTam(UnpitchedPercussion): 1478 def __init__(self): 1479 super().__init__() 1480 1481 self.instrumentName = 'Tam-Tam' 1482 # TODO: self.instrumentAbbreviation = '' 1483 self.instrumentSound = 'metal.tamtam' 1484 1485 1486class SleighBells(UnpitchedPercussion): 1487 def __init__(self): 1488 super().__init__() 1489 1490 self.instrumentName = 'Sleigh Bells' 1491 # TODO: self.instrumentAbbreviation = '' 1492 self.instrumentSound = 'metal.bells.sleigh-bells' 1493 1494 1495class SnareDrum(UnpitchedPercussion): 1496 def __init__(self): 1497 super().__init__() 1498 1499 self.instrumentName = 'Snare Drum' 1500 self.instrumentAbbreviation = 'Sn Dr' 1501 self.instrumentSound = 'drum.snare-drum' 1502 self.inGMPercMap = True 1503 self._modifier = 'acoustic' 1504 self._modifierToPercMapPitch = {'acoustic': 38, 1505 'side': 37, 1506 'electric': 40, 1507 } 1508 self._percMapPitchToModifier = {38: 'acoustic', 1509 37: 'side', 1510 40: 'electric', 1511 } 1512 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1513 1514 1515class TenorDrum(UnpitchedPercussion): 1516 def __init__(self): 1517 super().__init__() 1518 1519 self.instrumentName = 'Tenor Drum' 1520 self.instrumentAbbreviation = 'Ten Dr' 1521 self.instrumentSound = 'drum.tenor-drum' 1522 1523 1524class BongoDrums(UnpitchedPercussion): 1525 def __init__(self): 1526 super().__init__() 1527 1528 self.instrumentName = 'Bongo Drums' 1529 self.instrumentAbbreviation = 'Bgo Dr' 1530 self.instrumentSound = 'drum.bongo' 1531 1532 self.inGMPercMap = True 1533 self._modifier = 'high' 1534 self._modifierToPercMapPitch = {'high': 60, 'low': 61} 1535 self._percMapPitchToModifier = {60: 'high', 61: 'low'} 1536 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1537 1538 1539class TomTom(UnpitchedPercussion): 1540 def __init__(self): 1541 super().__init__() 1542 1543 self.instrumentName = 'Tom-Tom' 1544 # TODO: self.instrumentAbbreviation = '' 1545 self.instrumentSound = 'drum.tom-tom' 1546 self.inGMPercMap = True 1547 self._modifier = 'low floor' 1548 self._modifierToPercMapPitch = {'low floor': 41, 'high floor': 43, 'low': 45, 1549 'low-mid': 47, 'high-mid': 48, 'high': 50} 1550 self._percMapPitchToModifier = {41: 'low floor', 43: 'high floor', 45: 'low', 1551 47: 'low-mid', 48: 'high-mid', 50: 'high'} 1552 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1553 1554 1555class Timbales(UnpitchedPercussion): 1556 def __init__(self): 1557 super().__init__() 1558 1559 self.instrumentName = 'Timbales' 1560 self.instrumentAbbreviation = 'Tim' 1561 self.instrumentSound = 'drum.timbale' 1562 self.inGMPercMap = True 1563 self._modifier = 'high' 1564 self._modifierToPercMapPitch = {'high': 65, 'low': 66} 1565 self._percMapPitchToModifier = {65: 'high', 66: 'low'} 1566 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1567 1568 1569class CongaDrum(UnpitchedPercussion): 1570 def __init__(self): 1571 super().__init__() 1572 1573 self.instrumentName = 'Conga Drum' 1574 self.instrumentAbbreviation = 'Cga Dr' 1575 self.instrumentSound = 'drum.conga' 1576 self.inGMPercMap = True 1577 self._modifier = 'low' 1578 self._modifierToPercMapPitch = {'low': 64, 'mute high': 62, 'open high': 63} 1579 self._percMapPitchToModifier = {64: 'low', 62: 'mute high', 63: 'open high'} 1580 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1581 1582 1583class BassDrum(UnpitchedPercussion): 1584 def __init__(self): 1585 super().__init__() 1586 1587 self.instrumentName = 'Bass Drum' 1588 self.instrumentAbbreviation = 'B Dr' 1589 self.instrumentSound = 'drum.bass-drum' 1590 self.inGMPercMap = True 1591 self._modifier = 'acoustic' 1592 self._modifierToPercMapPitch = {'acoustic': 35, '1': 36} 1593 self._percMapPitchToModifier = {35: 'acoustic', 36: '1'} 1594 self.percMapPitch = self._modifierToPercMapPitch[self._modifier] 1595 1596 1597class Taiko(UnpitchedPercussion): 1598 def __init__(self): 1599 super().__init__() 1600 1601 self.instrumentName = 'Taiko' 1602 # TODO: self.instrumentAbbreviation = '' 1603 self.instrumentSound = 'drum.taiko' 1604 self.midiProgram = 116 1605 1606 1607class Tambourine(UnpitchedPercussion): 1608 def __init__(self): 1609 super().__init__() 1610 1611 self.instrumentName = 'Tambourine' 1612 self.instrumentAbbreviation = 'Tmbn' 1613 self.instrumentSound = 'drum.tambourine' 1614 self.inGMPercMap = True 1615 self.percMapPitch = 54 1616 1617 1618class Whip(UnpitchedPercussion): 1619 def __init__(self): 1620 super().__init__() 1621 1622 self.instrumentName = 'Whip' 1623 # TODO: self.instrumentAbbreviation = '' 1624 self.instrumentSound = 'effect.whip' 1625 1626 1627class Ratchet(UnpitchedPercussion): 1628 def __init__(self): 1629 super().__init__() 1630 1631 self.instrumentName = 'Ratchet' 1632 # TODO: self.instrumentAbbreviation = '' 1633 self.instrumentSound = 'rattle.ratchet' 1634 1635 1636class Siren(UnpitchedPercussion): 1637 def __init__(self): 1638 super().__init__() 1639 1640 self.instrumentName = 'Siren' 1641 # TODO: self.instrumentAbbreviation = '' 1642 self.instrumentSound = 'effect.siren' 1643 1644 1645class SandpaperBlocks(UnpitchedPercussion): 1646 def __init__(self): 1647 super().__init__() 1648 1649 self.instrumentName = 'Sandpaper Blocks' 1650 self.instrumentAbbreviation = 'Sand Bl' 1651 self.instrumentSound = 'wood.sand-block' 1652 1653 1654class WindMachine(UnpitchedPercussion): 1655 def __init__(self): 1656 super().__init__() 1657 1658 self.instrumentName = 'Wind Machine' 1659 # TODO: self.instrumentAbbreviation = '' 1660 self.instrumentSound = 'effect.wind' 1661 1662# ----------------------------------------------------- 1663 1664 1665class Vocalist(Instrument): 1666 ''' 1667 n.b. called Vocalist to not be confused with stream.Voice 1668 ''' 1669 1670 def __init__(self): 1671 super().__init__() 1672 1673 self.instrumentName = 'Voice' 1674 self.instrumentAbbreviation = 'V' 1675 self.midiProgram = 53 1676 1677 1678class Soprano(Vocalist): 1679 def __init__(self): 1680 super().__init__() 1681 1682 self.instrumentName = 'Soprano' 1683 self.instrumentAbbreviation = 'S' 1684 self.instrumentSound = 'voice.soprano' 1685 1686 1687class MezzoSoprano(Soprano): 1688 def __init__(self): 1689 super().__init__() 1690 1691 self.instrumentName = 'Mezzo-Soprano' 1692 self.instrumentAbbreviation = 'Mez' 1693 self.instrumentSound = 'voice.mezzo-soprano' 1694 1695 1696class Alto(Vocalist): 1697 def __init__(self): 1698 super().__init__() 1699 1700 self.instrumentName = 'Alto' 1701 self.instrumentAbbreviation = 'A' 1702 self.instrumentSound = 'voice.alto' 1703 1704 1705class Tenor(Vocalist): 1706 def __init__(self): 1707 super().__init__() 1708 1709 self.instrumentName = 'Tenor' 1710 self.instrumentAbbreviation = 'T' 1711 self.instrumentSound = 'voice.tenor' 1712 1713 1714class Baritone(Vocalist): 1715 def __init__(self): 1716 super().__init__() 1717 1718 self.instrumentName = 'Baritone' 1719 self.instrumentAbbreviation = 'Bar' 1720 self.instrumentSound = 'voice.baritone' 1721 1722 1723class Bass(Vocalist): 1724 def __init__(self): 1725 super().__init__() 1726 1727 self.instrumentName = 'Bass' 1728 self.instrumentAbbreviation = 'B' 1729 self.instrumentSound = 'voice.bass' 1730 1731 1732class Choir(Vocalist): 1733 def __init__(self): 1734 super().__init__() 1735 1736 self.instrumentName = 'Choir' 1737 self.instrumentAbbreviation = 'Ch' 1738 self.instrumentSound = 'voice.choir' 1739 self.midiProgram = 52 1740 1741# ----------------------------------------------------- 1742 1743 1744class Conductor(Instrument): 1745 '''Presently used only for tracking the MIDI track containing tempo, 1746 key signature, and related metadata.''' 1747 def __init__(self): 1748 super().__init__(instrumentName='Conductor') 1749 1750# ----------------------------------------------------------------------------- 1751 1752 1753# noinspection SpellCheckingInspection 1754ensembleNamesBySize = ['no performers', 'solo', 'duet', 'trio', 'quartet', 1755 'quintet', 'sextet', 'septet', 'octet', 'nonet', 'dectet', 1756 'undectet', 'duodectet', 'tredectet', 'quattuordectet', 1757 'quindectet', 'sexdectet', 'septendectet', 'octodectet', 1758 'novemdectet', 'vigetet', 'unvigetet', 'duovigetet', 1759 'trevigetet', 'quattuorvigetet', 'quinvigetet', 'sexvigetet', 1760 'septenvigetet', 'octovigetet', 'novemvigetet', 1761 'trigetet', 'untrigetet', 'duotrigetet', 'tretrigetet', 1762 'quottuortrigetet', 'quintrigetet', 'sextrigetet', 1763 'septentrigetet', 'octotrigetet', 'novemtrigetet', 1764 'quadragetet', 'unquadragetet', 'duoquadragetet', 1765 'trequadragetet', 'quattuorquadragetet', 'quinquadragetet', 1766 'sexquadragetet', 'octoquadragetet', 'octoquadragetet', 1767 'novemquadragetet', 'quinquagetet', 'unquinquagetet', 1768 'duoquinquagetet', 'trequinguagetet', 'quattuorquinquagetet', 1769 'quinquinquagetet', 'sexquinquagetet', 'septenquinquagetet', 1770 'octoquinquagetet', 'novemquinquagetet', 'sexagetet', 1771 'undexagetet', 'duosexagetet', 'tresexagetet', 1772 'quoattuorsexagetet', 'quinsexagetet', 'sexsexagetet', 1773 'septensexagetet', 'octosexagetet', 'novemsexagetet', 1774 'septuagetet', 'unseptuagetet', 'duoseptuagetet', 'treseptuagetet', 1775 'quattuorseptuagetet', 'quinseptuagetet', 'sexseptuagetet', 1776 'septenseptuagetet', 'octoseptuagetet', 'novemseptuagetet', 1777 'octogetet', 'unoctogetet', 'duooctogetet', 1778 'treoctogetet', 'quattuoroctogetet', 'quinoctogetet', 1779 'sexoctogetet', 'septoctogetet', 'octooctogetet', 1780 'novemoctogetet', 'nonagetet', 'unnonagetet', 'duononagetet', 1781 'trenonagetet', 'quattuornonagetet', 'quinnonagetet', 1782 'sexnonagetet', 'septennonagetet', 'octononagetet', 1783 'novemnonagetet', 'centet'] 1784 1785 1786def ensembleNameBySize(number): 1787 ''' 1788 return the name of a generic ensemble with "number" players: 1789 1790 >>> instrument.ensembleNameBySize(4) 1791 'quartet' 1792 >>> instrument.ensembleNameBySize(1) 1793 'solo' 1794 >>> instrument.ensembleNameBySize(83) 1795 'treoctogetet' 1796 ''' 1797 if number > 100: 1798 return 'large ensemble' 1799 elif number < 0: 1800 raise InstrumentException('okay, you are on your own for this one buddy') 1801 else: 1802 return ensembleNamesBySize[int(number)] 1803 1804def deduplicate(s: stream.Stream, inPlace: bool = False) -> stream.Stream: 1805 ''' 1806 Check every offset in `s` for multiple instrument instances. 1807 If the `.partName` can be standardized across instances, 1808 i.e. if each instance has the same value or `None`, 1809 and likewise for `.instrumentName`, standardize the attributes. 1810 Further, and only if the above conditions are met, 1811 if there are two instances of the same class, remove all but one; 1812 if at least one generic `Instrument` instance is found at the same 1813 offset as one or more specific instruments, remove the generic `Instrument` instances. 1814 1815 Two `Instrument` instances: 1816 1817 >>> i1 = instrument.Instrument(instrumentName='Semi-Hollow Body') 1818 >>> i2 = instrument.Instrument() 1819 >>> i2.partName = 'Electric Guitar' 1820 >>> s1 = stream.Stream() 1821 >>> s1.insert(4, i1) 1822 >>> s1.insert(4, i2) 1823 >>> list(s1.getInstruments()) 1824 [<music21.instrument.Instrument 'Semi-Hollow Body'>, 1825 <music21.instrument.Instrument 'Electric Guitar: '>] 1826 >>> post = instrument.deduplicate(s1) 1827 >>> list(post.getInstruments()) 1828 [<music21.instrument.Instrument 'Electric Guitar: Semi-Hollow Body'>] 1829 1830 One `Instrument` instance and one subclass instance, with `inPlace` and parts: 1831 1832 >>> from music21.stream import Score, Part 1833 >>> i3 = instrument.Instrument() 1834 >>> i3.partName = 'Piccolo' 1835 >>> i4 = instrument.Piccolo() 1836 >>> s2 = stream.Score() 1837 >>> p1 = stream.Part() 1838 >>> p1.append([i3, i4]) 1839 >>> p2 = stream.Part() 1840 >>> p2.append([instrument.Flute(), instrument.Flute()]) 1841 >>> s2.insert(0, p1) 1842 >>> s2.insert(0, p2) 1843 >>> list(p1.getInstruments()) 1844 [<music21.instrument.Instrument 'Piccolo: '>, <music21.instrument.Piccolo 'Piccolo'>] 1845 >>> list(p2.getInstruments()) 1846 [<music21.instrument.Flute 'Flute'>, <music21.instrument.Flute 'Flute'>] 1847 >>> s2 = instrument.deduplicate(s2, inPlace=True) 1848 >>> list(p1.getInstruments()) 1849 [<music21.instrument.Piccolo 'Piccolo: Piccolo'>] 1850 >>> list(p2.getInstruments()) 1851 [<music21.instrument.Flute 'Flute'>] 1852 ''' 1853 if inPlace: 1854 returnObj = s 1855 else: 1856 returnObj = s.coreCopyAsDerivation('instrument.deduplicate') 1857 1858 if not returnObj.hasPartLikeStreams(): 1859 substreams = [returnObj] 1860 else: 1861 substreams = returnObj.getElementsByClass('Stream') 1862 1863 for sub in substreams: 1864 oTree = OffsetTree(sub.recurse().getElementsByClass('Instrument')) 1865 for o in oTree: 1866 if len(o) == 1: 1867 continue 1868 notNonePartNames = {i.partName for i in o if i.partName is not None} 1869 notNoneInstNames = {i.instrumentName for i in o if i.instrumentName is not None} 1870 1871 # Proceed only if 0-1 part name AND 0-1 instrument name candidates 1872 if len(notNonePartNames) > 1 or len(notNoneInstNames) > 1: 1873 continue 1874 1875 partName = None 1876 for pName in notNonePartNames: 1877 partName = pName 1878 instrumentName = None 1879 for iName in notNoneInstNames: 1880 instrumentName = iName 1881 1882 classes = {inst.__class__ for inst in o} 1883 # Case: 2+ instances of the same class 1884 if len(classes) == 1: 1885 surviving = None 1886 # Treat first as the surviving instance and standardize name 1887 for inst in o: 1888 inst.partName = partName 1889 inst.instrumentName = instrumentName 1890 surviving = inst 1891 break 1892 # Remove remaining instruments 1893 for inst in o: 1894 if inst is surviving: 1895 continue 1896 sub.remove(inst, recurse=True) 1897 # Case: mixed classes: standardize names 1898 # Remove instances of generic `Instrument` if found 1899 else: 1900 for inst in o: 1901 if inst.__class__ == Instrument: 1902 sub.remove(inst, recurse=True) 1903 else: 1904 inst.partName = partName 1905 inst.instrumentName = instrumentName 1906 1907 return returnObj 1908 1909 1910# For lookup by MIDI Program 1911# TODOs should be resolved with another mapping from MIDI program 1912# to .instrumentSound 1913MIDI_PROGRAM_TO_INSTRUMENT = { 1914 0: Piano, 1915 1: Piano, 1916 2: ElectricPiano, 1917 3: Piano, 1918 4: ElectricPiano, 1919 5: ElectricPiano, 1920 6: Harpsichord, 1921 7: Clavichord, 1922 8: Celesta, 1923 9: Glockenspiel, 1924 10: Glockenspiel, # TODO: MusicBox 1925 11: Vibraphone, 1926 12: Marimba, 1927 13: Xylophone, 1928 14: TubularBells, 1929 15: Dulcimer, 1930 16: ElectricOrgan, # TODO: instrumentSound 1931 17: ElectricOrgan, # TODO: instrumentSound 1932 18: ElectricOrgan, # TODO: instrumentSound 1933 19: PipeOrgan, 1934 20: ReedOrgan, 1935 21: Accordion, 1936 22: Harmonica, 1937 23: Accordion, # TODO: instrumentSound 1938 24: AcousticGuitar, # TODO: instrumentSound 1939 25: AcousticGuitar, # TODO: instrumentSound 1940 26: ElectricGuitar, # TODO: instrumentSound 1941 27: ElectricGuitar, # TODO: instrumentSound 1942 28: ElectricGuitar, # TODO: instrumentSound 1943 29: ElectricGuitar, # TODO: instrumentSound 1944 30: ElectricGuitar, # TODO: instrumentSound 1945 31: ElectricGuitar, # TODO: instrumentSound 1946 32: AcousticBass, 1947 33: ElectricBass, 1948 34: ElectricBass, # TODO: instrumentSound 1949 35: FretlessBass, 1950 36: ElectricBass, # TODO: instrumentSound 1951 37: ElectricBass, # TODO: instrumentSound 1952 38: ElectricBass, # TODO: instrumentSound 1953 39: ElectricBass, # TODO: instrumentSound 1954 40: Violin, 1955 41: Viola, 1956 42: Violoncello, 1957 43: Contrabass, 1958 44: StringInstrument, # TODO: instrumentSound 1959 45: StringInstrument, # TODO: instrumentSound 1960 46: Harp, 1961 47: Timpani, 1962 48: StringInstrument, # TODO: instrumentSound 1963 49: StringInstrument, # TODO: instrumentSound 1964 50: StringInstrument, # TODO: instrumentSound 1965 51: StringInstrument, # TODO: instrumentSound 1966 52: Choir, # TODO: instrumentSound 1967 53: Vocalist, # TODO: instrumentSound 1968 54: Vocalist, # TODO: instrumentSound 1969 55: Sampler, 1970 56: Trumpet, 1971 57: Trombone, 1972 58: Tuba, 1973 59: Trumpet, # TODO: instrumentSound 1974 60: Horn, 1975 61: BrassInstrument, # TODO: instrumentSound 1976 62: BrassInstrument, # TODO: instrumentSound 1977 63: BrassInstrument, # TODO: instrumentSound 1978 64: SopranoSaxophone, 1979 65: AltoSaxophone, 1980 66: TenorSaxophone, 1981 67: BaritoneSaxophone, 1982 68: Oboe, 1983 69: EnglishHorn, 1984 70: Bassoon, 1985 71: Clarinet, 1986 72: Piccolo, 1987 73: Flute, 1988 74: Recorder, 1989 75: PanFlute, 1990 76: PanFlute, # TODO 76: Bottle 1991 77: Shakuhachi, 1992 78: Whistle, 1993 79: Ocarina, 1994 80: Sampler, # TODO: all Sampler here and below: instrumentSound 1995 81: Sampler, 1996 82: Sampler, 1997 83: Sampler, 1998 84: Sampler, 1999 85: Sampler, 2000 86: Sampler, 2001 87: Sampler, 2002 88: Sampler, 2003 89: Sampler, 2004 90: Sampler, 2005 91: Sampler, 2006 92: Sampler, 2007 93: Sampler, 2008 94: Sampler, 2009 95: Sampler, 2010 96: Sampler, 2011 97: Sampler, 2012 98: Sampler, 2013 99: Sampler, 2014 100: Sampler, 2015 101: Sampler, 2016 102: Sampler, 2017 103: Sampler, 2018 104: Sitar, 2019 105: Banjo, 2020 106: Shamisen, 2021 107: Koto, 2022 108: Kalimba, 2023 109: Bagpipes, 2024 110: Violin, # TODO: instrumentSound 2025 111: Shehnai, 2026 112: Glockenspiel, # TODO 112: Tinkle Bell 2027 113: Agogo, 2028 114: SteelDrum, 2029 115: Woodblock, 2030 116: Taiko, 2031 117: TomTom, 2032 118: Sampler, # TODO: instrumentSound # debatable if this should be drum? 2033 119: Sampler, 2034 120: Sampler, 2035 121: Sampler, 2036 122: Sampler, 2037 123: Sampler, 2038 124: Sampler, 2039 125: Sampler, 2040 126: Sampler, 2041 127: Sampler 2042} 2043 2044def instrumentFromMidiProgram(number: int) -> Instrument: 2045 ''' 2046 Return the instrument with "number" as its assigned MIDI program. 2047 Notice any of the values 0-5 will return Piano. 2048 2049 Lookups are performed against `instrument.MIDI_PROGRAM_TO_INSTRUMENT`. 2050 2051 >>> instrument.instrumentFromMidiProgram(4) 2052 <music21.instrument.ElectricPiano 'Electric Piano'> 2053 >>> instrument.instrumentFromMidiProgram(21) 2054 <music21.instrument.Accordion 'Accordion'> 2055 >>> instrument.instrumentFromMidiProgram(500) 2056 Traceback (most recent call last): 2057 music21.exceptions21.InstrumentException: No instrument found for MIDI program 500 2058 >>> instrument.instrumentFromMidiProgram('43') 2059 Traceback (most recent call last): 2060 TypeError: Expected int, got <class 'str'> 2061 ''' 2062 2063 try: 2064 class_ = MIDI_PROGRAM_TO_INSTRUMENT[number] 2065 inst = class_() 2066 inst.midiProgram = number 2067 # TODO: if midiProgram in MIDI_PROGRAM_SOUND_MAP: 2068 # inst.instrumentSound = MIDI_PROGRAM_SOUND_MAP[midiProgram] 2069 except KeyError as e: 2070 if not isinstance(number, int): 2071 raise TypeError(f'Expected int, got {type(number)}') from e 2072 raise InstrumentException(f'No instrument found for MIDI program {number}') from e 2073 return inst 2074 2075def partitionByInstrument(streamObj): 2076 # noinspection PyShadowingNames 2077 ''' 2078 Given a single Stream, or a Score or similar multi-part structure, 2079 partition into a Part for each unique Instrument, joining events 2080 possibly from different parts. 2081 2082 >>> p1 = converter.parse("tinynotation: 4/4 c4 d e f g a b c' c1") 2083 >>> p2 = converter.parse("tinynotation: 4/4 C#4 D# E# F# G# A# B# c# C#1") 2084 2085 >>> p1.getElementsByClass('Measure')[0].insert(0.0, instrument.Piccolo()) 2086 >>> p1.getElementsByClass('Measure')[0].insert(2.0, instrument.AltoSaxophone()) 2087 >>> p1.getElementsByClass('Measure')[1].insert(3.0, instrument.Piccolo()) 2088 2089 >>> p2.getElementsByClass('Measure')[0].insert(0.0, instrument.Trombone()) 2090 >>> p2.getElementsByClass('Measure')[0].insert(3.0, instrument.Piccolo()) # not likely... 2091 >>> p2.getElementsByClass('Measure')[1].insert(1.0, instrument.Trombone()) 2092 2093 >>> s = stream.Score() 2094 >>> s.insert(0, p1) 2095 >>> s.insert(0, p2) 2096 >>> s.show('text') 2097 {0.0} <music21.stream.Part ...> 2098 {0.0} <music21.stream.Measure 1 offset=0.0> 2099 {0.0} <music21.instrument.Piccolo 'Piccolo'> 2100 {0.0} <music21.clef.TrebleClef> 2101 {0.0} <music21.meter.TimeSignature 4/4> 2102 {0.0} <music21.note.Note C> 2103 {1.0} <music21.note.Note D> 2104 {2.0} <music21.instrument.AltoSaxophone 'Alto Saxophone'> 2105 {2.0} <music21.note.Note E> 2106 {3.0} <music21.note.Note F> 2107 {4.0} <music21.stream.Measure 2 offset=4.0> 2108 {0.0} <music21.note.Note G> 2109 {1.0} <music21.note.Note A> 2110 {2.0} <music21.note.Note B> 2111 {3.0} <music21.instrument.Piccolo 'Piccolo'> 2112 {3.0} <music21.note.Note C> 2113 {8.0} <music21.stream.Measure 3 offset=8.0> 2114 {0.0} <music21.note.Note C> 2115 {4.0} <music21.bar.Barline type=final> 2116 {0.0} <music21.stream.Part ...> 2117 {0.0} <music21.stream.Measure 1 offset=0.0> 2118 {0.0} <music21.instrument.Trombone 'Trombone'> 2119 {0.0} <music21.clef.BassClef> 2120 {0.0} <music21.meter.TimeSignature 4/4> 2121 {0.0} <music21.note.Note C#> 2122 {1.0} <music21.note.Note D#> 2123 {2.0} <music21.note.Note E#> 2124 {3.0} <music21.instrument.Piccolo 'Piccolo'> 2125 {3.0} <music21.note.Note F#> 2126 {4.0} <music21.stream.Measure 2 offset=4.0> 2127 {0.0} <music21.note.Note G#> 2128 {1.0} <music21.instrument.Trombone 'Trombone'> 2129 {1.0} <music21.note.Note A#> 2130 {2.0} <music21.note.Note B#> 2131 {3.0} <music21.note.Note C#> 2132 {8.0} <music21.stream.Measure 3 offset=8.0> 2133 {0.0} <music21.note.Note C#> 2134 {4.0} <music21.bar.Barline type=final> 2135 2136 >>> s2 = instrument.partitionByInstrument(s) 2137 >>> len(s2.parts) 2138 3 2139 2140 # TODO: this step might not be necessary... 2141 >>> for p in s2.parts: 2142 ... p.makeRests(fillGaps=True, inPlace=True) 2143 2144 # TODO: this step SHOULD not be necessary (.template())... 2145 2146 >>> for p in s2.parts: 2147 ... p.makeMeasures(inPlace=True) 2148 ... p.makeTies(inPlace=True) 2149 2150 >>> s2.show('text') 2151 {0.0} <music21.stream.Part Piccolo> 2152 {0.0} <music21.stream.Measure 1 offset=0.0> 2153 {0.0} <music21.instrument.Piccolo 'Piccolo'> 2154 {0.0} <music21.clef.TrebleClef> 2155 {0.0} <music21.meter.TimeSignature 4/4> 2156 {0.0} <music21.note.Note C> 2157 {1.0} <music21.note.Note D> 2158 {2.0} <music21.note.Rest quarter> 2159 {3.0} <music21.note.Note F#> 2160 {4.0} <music21.stream.Measure 2 offset=4.0> 2161 {0.0} <music21.note.Note G#> 2162 {1.0} <music21.note.Rest half> 2163 {3.0} <music21.note.Note C> 2164 {8.0} <music21.stream.Measure 3 offset=8.0> 2165 {0.0} <music21.note.Note C> 2166 {4.0} <music21.bar.Barline type=final> 2167 {0.0} <music21.stream.Part Alto Saxophone> 2168 {0.0} <music21.stream.Measure 1 offset=0.0> 2169 {0.0} <music21.instrument.AltoSaxophone 'Alto Saxophone'> 2170 {0.0} <music21.clef.TrebleClef> 2171 {0.0} <music21.meter.TimeSignature 4/4> 2172 {0.0} <music21.note.Rest half> 2173 {2.0} <music21.note.Note E> 2174 {3.0} <music21.note.Note F> 2175 {4.0} <music21.stream.Measure 2 offset=4.0> 2176 {0.0} <music21.note.Note G> 2177 {1.0} <music21.note.Note A> 2178 {2.0} <music21.note.Note B> 2179 {3.0} <music21.bar.Barline type=final> 2180 {0.0} <music21.stream.Part Trombone> 2181 {0.0} <music21.stream.Measure 1 offset=0.0> 2182 {0.0} <music21.instrument.Trombone 'Trombone'> 2183 {0.0} <music21.clef.BassClef> 2184 {0.0} <music21.meter.TimeSignature 4/4> 2185 {0.0} <music21.note.Note C#> 2186 {1.0} <music21.note.Note D#> 2187 {2.0} <music21.note.Note E#> 2188 {3.0} <music21.note.Rest quarter> 2189 {4.0} <music21.stream.Measure 2 offset=4.0> 2190 {0.0} <music21.note.Rest quarter> 2191 {1.0} <music21.note.Note A#> 2192 {2.0} <music21.note.Note B#> 2193 {3.0} <music21.note.Note C#> 2194 {8.0} <music21.stream.Measure 3 offset=8.0> 2195 {0.0} <music21.note.Note C#> 2196 {4.0} <music21.bar.Barline type=final> 2197 2198 TODO: parts should be in Score Order. Coincidence that this almost works. 2199 TODO: use proper recursion to make a copy of the stream. 2200 TODO: final barlines should be aligned. 2201 ''' 2202 if not streamObj.hasPartLikeStreams(): 2203 # place in a score for uniform operations 2204 s = stream.Score() 2205 s.insert(0, streamObj.flatten()) 2206 else: 2207 s = stream.Score() 2208 # append flat parts 2209 for sub in streamObj.getElementsByClass(stream.Stream): 2210 s.insert(0, sub.flatten()) 2211 2212 # first, let's extend the duration of each instrument to match stream 2213 for sub in s.getElementsByClass(stream.Stream): 2214 sub.extendDuration('Instrument', inPlace=True) 2215 2216 # first, find all unique instruments 2217 instrumentIterator = s.recurse().getElementsByClass(Instrument) 2218 if not instrumentIterator: 2219 # TODO(msc): v7 return s. 2220 return None # no partition is available 2221 2222 names = OrderedDict() # store unique names 2223 for instrumentObj in instrumentIterator: 2224 # matching here by instrument name 2225 if instrumentObj.instrumentName not in names: 2226 names[instrumentObj.instrumentName] = {'Instrument': instrumentObj} 2227 # just store one instance 2228 2229 # create a return object that has a part for each instrument 2230 post = stream.Score() 2231 for iName in names: 2232 p = stream.Part() 2233 p.id = iName 2234 # add the instrument instance 2235 p.insert(0, names[iName]['Instrument']) 2236 # store a handle to this part 2237 names[iName]['Part'] = p 2238 post.insert(0, p) 2239 2240 # iterate over flat sources; get events within each defined instrument 2241 # add to corresponding part 2242 for el in s: 2243 if not el.isStream: 2244 post.insert(el.offset, el) 2245 2246 subStream = el 2247 for i in subStream.getElementsByClass(Instrument): 2248 start = i.offset 2249 # duration will have been set with sub.extendDuration above 2250 end = i.offset + i.duration.quarterLength 2251 # get destination Part 2252 p = names[i.instrumentName]['Part'] 2253 2254 coll = subStream.getElementsByOffset( 2255 start, 2256 end, 2257 # do not include elements that start at the end 2258 includeEndBoundary=False, 2259 mustFinishInSpan=False, 2260 mustBeginInSpan=True 2261 ) 2262 # add to part at original offset 2263 # do not gather instrument 2264 for e in coll.getElementsNotOfClass(Instrument): 2265 try: 2266 p.insert(subStream.elementOffset(e), e) 2267 except stream.StreamException: 2268 pass 2269 # it is possible to enter an element twice because the getElementsByOffset 2270 # might return something twice if it's at the same offset as the 2271 # instrument switch... 2272 2273 for inst in post.recurse().getElementsByClass(Instrument): 2274 inst.duration.quarterLength = 0 2275 return post 2276 2277 2278def _combinations(instrumentString): 2279 ''' 2280 find all combinations of instrumentString. Remove all punctuation. 2281 ''' 2282 sampleList = instrumentString.split() 2283 allComb = [] 2284 for size in range(1, len(sampleList) + 1): 2285 for i in range(len(sampleList) - size + 1): 2286 allComb.append(' '.join(sampleList[i:i + size])) 2287 return allComb 2288 2289 2290def fromString(instrumentString): 2291 ''' 2292 Given a string with instrument content (from an orchestral score 2293 for example), attempts to return an appropriate 2294 :class:`~music21.instrument.Instrument`. 2295 2296 >>> from music21 import instrument 2297 >>> t1 = instrument.fromString('Clarinet 2 in A') 2298 >>> t1 2299 <music21.instrument.Clarinet 'Clarinet 2 in A'> 2300 >>> t1.transposition 2301 <music21.interval.Interval m-3> 2302 2303 >>> t2 = instrument.fromString('Clarinetto 3') 2304 >>> t2 2305 <music21.instrument.Clarinet 'Clarinetto 3'> 2306 2307 >>> t3 = instrument.fromString('flauto 2') 2308 >>> t3 2309 <music21.instrument.Flute 'flauto 2'> 2310 2311 2312 Excess information is ignored, and the useful information can be extracted 2313 correctly as long as it's sequential. 2314 2315 >>> t4 = instrument.fromString('I <3 music saxofono tenore go beavers') 2316 >>> t4 2317 <music21.instrument.TenorSaxophone 'I <3 music saxofono tenore go beavers'> 2318 2319 Some more demos: 2320 2321 >>> t5 = instrument.fromString('Bb Clarinet') 2322 >>> t5 2323 <music21.instrument.Clarinet 'Bb Clarinet'> 2324 >>> t5.transposition 2325 <music21.interval.Interval M-2> 2326 2327 >>> t6 = instrument.fromString('Clarinet in B-flat') 2328 >>> t5.__class__ == t6.__class__ 2329 True 2330 2331 >>> t5.transposition == t6.transposition 2332 True 2333 2334 >>> t7 = instrument.fromString('B-flat Clarinet.') 2335 >>> t5.__class__ == t7.__class__ and t5.transposition == t7.transposition 2336 True 2337 2338 >>> t8 = instrument.fromString('Eb Clarinet') 2339 >>> t5.__class__ == t8.__class__ 2340 True 2341 >>> t8.transposition 2342 <music21.interval.Interval m3> 2343 2344 2345 Note that because of the ubiquity of B-flat clarinets and trumpets, and the 2346 rareness of B-natural forms of those instruments, this gives a B-flat, not 2347 B-natural clarinet, using the German form: 2348 2349 >>> t9 = instrument.fromString('Klarinette in B.') 2350 >>> t9 2351 <music21.instrument.Clarinet 'Klarinette in B.'> 2352 >>> t9.transposition 2353 <music21.interval.Interval M-2> 2354 2355 Use "H" or "b-natural" to get an instrument in B-major. Or donate one to me 2356 and I'll change this back! 2357 2358 2359 Finally, standard abbreviations are acceptable: 2360 2361 >>> t10 = instrument.fromString('Cl in B-flat') 2362 >>> t10 2363 <music21.instrument.Clarinet 'Cl in B-flat'> 2364 >>> t10.transposition 2365 <music21.interval.Interval M-2> 2366 2367 This should work with or without a terminal period (for both 'Cl' and 'Cl.'): 2368 2369 >>> t11 = instrument.fromString('Cl. in B-flat') 2370 >>> t11.__class__ == t10.__class__ 2371 True 2372 2373 2374 Previously an exact instrument name was not always working: 2375 2376 >>> instrument.fromString('Flute') 2377 <music21.instrument.Flute 'Flute'> 2378 2379 This common MIDI instrument was not previously working: 2380 2381 >>> instrument.fromString('Choir (Aahs)') 2382 <music21.instrument.Choir 'Choir (Aahs)'> 2383 2384 ''' 2385 # pylint: disable=undefined-variable 2386 from music21.languageExcerpts import instrumentLookup 2387 2388 instrumentStringOrig = instrumentString 2389 instrumentString = instrumentString.replace('.', ' ') # sic, before removePunctuation 2390 instrumentString = common.removePunctuation(instrumentString) 2391 allCombinations = _combinations(instrumentString) 2392 # First task: Find the best instrument. 2393 bestInstClass = None 2394 bestInstrument = None 2395 bestName = None 2396 2397 for substring in allCombinations: 2398 substring = substring.lower() 2399 try: 2400 if substring in instrumentLookup.bestNameToInstrumentClass: 2401 englishName = substring 2402 else: 2403 englishName = instrumentLookup.allToBestName[substring] 2404 className = instrumentLookup.bestNameToInstrumentClass[englishName] 2405 2406 # This would be unsafe... 2407 thisInstClass = globals()[className] 2408 thisInstClassParentClasses = [parentCls.__name__ for parentCls in thisInstClass.mro()] 2409 # if not for this... 2410 if ('Instrument' not in thisInstClassParentClasses 2411 or 'Music21Object' not in thisInstClassParentClasses): 2412 # little bit of security against calling another global... 2413 raise KeyError 2414 2415 thisInstrument = thisInstClass() 2416 thisBestName = thisInstrument.bestName().lower() 2417 if (bestInstClass is None 2418 or len(thisBestName.split()) >= len(bestName.split()) 2419 and not issubclass(bestInstClass, thisInstClass)): 2420 # priority is also given to same length instruments which fall later 2421 # on in the string (i.e. Bb Piccolo Trumpet) 2422 bestInstClass = thisInstClass 2423 bestInstrument = thisInstrument 2424 bestInstrument.instrumentName = instrumentStringOrig 2425 bestName = thisBestName 2426 except KeyError: 2427 pass 2428 if bestInstClass is None: 2429 raise InstrumentException( 2430 f'Could not match string with instrument: {instrumentStringOrig}') 2431 if bestName not in instrumentLookup.transposition: 2432 return bestInstrument 2433 2434 # A transposition table is defined for the instrument. 2435 # Second task: Determine appropriate transposition (if any) 2436 for substring in allCombinations: 2437 try: 2438 bestPitch = instrumentLookup.pitchFullNameToName[substring.lower()] 2439 bestInterval = instrumentLookup.transposition[bestName][bestPitch] 2440 bestInstrument.transposition = interval.Interval(bestInterval) 2441 break 2442 except KeyError: 2443 pass 2444 return bestInstrument 2445 2446 2447# ------------------------------------------------------------------------------ 2448class TestExternal(unittest.TestCase): 2449 pass 2450 2451 2452class Test(unittest.TestCase): 2453 2454 def testCopyAndDeepcopy(self): 2455 '''Test copying all objects defined in this module 2456 ''' 2457 import types 2458 for part in sys.modules[self.__module__].__dict__.keys(): 2459 match = False 2460 for skip in ['_', '__', 'Test', 'Exception']: 2461 if part.startswith(skip) or part.endswith(skip): 2462 match = True 2463 if match: 2464 continue 2465 name = getattr(sys.modules[self.__module__], part) 2466 # noinspection PyTypeChecker 2467 if callable(name) and not isinstance(name, types.FunctionType): 2468 try: # see if obj can be made w/ args 2469 obj = name() 2470 except TypeError: # pragma: no cover 2471 continue 2472 i = copy.copy(obj) 2473 j = copy.deepcopy(obj) 2474 2475 def testMusicXMLExport(self): 2476 s1 = stream.Stream() 2477 i1 = Violin() 2478 i1.partName = 'test' 2479 s1.append(i1) 2480 s1.repeatAppend(note.Note(), 10) 2481 # s.show() 2482 2483 s2 = stream.Stream() 2484 i2 = Piano() 2485 i2.partName = 'test2' 2486 s2.append(i2) 2487 s2.repeatAppend(note.Note('g4'), 10) 2488 2489 s3 = stream.Score() 2490 s3.insert(0, s1) 2491 s3.insert(0, s2) 2492 2493 # s3.show() 2494 2495 def testPartitionByInstrumentA(self): 2496 from music21 import instrument 2497 2498 # basic case of instruments in Parts 2499 s = stream.Score() 2500 p1 = stream.Part() 2501 p1.append(instrument.Piano()) 2502 2503 p2 = stream.Part() 2504 p2.append(instrument.Piccolo()) 2505 s.insert(0, p1) 2506 s.insert(0, p2) 2507 2508 post = instrument.partitionByInstrument(s) 2509 self.assertEqual(len(post), 2) 2510 self.assertEqual(len(post.flatten().getElementsByClass('Instrument')), 2) 2511 2512 # post.show('t') 2513 2514 # one Stream with multiple instruments 2515 s = stream.Stream() 2516 s.insert(0, instrument.PanFlute()) 2517 s.insert(20, instrument.ReedOrgan()) 2518 2519 post = instrument.partitionByInstrument(s) 2520 self.assertEqual(len(post), 2) 2521 self.assertEqual(len(post.flatten().getElementsByClass('Instrument')), 2) 2522 # post.show('t') 2523 2524 def testPartitionByInstrumentB(self): 2525 from music21 import instrument 2526 2527 # basic case of instruments in Parts 2528 s = stream.Score() 2529 p1 = stream.Part() 2530 p1.append(instrument.Piano()) 2531 p1.repeatAppend(note.Note(), 6) 2532 2533 p2 = stream.Part() 2534 p2.append(instrument.Piccolo()) 2535 p2.repeatAppend(note.Note(), 12) 2536 s.insert(0, p1) 2537 s.insert(0, p2) 2538 2539 post = instrument.partitionByInstrument(s) 2540 self.assertEqual(len(post), 2) 2541 self.assertEqual(len(post.flatten().getElementsByClass('Instrument')), 2) 2542 self.assertEqual(len(post.parts[0].notes), 6) 2543 self.assertEqual(len(post.parts[1].notes), 12) 2544 2545 def testPartitionByInstrumentC(self): 2546 from music21 import instrument 2547 2548 # basic case of instruments in Parts 2549 s = stream.Score() 2550 p1 = stream.Part() 2551 p1.append(instrument.Piano()) 2552 p1.repeatAppend(note.Note('a'), 6) 2553 # will go in next available offset 2554 p1.append(instrument.AcousticGuitar()) 2555 p1.repeatAppend(note.Note('b'), 3) 2556 2557 p2 = stream.Part() 2558 p2.append(instrument.Piccolo()) 2559 p2.repeatAppend(note.Note('c'), 2) 2560 p2.append(instrument.Flute()) 2561 p2.repeatAppend(note.Note('d'), 4) 2562 2563 s.insert(0, p1) 2564 s.insert(0, p2) 2565 2566 post = instrument.partitionByInstrument(s) 2567 self.assertEqual(len(post), 4) # 4 instruments 2568 self.assertEqual(len(post.flatten().getElementsByClass('Instrument')), 4) 2569 self.assertEqual(post.parts[0].getInstrument().instrumentName, 'Piano') 2570 self.assertEqual(len(post.parts[0].notes), 6) 2571 self.assertEqual(post.parts[1].getInstrument().instrumentName, 'Acoustic Guitar') 2572 self.assertEqual(len(post.parts[1].notes), 3) 2573 self.assertEqual(post.parts[2].getInstrument().instrumentName, 'Piccolo') 2574 self.assertEqual(len(post.parts[2].notes), 2) 2575 self.assertEqual(post.parts[3].getInstrument().instrumentName, 'Flute') 2576 self.assertEqual(len(post.parts[3].notes), 4) 2577 2578 # environLocal.printDebug(['post processing']) 2579 # post.show('t') 2580 2581 def testPartitionByInstrumentD(self): 2582 from music21 import instrument 2583 2584 # basic case of instruments in Parts 2585 s = stream.Score() 2586 p1 = stream.Part() 2587 p1.append(instrument.Piano()) 2588 p1.repeatAppend(note.Note('a'), 6) 2589 # will go in next available offset 2590 p1.append(instrument.AcousticGuitar()) 2591 p1.repeatAppend(note.Note('b'), 3) 2592 p1.append(instrument.Piano()) 2593 p1.repeatAppend(note.Note('e'), 5) 2594 2595 p2 = stream.Part() 2596 p2.append(instrument.Piccolo()) 2597 p2.repeatAppend(note.Note('c'), 2) 2598 p2.append(instrument.Flute()) 2599 p2.repeatAppend(note.Note('d'), 4) 2600 p2.append(instrument.Piano()) 2601 p2.repeatAppend(note.Note('f'), 1) 2602 2603 s.insert(0, p1) 2604 s.insert(0, p2) 2605 2606 post = instrument.partitionByInstrument(s) 2607 self.assertEqual(len(post), 4) # 4 instruments 2608 self.assertEqual(len(post.flatten().getElementsByClass('Instrument')), 4) 2609 # piano spans are joined together 2610 self.assertEqual(post.parts[0].getInstrument().instrumentName, 'Piano') 2611 self.assertEqual(len(post.parts[0].notes), 12) 2612 2613 self.assertEqual([n.offset for n in post.parts[0].notes], 2614 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 9.0, 10.0, 11.0, 12.0, 13.0]) 2615 2616 # environLocal.printDebug(['post processing']) 2617 # post.show('t') 2618 2619 def testPartitionByInstrumentE(self): 2620 from music21 import instrument 2621 2622 # basic case of instruments in Parts 2623 # s = stream.Score() 2624 p1 = stream.Part() 2625 p1.append(instrument.Piano()) 2626 p1.repeatAppend(note.Note('a'), 6) 2627 # will go in next available offset 2628 p1.append(instrument.AcousticGuitar()) 2629 p1.repeatAppend(note.Note('b'), 3) 2630 p1.append(instrument.Piano()) 2631 p1.repeatAppend(note.Note('e'), 5) 2632 2633 p1.append(instrument.Piccolo()) 2634 p1.repeatAppend(note.Note('c'), 2) 2635 p1.append(instrument.Flute()) 2636 p1.repeatAppend(note.Note('d'), 4) 2637 p1.append(instrument.Piano()) 2638 p1.repeatAppend(note.Note('f'), 1) 2639 2640 s = p1 2641 2642 post = instrument.partitionByInstrument(s) 2643 self.assertEqual(len(post), 4) # 4 instruments 2644 self.assertEqual(len(post.flatten().getElementsByClass('Instrument')), 4) 2645 # piano spans are joined together 2646 self.assertEqual(post.parts[0].getInstrument().instrumentName, 'Piano') 2647 2648 self.assertEqual(len(post.parts[0].notes), 12) 2649 offsetList = [] 2650 ppn = post.parts[0].notes 2651 for n in ppn: 2652 offsetList.append(n.offset) 2653 2654 self.assertEqual(offsetList, 2655 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 9.0, 10.0, 11.0, 12.0, 13.0, 20.0]) 2656 2657 def testPartitionByInstrumentF(self): 2658 from music21 import instrument 2659 2660 s1 = stream.Stream() 2661 s1.append(instrument.AcousticGuitar()) 2662 s1.append(note.Note()) 2663 s1.append(instrument.Tuba()) 2664 s1.append(note.Note()) 2665 2666 post = instrument.partitionByInstrument(s1) 2667 self.assertEqual(len(post), 2) # 4 instruments 2668 2669 # def testPartitionByInstrumentDocTest(self): 2670 # ''' 2671 # For debugging the doctest. 2672 # ''' 2673 # from music21 import instrument, converter, stream 2674 # p1 = converter.parse("tinynotation: 4/4 c4 d e f g a b c' c1") 2675 # p2 = converter.parse("tinynotation: 4/4 C#4 D# E# F# G# A# B# c# C#1") 2676 # 2677 # p1.getElementsByClass('Measure')[0].insert(0.0, instrument.Piccolo()) 2678 # p1.getElementsByClass('Measure')[0].insert(2.0, instrument.AltoSaxophone()) 2679 # p1.getElementsByClass('Measure')[1].insert(3.0, instrument.Piccolo()) 2680 # 2681 # p2.getElementsByClass('Measure')[0].insert(0.0, instrument.Trombone()) 2682 # p2.getElementsByClass('Measure')[0].insert(3.0, instrument.Piccolo()) # not likely... 2683 # p2.getElementsByClass('Measure')[1].insert(1.0, instrument.Trombone()) 2684 # 2685 # s = stream.Score() 2686 # s.insert(0, p1) 2687 # s.insert(0, p2) 2688 # s2 = instrument.partitionByInstrument(s) 2689 # for p in s2.parts: 2690 # p.makeRests(fillGaps=True, inPlace=True) 2691 2692 2693# ------------------------------------------------------------------------------ 2694# define presented order in documentation 2695_DOC_ORDER = [Instrument] 2696 2697 2698if __name__ == '__main__': 2699 # sys.arg test options will be used in mainTest() 2700 import music21 2701 music21.mainTest(Test) 2702