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