music21.midi.translate

Module to translate MIDI data to music21 Streams and vice versa. Note that quantization of notes takes place in the quantize() method not here.

Functions

music21.midi.translate.streamToMidiFile(inputM21: stream.Stream, *, addStartDelay: bool = False, acceptableChannelList: list[int] | None = None) midi.MidiFile

Converts a Stream hierarchy into a MidiFile object.

>>> s = stream.Stream()
>>> n = note.Note('g#')
>>> n.quarterLength = 0.5
>>> s.repeatAppend(n, 4)
>>> mf = midi.translate.streamToMidiFile(s)
>>> mf.tracks[0].index  # Track 0: conductor track
0
>>> len(mf.tracks[1].events)  # Track 1: music track
22

From here, you can call mf.writestr() to get the actual file info.

>>> sc = scale.PhrygianScale('g')
>>> s = stream.Stream()
>>> x=[s.append(note.Note(sc.pitchFromDegree(i % 11), quarterLength=0.25)) for i in range(60)]
>>> mf = midi.translate.streamToMidiFile(s)
>>> mf.open('/Volumes/disc/_scratch/midi.mid', 'wb')
>>> mf.write()
>>> mf.close()

See channelInstrumentData() for documentation on acceptableChannelList.

music21.midi.translate.midiFileToStream(mf: midi.MidiFile, *, inputM21=None, quantizePost=True, **keywords)

Note: this is NOT the normal way to read a MIDI file. The best way is generally:

score = converter.parse(‘path/to/file.mid’)

Convert a MidiFile object to a Stream object.

The inputM21 object can specify an existing Stream (or Stream subclass) to fill.

Keywords to control quantization: quantizePost controls whether to quantize the output. (Default: True) quarterLengthDivisors allows for overriding the default quantization units in defaults.quantizationQuarterLengthDivisors. (Default: (4, 3)).

>>> import os
>>> fp = common.getSourceFilePath() / 'midi' / 'testPrimitive' / 'test05.mid'
>>> mf = midi.MidiFile()
>>> mf.open(fp)
>>> mf.read()
>>> mf.close()
>>> len(mf.tracks)
1
>>> s = midi.translate.midiFileToStream(mf)
>>> s
<music21.stream.Score ...>
>>> len(s.flatten().notesAndRests)
14
  • Changed in v8: inputM21 and quantizePost are keyword only.

music21.midi.translate.assignPacketsToChannels(packets, channelByInstrument=None, channelsDynamic=None, initTrackIdToChannelMap=None)

Given a list of packets, assign each to a channel.

Do each track one at time, based on the track id.

Shift to different channels if a pitch bend is necessary.

Keep track of which channels are available. Need to insert a program change in the empty channel too, based on last instrument.

Insert pitch bend messages as well, one for start of event, one for end of event.

packets is a list of packets. channelByInstrument should be a dictionary. channelsDynamic should be a list. initTrackIdToChannelMap should be a dictionary.

music21.midi.translate.channelInstrumentData(s: Stream, acceptableChannelList: list[int] | None = None) tuple[dict[int | None, int], list[int]]

Read through Stream s and finding instruments in it, return a 2-tuple, the first a dictionary mapping MIDI program numbers to channel numbers, and the second, a list of unassigned channels that can be used for dynamic allocation. One channel is always left unassigned for dynamic allocation. If the number of needed channels exceeds the number of available ones, any further MIDI program numbers are assigned to channel 1.

Substreams without notes or rests (e.g. representing a conductor track) will not consume a channel.

Only necessarily works if prepareStreamForMidi() has been run before calling this routine.

An instrument’s .midiChannel attribute is observed. None is the default .midiChannel for all instruments except UnpitchedPercussion subclasses. Put another way, the priority is:

  • Instrument instance .midiChannel (set by user or imported from MIDI)

  • UnpitchedPercussion subclasses receive MIDI Channel 10 (9 in music21)

  • The channel mappings produced by reading from acceptableChannelList, or the default range 1-16. (More precisely, 1-15, since one dynamic channel is always reserved.)

Warning

The attribute .midiChannel on Instrument is 0-indexed, but .channel on MidiEvent is 1-indexed, as are all references to channels in this function.

music21.midi.translate.chordToMidiEvents(inputM21: chord.ChordBase, *, includeDeltaTime=True, channel=1) list[midi.DeltaTime | midi.MidiEvent]

Translates a Chord object to a list of base.DeltaTime and base.MidiEvents objects.

The channel can be specified, otherwise channel 1 is assumed.

See noteToMidiEvents above for more details.

>>> c = chord.Chord(['c3', 'g#4', 'b5'])
>>> c.volume = volume.Volume(velocity=90)
>>> c.volume.velocityIsRelative = False
>>> eventList = midi.translate.chordToMidiEvents(c)
>>> eventList
[<music21.midi.DeltaTime (empty) track=None, channel=None>,
 <music21.midi.MidiEvent NOTE_ON, track=None, channel=1, pitch=48, velocity=90>,
 <music21.midi.DeltaTime (empty) track=None, channel=None>,
 <music21.midi.MidiEvent NOTE_ON, track=None, channel=1, pitch=68, velocity=90>,
 <music21.midi.DeltaTime (empty) track=None, channel=None>,
 <music21.midi.MidiEvent NOTE_ON, track=None, channel=1, pitch=83, velocity=90>,
 <music21.midi.DeltaTime t=10080, track=None, channel=None>,
 <music21.midi.MidiEvent NOTE_OFF, track=None, channel=1, pitch=48, velocity=0>,
 <music21.midi.DeltaTime (empty) track=None, channel=None>,
 <music21.midi.MidiEvent NOTE_OFF, track=None, channel=1, pitch=68, velocity=0>,
 <music21.midi.DeltaTime (empty) track=None, channel=None>,
 <music21.midi.MidiEvent NOTE_OFF, track=None, channel=1, pitch=83, velocity=0>]
  • Changed in v7: made keyword-only.

  • Changed in v8: added support for PercussionChord

music21.midi.translate.conductorStream(s: Stream) Part

Strip the given stream of any events that belong in a conductor track rather than in a music track, and returns a Part containing just those events, without duplicates, suitable for being a Part to turn into a conductor track.

Sets a default MetronomeMark of 120 if no MetronomeMarks are present and a TimeSignature of 4/4 if not present.

Ensures that the conductor track always sorts before other parts.

Here we purposely use nested generic streams instead of Scores, Parts, etc. to show that this still works. But you should use Score, Part, Measure instead.

>>> s = stream.Stream(id='scoreLike')
>>> p = stream.Stream(id='partLike')
>>> p.priority = -2
>>> m = stream.Stream(id='measureLike')
>>> m.append(tempo.MetronomeMark(100))
>>> m.append(note.Note('C4'))
>>> p.append(m)
>>> s.insert(0, p)
>>> conductor = midi.translate.conductorStream(s)
>>> conductor.priority
-3

The MetronomeMark is moved and a default TimeSignature is added:

>>> conductor.show('text')
{0.0} <music21.instrument.Conductor 'Conductor'>
{0.0} <music21.tempo.MetronomeMark Quarter=100>
{0.0} <music21.meter.TimeSignature 4/4>

The original stream still has the note:

>>> s.show('text')
{0.0} <music21.stream.Stream partLike>
    {0.0} <music21.stream.Stream measureLike>
        {0.0} <music21.note.Note C>
music21.midi.translate.durationToMidiTicks(d)

Converts a Duration object to midi ticks.

Depends on defaults.ticksPerQuarter, Returns an int. Does not use defaults.ticksAtStart

>>> n = note.Note()
>>> n.duration.type = 'half'
>>> midi.translate.durationToMidiTicks(n.duration)
20160
>>> d = duration.Duration('quarter')
>>> dReference = midi.translate.ticksToDuration(10080, inputM21DurationObject=d)
>>> dReference is d
True
>>> d.type
'quarter'
>>> d.type = '16th'
>>> d.quarterLength
0.25
>>> midi.translate.durationToMidiTicks(d)
2520
music21.midi.translate.elementToMidiEventList(el: base.Music21Object) list[midi.MidiEvent | midi.MidiEvent] | None

Return a list of MidiEvents (or None) from a Music21Object, assuming that dynamics have already been applied, etc. Does not include DeltaTime objects.

Channel (1-indexed) is set to the default, 1. Track is not set.

>>> n = note.Note('C4')
>>> midiEvents = midi.translate.elementToMidiEventList(n)
>>> midiEvents
[<music21.midi.MidiEvent NOTE_ON, track=None, channel=1, pitch=60, velocity=90>,
 <music21.midi.MidiEvent NOTE_OFF, track=None, channel=1, pitch=60, velocity=0>]
music21.midi.translate.filterPacketsByTrackId(packetsSrc: list[dict[str, Any]], trackIdFilter: int | None = None) list[dict[str, Any]]

Given a list of Packet dictionaries, return a list of only those whose trackId matches the filter.

>>> packets = [
...     {'trackId': 1, 'name': 'hello'},
...     {'trackId': 2, 'name': 'bye'},
...     {'trackId': 1, 'name': 'hi'},
... ]
>>> midi.translate.filterPacketsByTrackId(packets, 1)
[{'trackId': 1, 'name': 'hello'},
 {'trackId': 1, 'name': 'hi'}]
>>> midi.translate.filterPacketsByTrackId(packets, 2)
[{'trackId': 2, 'name': 'bye'}]

If no trackIdFilter is passed, the original list is returned:

>>> midi.translate.filterPacketsByTrackId(packets) is packets
True
music21.midi.translate.getEndEvents(mt=None, channel=1)

Returns a list of midi.MidiEvent objects found at the end of a track.

>>> midi.translate.getEndEvents(channel=2)
[<music21.midi.DeltaTime t=10080, track=None, channel=2>,
 <music21.midi.MidiEvent END_OF_TRACK, track=None, channel=2, data=b''>]
music21.midi.translate.getMetaEvents(events: list[tuple[int, midi.MidiEvent]]) list[tuple[int, base.Music21Object]]
music21.midi.translate.getNotesFromEvents(events: list[tuple[int, midi.MidiEvent]]) list[tuple[tuple[int, midi.MidiEvent], tuple[int, midi.MidiEvent]]]

Returns a list of Tuples of MIDI events that are pairs of note-on and note-off events.

music21.midi.translate.getPacketFromMidiEvent(trackId: int, offset: int, midiEvent: midi.MidiEvent, obj: base.Music21Object | None = None, lastInstrument: instrument.Instrument | None = None) dict[str, t.Any]

Pack a dictionary of parameters for each event. Packets are used for sorting and configuring all note events. Includes offset, any cent shift, the midi event, and the source object.

Offset and duration values stored here are MIDI ticks, not quarter lengths.

>>> n = note.Note('C4')
>>> midiEvents = midi.translate.elementToMidiEventList(n)
>>> getPacket = midi.translate.getPacketFromMidiEvent
>>> getPacket(trackId=1, offset=0, midiEvent=midiEvents[0], obj=n)
{'trackId': 1,
 'offset': 0,
 'midiEvent': <music21.midi.MidiEvent NOTE_ON, track=None, channel=1, pitch=60, velocity=90>,
 'obj': <music21.note.Note C>,
 'centShift': None,
 'duration': 10080,
 'lastInstrument': None}
>>> inst = instrument.Harpsichord()
>>> getPacket(trackId=1, offset=0, midiEvent=midiEvents[1], obj=n, lastInstrument=inst)
{'trackId': 1,
 'offset': 0,
 'midiEvent': <music21.midi.MidiEvent NOTE_OFF, track=None, channel=1, pitch=60, velocity=0>,
 'obj': <music21.note.Note C>,
 'centShift': None,
 'duration': 0,
 'lastInstrument': <music21.instrument.Harpsichord 'Harpsichord'>}
music21.midi.translate.getStartEvents(mt=None, channel=1, instrumentObj=None)

Returns a list of midi.MidiEvent objects found at the beginning of a track.

A MidiTrack reference can be provided via the mt parameter.

>>> midi.translate.getStartEvents()
[<music21.midi.DeltaTime (empty) track=None, channel=1>,
 <music21.midi.MidiEvent SEQUENCE_TRACK_NAME, track=None, channel=1, data=b''>]
>>> midi.translate.getStartEvents(channel=2, instrumentObj=instrument.Harpsichord())
[<music21.midi.DeltaTime (empty) track=None, channel=2>,
 <music21.midi.MidiEvent SEQUENCE_TRACK_NAME, track=None, channel=2, data=b'Harpsichord'>,
 <music21.midi.DeltaTime (empty) track=None, channel=2>,
 <music21.midi.MidiEvent PROGRAM_CHANGE, track=None, channel=2, data=6>]
music21.midi.translate.getTimeForEvents(mt: midi.MidiTrack) list[tuple[int, midi.MidiEvent]]

Get a list of tuples of (tickTime, MidiEvent) from the events with time deltas.

music21.midi.translate.insertConductorEvents(conductorPart: Part, target: Part, *, isFirst: bool = False)

Insert a deepcopy of any TimeSignature, KeySignature, or MetronomeMark found in the conductorPart into the target Part at the same offset.

Obligatory to do this before making measures. New in v7.

music21.midi.translate.instrumentToMidiEvents(inputM21, includeDeltaTime=True, midiTrack=None, channel=1)

Converts a Instrument object to a list of MidiEvents

TODO: DOCS and TESTS

music21.midi.translate.keySignatureToMidiEvents(ks: key.KeySignature, includeDeltaTime=True) list[midi.DeltaTime | midi.MidiEvent]

Convert a single Key or KeySignature object to a two-element list of midi events, where the first is an empty DeltaTime (unless includeDeltaTime is False) and the second is a KEY_SIGNATURE MidiEvent

>>> ks = key.KeySignature(2)
>>> ks
<music21.key.KeySignature of 2 sharps>
>>> eventList = midi.translate.keySignatureToMidiEvents(ks)
>>> eventList
[<music21.midi.DeltaTime (empty) track=None, channel=None>,
 <music21.midi.MidiEvent KEY_SIGNATURE, track=None, channel=1, data=b'\x02\x00'>]
>>> k = key.Key('b-')
>>> k
<music21.key.Key of b- minor>
>>> eventList = midi.translate.keySignatureToMidiEvents(k, includeDeltaTime=False)
>>> eventList
[<music21.midi.MidiEvent KEY_SIGNATURE, track=None, channel=1, data=b'\xfb\x01'>]
music21.midi.translate.midiAsciiStringToBinaryString(midiFormat=1, ticksPerQuarterNote=960, tracksEventsList=None) bytes

Convert Ascii midi data to a bytes object (formerly binary midi string).

tracksEventsList contains a list of tracks which contain also a list of events.

asciiMidiEventList = [‘0 90 27 66’, ‘0 90 3e 60’, ‘3840 80 27 00’, ‘0 80 3e 00’]

The format of one event is : ‘aa bb cc dd’:

aa = delta time to last event (integer)
bb = Midi event type
cc = Note number (hex)
dd = Velocity (integer)

Example:

>>> asciiMidiEventList = []
>>> asciiMidiEventList.append('0 90 31 15')
>>> midiTrack = []
>>> midiTrack.append(asciiMidiEventList)
>>> midiBinaryBytes = midi.translate.midiAsciiStringToBinaryString(tracksEventsList=midiTrack)
>>> midiBinaryBytes
b'MThd\x00\x00\x00\x06\x00\x01\x00\x01\x03\xc0MTrk\x00\x00\x00\x04\x00\x901\x0f'

Note that the name is from pre-Python 3. There is now in fact nothing called a “binary string” it is in fact a bytes object.

music21.midi.translate.midiEventsToChord(eventList: Sequence[tuple[tuple[int, midi.MidiEvent], tuple[int, midi.MidiEvent]]], ticksPerQuarter: int = 10080) chord.ChordBase

Creates a Chord from a list of DeltaTime or integers and MidiEvent objects grouped as follows:

[((time_pitch1_on, pitch1_note_on_event),
  (time_pitch1_off, pitch1_note_off_event)
  ),
 ((time_pitch2_on, pitch2_note_on_event),
  (time_pitch2_off, pitch2_note_off_event)
  ),

(etc.)

midiEventsToNote().

All DeltaTime objects except the first (for the first note on) and last (for the last note off) are ignored.

>>> mt = midi.MidiTrack(1)
>>> dt1 = midi.DeltaTime(mt)
>>> me1 = midi.MidiEvent(mt)
>>> me1.type = midi.ChannelVoiceMessages.NOTE_ON
>>> me1.pitch = 45
>>> me1.velocity = 94

Note that only the times of the first NOTE_ON and last NOTE_OFF matter, so we don’t even bother setting the time of dt3 and dt4.

>>> dt3 = midi.DeltaTime(mt)
>>> me3 = midi.MidiEvent(mt)
>>> me3.type = midi.ChannelVoiceMessages.NOTE_OFF

The pitch of the NOTE_OFF events are not checked by this function. They are assumed to have been aligned by the previous parser.

>>> me3.pitch = 45
>>> me3.velocity = 0
>>> dt2 = midi.DeltaTime(mt)
>>> me2 = midi.MidiEvent(mt)
>>> me2.type = midi.ChannelVoiceMessages.NOTE_ON
>>> me2.pitch = 46
>>> me2.velocity = 94
>>> dt4 = midi.DeltaTime(mt)
>>> dt4.time = 20160
>>> me4 = midi.MidiEvent(mt)
>>> me4.type = midi.ChannelVoiceMessages.NOTE_OFF
>>> me4.pitch = 46
>>> me4.velocity = 0
>>> c = midi.translate.midiEventsToChord([((dt1.time, me1), (dt3.time, me3)),
...                                       ((dt2.time, me2), (dt4.time, me4))])
>>> c
<music21.chord.Chord A2 B-2>
>>> c.duration.quarterLength
2.0

If the channel is set to 10, then a PercussionChord is returned:

>>> me2.channel = 10
>>> midi.translate.midiEventsToChord([((dt1.time, me1), (dt3.time, me3)),
...                                   ((dt2.time, me2), (dt4.time, me4))])
<music21.percussion.PercussionChord [Tom-Tom Hi-Hat Cymbal]>
  • Changed in v7: Uses the last DeltaTime in the list to get the end time.

  • Changed in v7.3: Returns a PercussionChord if any event is on channel 10.

  • Changed in v8: inputM21 is no longer supported. Flat list format is removed.

music21.midi.translate.midiEventsToInstrument(eventList: midi.MidiEvent | tuple[int, midi.MidiEvent]) instrument.Instrument

Convert a single MIDI event into a music21 Instrument object.

>>> me = midi.MidiEvent()
>>> me.type = midi.ChannelVoiceMessages.PROGRAM_CHANGE
>>> me.data = 53  # MIDI program 54: Voice Oohs
>>> midi.translate.midiEventsToInstrument(me)
<music21.instrument.Vocalist 'Voice'>

The percussion map will be used if the channel is 10:

>>> me.channel = 10
>>> instrumentObj = midi.translate.midiEventsToInstrument(me)
>>> instrumentObj
<music21.instrument.UnpitchedPercussion 'Percussion'>
>>> instrumentObj.midiChannel  # 0-indexed in music21
9
>>> instrumentObj.midiProgram  # 0-indexed in music21
53
music21.midi.translate.midiEventsToKey(eventList) Key

Convert a single MIDI event into a KeySignature object.

>>> mt = midi.MidiTrack(1)
>>> me1 = midi.MidiEvent(mt)
>>> me1.type = midi.MetaEvents.KEY_SIGNATURE
>>> me1.data = midi.putNumbersAsList([2, 0])  # d major
>>> ks = midi.translate.midiEventsToKey(me1)
>>> ks
<music21.key.Key of D major>
>>> ks.mode
'major'
>>> me2 = midi.MidiEvent(mt)
>>> me2.type = midi.MetaEvents.KEY_SIGNATURE
>>> me2.data = midi.putNumbersAsList([-2, 1])  # g minor
>>> me2.data
b'\xfe\x01'
>>> midi.getNumbersAsList(me2.data)
[254, 1]
>>> ks = midi.translate.midiEventsToKey(me2)
>>> ks
<music21.key.Key of g minor>
>>> ks.sharps
-2
>>> ks.mode
'minor'
music21.midi.translate.midiEventsToNote(eventTuple: tuple[tuple[int, midi.MidiEvent], tuple[int, midi.MidiEvent]], ticksPerQuarter: int = 10080) note.Note | note.Unpitched

Convert from a tuple of two tuples of an int and a midi.MidiEvent objects to a music21 Note. The events should be arranged as follows:

((int timeStart, midiEvent1), (int timeEnd, midiEvent2))

It is assumed, but not checked, that midiEvent2 is an appropriate Note_Off command. Thus, only three elements are really needed.

N.B. this takes in a list of music21 MidiEvent objects so see […] on how to convert raw MIDI data to MidiEvent objects

In this example, we start a NOTE_ON event at offset 1.0 that lasts for 2.0 quarter notes until we send a zero-velocity NOTE_ON (=NOTE_OFF) event for the same pitch.

>>> mt = midi.MidiTrack(1)
>>> dt1 = midi.DeltaTime(mt)
>>> dt1.time = 10080
>>> me1 = midi.MidiEvent(mt)
>>> me1.type = midi.ChannelVoiceMessages.NOTE_ON
>>> me1.pitch = 45
>>> me1.velocity = 94
>>> dt2 = midi.DeltaTime(mt)
>>> dt2.time = 20160
>>> me2 = midi.MidiEvent(mt)
>>> me2.type = midi.ChannelVoiceMessages.NOTE_ON
>>> me2.pitch = 45
>>> me2.velocity = 0
>>> n = midi.translate.midiEventsToNote(((dt1.time, me1), (dt2.time, me2)))
>>> n.pitch
<music21.pitch.Pitch A2>
>>> n.duration.quarterLength
1.0
>>> n.volume.velocity
94

If channel is 10, an Unpitched element is returned.

>>> me1.channel = 10
>>> unp = midi.translate.midiEventsToNote(((dt1.time, me1), (dt2.time, me2)))
>>> unp
<music21.note.Unpitched 'Tom-Tom'>

Access the storedInstrument:

>>> unp.storedInstrument
<music21.instrument.TomTom 'Tom-Tom'>

And with values that cannot be translated, a generic UnpitchedPercussion instance is given:

>>> me1.pitch = 1
>>> unp = midi.translate.midiEventsToNote(((dt1.time, me1), (dt2.time, me2)))
>>> unp.storedInstrument
<music21.instrument.UnpitchedPercussion 'Percussion'>
  • Changed in v7.3: Returns None if inputM21 is provided. Returns a

    Unpitched instance if the event is on Channel 10.

  • Changed in v8: inputM21 is no longer supported.

    The only supported usage now is two tuples.

music21.midi.translate.midiEventsToTempo(eventList)

Convert a single MIDI event into a music21 Tempo object.

TODO: Need Tests

music21.midi.translate.midiEventsToTimeSignature(eventList)

Convert a single MIDI event into a music21 TimeSignature object.

>>> mt = midi.MidiTrack(1)
>>> me1 = midi.MidiEvent(mt)
>>> me1.type = midi.MetaEvents.TIME_SIGNATURE
>>> me1.data = midi.putNumbersAsList([3, 1, 24, 8])  # 3/2 time
>>> ts = midi.translate.midiEventsToTimeSignature(me1)
>>> ts
<music21.meter.TimeSignature 3/2>
>>> me2 = midi.MidiEvent(mt)
>>> me2.type = midi.MetaEvents.TIME_SIGNATURE
>>> me2.data = midi.putNumbersAsList([3, 4])  # 3/16 time
>>> ts = midi.translate.midiEventsToTimeSignature(me2)
>>> ts
<music21.meter.TimeSignature 3/16>
music21.midi.translate.midiFilePathToStream(filePath, *, inputM21=None, **keywords)

Used by music21.converter:

Take in a file path (name of a file on disk) and using midiFileToStream,

return a Score object (or if inputM21 is passed in, use that object instead).

Keywords to control quantization: quantizePost controls whether to quantize the output. (Default: True) quarterLengthDivisors allows for overriding the default quantization units in defaults.quantizationQuarterLengthDivisors. (Default: (4, 3)).

>>> fp = '/Users/test/music21/midi/testPrimitive/test05.mid'
>>> streamScore = midi.translate.midiFilePathToStream(fp)
>>> streamScore
<music21.stream.Score ...>
  • Changed in v8: inputM21 is keyword only.

music21.midi.translate.midiStringToStream(strData, **keywords)

Convert a string of binary midi data to a Music21 stream.Score object.

Keywords to control quantization: quantizePost controls whether to quantize the output. (Default: True) quarterLengthDivisors allows for overriding the default quantization units in defaults.quantizationQuarterLengthDivisors. (Default: (4, 3)).

N.B. – this has been somewhat problematic, so use at your own risk.

>>> midiBinStr = (b'MThd\x00\x00\x00\x06\x00\x01\x00\x01\x04\x00'
...               + b'MTrk\x00\x00\x00\x16\x00\xff\x03\x00\x00\xe0\x00@\x00'
...               + b'\x90CZ\x88\x00\x80C\x00\x88\x00\xff/\x00')
>>> s = midi.translate.midiStringToStream(midiBinStr)
>>> s.show('text')
{0.0} <music21.stream.Part 0x108aa94f0>
    {0.0} <music21.stream.Measure 1 offset=0.0>
        {0.0} <music21.instrument.Instrument ''>
        {0.0} <music21.clef.TrebleClef>
        {0.0} <music21.meter.TimeSignature 4/4>
        {0.0} <music21.note.Note G>
        {1.0} <music21.note.Rest dotted-half>
        {4.0} <music21.bar.Barline type=final>
music21.midi.translate.midiTrackToStream(mt, *, ticksPerQuarter: int = 10080, quantizePost=True, inputM21=None, conductorPart: Part | None = None, isFirst: bool = False, quarterLengthDivisors: Sequence[int] = (), **keywords) Part

Note that quantization takes place in stream.py since it’s useful not just for MIDI.

>>> fp = common.getSourceFilePath() / 'midi' / 'testPrimitive' / 'test05.mid'
>>> mf = midi.MidiFile()
>>> mf.open(fp)
>>> mf.read()
>>> mf.close()
>>> mf
<music21.midi.MidiFile 1 track>
>>> len(mf.tracks)
1
>>> mt = mf.tracks[0]
>>> mt
<music21.midi.MidiTrack 0 -- 56 events>
>>> mt.events
[<music21.midi.DeltaTime ...>,
 <music21.midi.MidiEvent SEQUENCE_TRACK_NAME...>,
 <music21.midi.DeltaTime ...>,
 <music21.midi.MidiEvent NOTE_ON, track=0, channel=1, pitch=36, velocity=90>,
 ...]
>>> p = midi.translate.midiTrackToStream(mt, ticksPerQuarter=mf.ticksPerQuarterNote)
>>> p
<music21.stream.Part ...>
>>> len(p.recurse().notesAndRests)
14
>>> p.recurse().notes.first().pitch.midi
36
>>> p.recurse().notes.first().volume.velocity
90

Note that as of music21 v7, the Part object already has measures made:

>>> p.show('text')
{0.0} <music21.stream.Measure 1 offset=0.0>
    {0.0} <music21.instrument.Instrument ''>
    {0.0} <music21.clef.TrebleClef>
    {0.0} <music21.meter.TimeSignature 4/4>
    {0.0} <music21.note.Note C>
    {1.0} <music21.note.Rest quarter>
    {2.0} <music21.chord.Chord F3 G#4 C5>
    {3.0} <music21.note.Rest quarter>
{4.0} <music21.stream.Measure 2 offset=4.0>
    {0.0} <music21.note.Rest eighth>
    {0.5} <music21.note.Note B->
    {1.5} <music21.note.Rest half>
    {3.5} <music21.chord.Chord D2 A4>
{8.0} <music21.stream.Measure 3 offset=8.0>
    {0.0} <music21.note.Rest eighth>
    {0.5} <music21.chord.Chord C#2 B-3 G#6>
    {1.0} <music21.note.Rest dotted-quarter>
    {2.5} <music21.chord.Chord F#3 A4 C#5>
{12.0} <music21.stream.Measure 4 offset=12.0>
    {0.0} <music21.chord.Chord F#3 A4 C#5>
    {2.5} <music21.note.Rest dotted-quarter>
    {4.0} <music21.bar.Barline type=final>
  • Changed in v7: Now makes measures.

  • Changed in v8: all but the first attribute are keyword only.

music21.midi.translate.midiTracksToStreams(midiTracks: list[midi.MidiTrack], ticksPerQuarter: int = 10080, quantizePost=True, inputM21: stream.Score | None = None, **keywords) stream.Score

Given a list of midiTracks, populate either a new stream.Score or inputM21 with a Part for each track.

music21.midi.translate.music21ObjectToMidiFile(music21Object, *, addStartDelay=False) midi.MidiFile

Either calls streamToMidiFile on the music21Object or puts a copy of that object into a Stream (so as not to change activeSites, etc.) and calls streamToMidiFile on that object.

music21.midi.translate.noteToMidiEvents(inputM21: note.Note | note.Unpitched, *, includeDeltaTime=True, channel=1) list[midi.DeltaTime | midi.MidiEvent]

Translate a music21 Note to a list of four MIDI events – the DeltaTime for the start of the note (0), the NOTE_ON event, the DeltaTime to the end of the note, and the NOTE_OFF event.

If includeDeltaTime is not True then the DeltaTime events aren’t returned, thus only two events are returned.

The initial deltaTime object is always 0. It will be changed when processing Notes from a Stream.

The channel can be specified, otherwise channel 1 is assumed.

>>> n1 = note.Note('C#4')
>>> eventList = midi.translate.noteToMidiEvents(n1)
>>> eventList
[<music21.midi.DeltaTime (empty) track=None, channel=1>,
 <music21.midi.MidiEvent NOTE_ON, track=None, channel=1, pitch=61, velocity=90>,
 <music21.midi.DeltaTime t=10080, track=None, channel=1>,
 <music21.midi.MidiEvent NOTE_OFF, track=None, channel=1, pitch=61, velocity=0>]
>>> n1.duration.quarterLength = 2.5
>>> eventList = midi.translate.noteToMidiEvents(n1)
>>> eventList
[<music21.midi.DeltaTime (empty) track=None, channel=1>,
 <music21.midi.MidiEvent NOTE_ON, track=None, channel=1, pitch=61, velocity=90>,
 <music21.midi.DeltaTime t=25200, track=None, channel=1>,
 <music21.midi.MidiEvent NOTE_OFF, track=None, channel=1, pitch=61, velocity=0>]

Omitting DeltaTimes:

>>> eventList2 = midi.translate.noteToMidiEvents(n1, includeDeltaTime=False, channel=9)
>>> eventList2
[<music21.midi.MidiEvent NOTE_ON, track=None, channel=9, pitch=61, velocity=90>,
 <music21.midi.MidiEvent NOTE_OFF, track=None, channel=9, pitch=61, velocity=0>]
  • Changed in v7: made keyword-only.

  • Changed in v8: added support for Unpitched

music21.midi.translate.offsetToMidiTicks(o, addStartDelay=False)

Helper function to convert a music21 offset value to MIDI ticks, depends on defaults.ticksPerQuarter and defaults.ticksAtStart.

Returns an int.

>>> defaults.ticksPerQuarter
10080
>>> defaults.ticksAtStart
10080
>>> midi.translate.offsetToMidiTicks(0)
0
>>> midi.translate.offsetToMidiTicks(0, addStartDelay=True)
10080
>>> midi.translate.offsetToMidiTicks(1)
10080
>>> midi.translate.offsetToMidiTicks(20.5)
206640
music21.midi.translate.packetStorageFromSubstreamList(substreamList: list[music21.stream.base.Part], *, addStartDelay=False) dict[int, dict[str, Any]]

Make a dictionary of raw packets and the initial instrument for each subStream.

If the first Part in the list of parts is empty then a new Conductor object will be given as the instrument.

>>> s = stream.Score()
>>> p = stream.Part()
>>> m = stream.Measure(number=1)
>>> m.append(tempo.MetronomeMark(100))
>>> m.append(instrument.Oboe())
>>> m.append(note.Note('C4', type='whole'))  # MIDI 60
>>> p.append(m)
>>> s.append(p)
>>> sOut = midi.translate.prepareStreamForMidi(s)
>>> partList = list(sOut.parts)
>>> packetStorage = midi.translate.packetStorageFromSubstreamList(partList)
>>> list(sorted(packetStorage.keys()))
[0, 1]
>>> list(sorted(packetStorage[0].keys()))
['initInstrument', 'rawPackets']
>>> from pprint import pprint
>>> pprint(packetStorage)
{0: {'initInstrument': <music21.instrument.Conductor 'Conductor'>,
     'rawPackets': [{'centShift': None,
                     'duration': 0,
                     'lastInstrument': <music21.instrument.Conductor 'Conductor'>,
                     'midiEvent': <music21.midi.MidiEvent SET_TEMPO, ... channel=1, ...>,
                     'obj': <music21.tempo.MetronomeMark Quarter=100>,
                     'offset': 0,
                     'trackId': 0},
                    {'centShift': None,
                     'duration': 0,
                     'lastInstrument': <music21.instrument.Conductor 'Conductor'>,
                     'midiEvent': <music21.midi.MidiEvent TIME_SIGNATURE, ...>,
                     'obj': <music21.meter.TimeSignature 4/4>,
                     'offset': 0,
                     'trackId': 0}]},
 1: {'initInstrument': <music21.instrument.Oboe 'Oboe'>,
     'rawPackets': [{'centShift': None,
                     'duration': 0,
                     'lastInstrument': <music21.instrument.Oboe 'Oboe'>,
                     'midiEvent': <music21.midi.MidiEvent PROGRAM_CHANGE,
                                      track=None, channel=1, data=68>,
                     'obj': <music21.instrument.Oboe 'Oboe'>,
                     'offset': 0,
                     'trackId': 1},
                    {'centShift': None,
                     'duration': 40320,
                     'lastInstrument': <music21.instrument.Oboe 'Oboe'>,
                     'midiEvent': <music21.midi.MidiEvent NOTE_ON,
                                      track=None, channel=1, pitch=60, velocity=90>,
                     'obj': <music21.note.Note C>,
                     'offset': 0,
                     'trackId': 1},
                    {'centShift': None,
                     'duration': 0,
                     'lastInstrument': <music21.instrument.Oboe 'Oboe'>,
                     'midiEvent': <music21.midi.MidiEvent NOTE_OFF,
                                       track=None, channel=1, pitch=60, velocity=0>,
                     'obj': <music21.note.Note C>,
                     'offset': 40320,
                     'trackId': 1}]}}
music21.midi.translate.packetsToDeltaSeparatedEvents(packets: list[dict[str, t.Any]], midiTrack: midi.MidiTrack) list[midi.MidiEvent | midi.DeltaTime]

Given a list of packets (which already contain MidiEvent objects) return a list of those Events with proper delta times between them.

At this stage MIDI event objects have been created. The key process here is finding the adjacent time between events and adding DeltaTime events before each MIDI event.

Delta time channel values are derived from the previous midi event.

music21.midi.translate.packetsToMidiTrack(packets, trackId=1, channel=1, instrumentObj=None)

Given packets already allocated with channel and/or instrument assignments, place these in a MidiTrack.

Note that all packets can be sent; only those with matching trackIds will be collected into the resulting track

The channel defines the channel that startEvents and endEvents will be assigned to

Use streamToPackets to convert the Stream to the packets

music21.midi.translate.prepareStreamForMidi(s) Stream

Given a score, prepare it for MIDI processing, and return a new Stream:

  1. Expand repeats.

2. Make changes that will let us later create a conductor (tempo) track by placing MetronomeMark, TimeSignature, and KeySignature objects into a new Part, and remove them from other parts.

  1. Ensure that the resulting Stream always has part-like substreams.

Note: will make a deepcopy() of the stream.

>>> s = stream.Score()
>>> p = stream.Part()
>>> m = stream.Measure(number=1)
>>> m.append(tempo.MetronomeMark(100))
>>> m.append(note.Note('C4', type='whole'))  # MIDI 60
>>> p.append(m)
>>> s.append(p)
>>> sOut = midi.translate.prepareStreamForMidi(s)
>>> sOut.show('text')
{0.0} <music21.stream.Part 0x10b0439a0>
    {0.0} <music21.tempo.MetronomeMark Quarter=100>
    {0.0} <music21.meter.TimeSignature 4/4>
{0.0} <music21.stream.Part 0x10b043c10>
    {0.0} <music21.stream.Measure 1 offset=0.0>
        {0.0} <music21.note.Note C>
music21.midi.translate.streamHierarchyToMidiTracks(inputM21, *, acceptableChannelList=None, addStartDelay=False)

Given a Stream, Score, Part, etc., that may have substreams (i.e., a hierarchy), return a list of MidiTrack objects.

acceptableChannelList is a list of MIDI Channel numbers that can be used or None. If None, then 1-9, 11-16 are used (10 being reserved for percussion).

In addition, if an Instrument object in the stream has a .midiChannel that is not None, that channel is observed, and also treated as reserved. Only subclasses of UnpitchedPercussion have a default .midiChannel, but users may manipulate this. See channelInstrumentData() for more, and for documentation on acceptableChannelList.

Called by streamToMidiFile()

The process:

  1. makes a deepcopy of the Stream (Developer TODO: could this be done with a shallow copy? Not if ties are stripped and volume realized.)

  2. we make a list of all instruments that are being used in the piece.

  • Changed in v6: acceptableChannelList is keyword only. addStartDelay is new.

  • Changed in v6.5: Track 0 (tempo/conductor track) always exported.

music21.midi.translate.streamToPackets(s: Stream, trackId: int = 1, addStartDelay: bool = False) list[dict[str, Any]]

Convert a (flattened, sorted) Stream to packets.

This assumes that the Stream has already been flattened, ties have been stripped, and instruments, if necessary, have been added.

In converting from a Stream to MIDI, this is called first, resulting in a collection of packets by offset. Then, packets to events is called.

music21.midi.translate.tempoToMidiEvents(tempoIndication: tempo.MetronomeMark, includeDeltaTime=True) list[midi.DeltaTime | midi.MidiEvent] | None

Given any TempoIndication, convert it to list of MidiEvent objects that signifies a MIDI tempo indication.

>>> mm = tempo.MetronomeMark(number=90)
>>> events = midi.translate.tempoToMidiEvents(mm)
>>> events
[<music21.midi.DeltaTime ...>, <music21.midi.MidiEvent SET_TEMPO...>]
>>> len(events)
2
>>> events[0]
<music21.midi.DeltaTime (empty) track=None, channel=None>
>>> evt1 = events[1]
>>> evt1
<music21.midi.MidiEvent SET_TEMPO, track=None, channel=1, data=b'\n,+'>
>>> evt1.data
b'\n,+'
>>> microSecondsPerQuarterNote = midi.getNumber(evt1.data, len(evt1.data))[0]
>>> microSecondsPerQuarterNote
666667
>>> round(60_000_000 / microSecondsPerQuarterNote, 1)
90.0

If includeDeltaTime is False then the DeltaTime object is omitted:

>>> midi.translate.tempoToMidiEvents(mm, includeDeltaTime=False)
[<music21.midi.MidiEvent SET_TEMPO...>]

Test round-trip. Note that for pure tempo numbers, by default we create a text name if there’s an appropriate one:

>>> midi.translate.midiEventsToTempo(events)
<music21.tempo.MetronomeMark maestoso Quarter=90>

None is returned if the MetronomeMark lacks a number, which can happen with metric modulation marks.

>>> midi.translate.tempoToMidiEvents(tempo.MetronomeMark(number=None)) is None
True

Sounding numbers also translate even if number is None

>>> mm = tempo.MetronomeMark(numberSounding=80)
>>> midi.translate.tempoToMidiEvents(mm)
[<music21.midi.DeltaTime ...>, <music21.midi.MidiEvent SET_TEMPO...>]
music21.midi.translate.ticksToDuration(ticks, ticksPerQuarter: int = 10080, inputM21DurationObject=None)

Converts a number of MIDI Ticks to a music21 duration.Duration() object.

Optional parameters include ticksPerQuarter – in case something other than the default.ticksPerQuarter (10080) is used in this file. And it can take a Duration object to modify, specified as inputM21DurationObject

>>> d = midi.translate.ticksToDuration(10080)
>>> d
<music21.duration.Duration 1.0>
>>> d.type
'quarter'
>>> n = note.Note()
>>> midi.translate.ticksToDuration(30240, inputM21DurationObject=n.duration)
<music21.duration.Duration 3.0>
>>> n.duration.type
'half'
>>> n.duration.dots
1

More complex rhythms can also be set automatically:

>>> d2 = duration.Duration()
>>> d2reference = midi.translate.ticksToDuration(23625, inputM21DurationObject=d2)
>>> d2 is d2reference
True
>>> d2.quarterLength
2.34375
>>> d2.type
'complex'
>>> d2.components
(DurationTuple(type='half', dots=0, quarterLength=2.0),
 DurationTuple(type='16th', dots=0, quarterLength=0.25),
 DurationTuple(type='64th', dots=1, quarterLength=0.09375))
>>> d2.components[2].type
'64th'
>>> d2.components[2].dots
1
music21.midi.translate.timeSignatureToMidiEvents(ts, includeDeltaTime=True)

Translate a TimeSignature to a pair of events: a DeltaTime and a MidiEvent TIME_SIGNATURE.

Returns a two-element list

>>> ts = meter.TimeSignature('5/4')
>>> eventList = midi.translate.timeSignatureToMidiEvents(ts)
>>> eventList[0]
<music21.midi.DeltaTime (empty) track=None, channel=None>
>>> eventList[1]
<music21.midi.MidiEvent TIME_SIGNATURE, track=None, channel=1, data=b'\x05\x02\x18\x08'>
music21.midi.translate.updatePacketStorageWithChannelInfo(packetStorage: dict[int, dict[str, Any]], channelByInstrument: dict[int | None, int | None]) None

Take the packetStorage dictionary and using information from ‘initInstrument’ and channelByInstrument, add an ‘initChannel’ key to each packetStorage bundle and to each rawPacket in the bundle[‘rawPackets’]