Source code for music21.midi.percussion

# ------------------------------------------------------------------------------
# Name:         midi/percussion.py
# Purpose:      music21 classes for representing pitches
#
# Authors:      Michael Scott Asato Cuthbert
#               Ben Houge
#
# Copyright:    Copyright © 2012, 2017 Michael Scott Asato Cuthbert
# License:      BSD, see license.txt
# ------------------------------------------------------------------------------
from __future__ import annotations

import typing as t
import unittest

from music21 import pitch
from music21 import exceptions21
from music21 import instrument


class MIDIPercussionException(exceptions21.Music21Exception):
    pass


[docs] class PercussionMapper: ''' PercussionMapper provides tools to convert between 0-indexed MIDI pitches and music21 instruments, based on the official General MIDI Level 1 Percussion Key Map. This mapping is conventionally applied to MIDI channel 10; see https://midi.org/general-midi-level-1 for more info. Give me the instrument that corresponds to MIDI note 58! >>> pm = midi.percussion.PercussionMapper() >>> pm.reverseInstrumentMapping[58] <class 'music21.instrument.Vibraslap'> That's right, vibraslap. But you're better off using the midiPitchToInstrument() method below! ''' i = instrument reverseInstrumentMapping = {35: i.BassDrum, # Acoustic Bass Drum 36: i.BassDrum, # Bass Drum 1 37: i.SnareDrum, # Side Stick 38: i.SnareDrum, # Acoustic Snare # 39: i.Hand Clap, 40: i.SnareDrum, # Electric Snare 41: i.TomTom, # Low Floor Tom 42: i.HiHatCymbal, # Closed Hi Hat 43: i.TomTom, # High Floor Tom 44: i.HiHatCymbal, # Pedal Hi-Hat 45: i.TomTom, # Low Tom 46: i.HiHatCymbal, # Open Hi-Hat 47: i.TomTom, # Low-Mid Tom 48: i.TomTom, # Hi-Mid Tom 49: i.CrashCymbals, # Crash Cymbal 1 50: i.TomTom, # High Tom # 51: i.Ride Cymbal 1, # 52: i.Chinese Cymbal, # 53: i.Ride Bell, 54: i.Tambourine, # 55: i.Splash Cymbal, 56: i.Cowbell, 57: i.CrashCymbals, # Crash Cymbal 2 58: i.Vibraslap, # 59: i.Ride Cymbal 2, 60: i.BongoDrums, # Hi Bongo 61: i.BongoDrums, # Low Bongo 62: i.CongaDrum, # Mute Hi Conga 63: i.CongaDrum, # Open Hi Conga 64: i.CongaDrum, # Low Conga 65: i.Timbales, # High Timbale 66: i.Timbales, # Low Timbale 67: i.Agogo, # High Agogo 68: i.Agogo, # Low Agogo # 69: i.Cabasa, 70: i.Maracas, 71: i.Whistle, # Short Whistle 72: i.Whistle, # Long Whistle # 73: i.Short Guiro, # 74: i.Long Guiro, # 75: i.Claves, 76: i.Woodblock, # Hi Wood Block 77: i.Woodblock, # Low Wood Block # 78: i.Mute Cuica, # 79: i.Open Cuica, 80: i.Triangle, # Mute Triangle 81: i.Triangle, # Open Triangle } # MIDI percussion mappings from https://midi.org/general-midi-level-1 download at # https://drive.google.com/file/d/1TTM2HI2JAp_XiTJYEJ8gPNvuJpIjJiTP/view?pli=1 # formerly at: # https://www.midi.org/specifications/item/gm-level-1-sound-set
[docs] def midiPitchToInstrument(self, midiPitch: int|pitch.Pitch) -> instrument.Instrument: ''' Takes a pitch.Pitch object or int ranging from 0-127 and returns the corresponding instrument in the GM Percussion Map. >>> pm = midi.percussion.PercussionMapper() >>> cowPitch = pitch.Pitch(56) >>> cowbell = pm.midiPitchToInstrument(cowPitch) >>> cowbell <music21.instrument.Cowbell 'Cowbell'> Or it can just take an integer (representing MIDI note) for the pitch instead: >>> moreCowbell = pm.midiPitchToInstrument(56) >>> moreCowbell <music21.instrument.Cowbell 'Cowbell'> The standard GM Percussion list goes from 35 to 81; pitches outside this range raise an exception. >>> bassDrum1Pitch = pitch.Pitch('B-1') >>> pm.midiPitchToInstrument(bassDrum1Pitch) Traceback (most recent call last): music21.midi.percussion.MIDIPercussionException: 34 does not map to a valid instrument! Also, certain GM instruments do not have corresponding music21 instruments, so at present they also raise an exception. >>> cabasaPitch = 69 >>> pm.midiPitchToInstrument(cabasaPitch) Traceback (most recent call last): music21.midi.percussion.MIDIPercussionException: 69 does not map to a valid instrument! Some music21 Instruments have more than one MidiPitch. In this case you'll get the same Instrument object but with a different modifier >>> acousticBassDrumPitch = pitch.Pitch(35) >>> acousticBDInstrument = pm.midiPitchToInstrument(acousticBassDrumPitch) >>> acousticBDInstrument <music21.instrument.BassDrum 'Bass Drum'> >>> acousticBDInstrument.modifier 'acoustic' >>> oneBassDrumPitch = pitch.Pitch(36) >>> oneBDInstrument = pm.midiPitchToInstrument(oneBassDrumPitch) >>> oneBDInstrument <music21.instrument.BassDrum 'Bass Drum'> >>> oneBDInstrument.modifier '1' ''' if isinstance(midiPitch, int): midiNumber = midiPitch else: midiNumber = midiPitch.midi if midiNumber not in self.reverseInstrumentMapping: raise MIDIPercussionException(f'{midiNumber!r} does not map to a valid instrument!') midiInstrument = self.reverseInstrumentMapping[midiNumber] midiInstrumentObject = midiInstrument() if (midiInstrumentObject.inGMPercMap is True and hasattr(midiInstrumentObject, '_percMapPitchToModifier')): midiUnpitchedPercussion = t.cast( instrument.UnpitchedPercussion, midiInstrumentObject) if midiNumber in midiUnpitchedPercussion._percMapPitchToModifier: modifier = midiUnpitchedPercussion._percMapPitchToModifier[midiNumber] midiUnpitchedPercussion.modifier = modifier return midiInstrumentObject
[docs] def midiInstrumentToPitch(self, midiInstrument: instrument.Instrument) -> pitch.Pitch: ''' Takes an instrument.Instrument object and returns a pitch object with the corresponding 1-indexed MIDI note, according to the GM Percussion Map. >>> pm = midi.percussion.PercussionMapper() >>> myCow = instrument.Cowbell() >>> cowPitch = pm.midiInstrumentToPitch(myCow) >>> cowPitch.midi 56 Note that cowPitch is an actual pitch.Pitch object even though it's meaningless! >>> cowPitch <music21.pitch.Pitch G#3> If the instrument does not have an equivalent in the GM Percussion Map, return an Exception: >>> myBagpipes = instrument.Bagpipes() >>> pipePitch = pm.midiInstrumentToPitch(myBagpipes) Traceback (most recent call last): music21.midi.percussion.MIDIPercussionException: <music21.instrument.Bagpipes 'Bagpipes'> is not in the GM Percussion Map! ''' if not hasattr(midiInstrument, 'inGMPercMap') or midiInstrument.inGMPercMap is False: raise MIDIPercussionException(f'{midiInstrument!r} is not in the GM Percussion Map!') percInstrument = t.cast(instrument.Percussion, midiInstrument) midiPitch = t.cast(int, percInstrument.percMapPitch) pitchObject = pitch.Pitch() pitchObject.midi = midiPitch return pitchObject
_DOC_ORDER = [midiInstrumentToPitch, midiPitchToInstrument]
class Test(unittest.TestCase): def testCopyAndDeepcopy(self) -> None: from music21.test.commonTest import testCopyAll testCopyAll(self, globals()) # ------------------------------------------------------------------------------ # define presented order in documentation _DOC_ORDER = [PercussionMapper] if __name__ == '__main__': import music21 music21.mainTest(Test)