1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
# This file is part of LilyPond, the GNU music typesetter.
#
# Copyright (C) 2001--2022 Han-Wen Nienhuys <hanwen@xs4all.nl>
# Jan Nieuwenhuizen <janneke@gnu.org>
#
#
# LilyPond is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# LilyPond is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
# import midi
# s = open ("s.midi").read ()
# midi.parse_track (s)
# midi.parse (s)
#
#
# returns a MIDI file as the tuple
#
# ((format, division), TRACKLIST) # division (>0) = TPQN*4
# # or (<0) TBD
#
# each track is an EVENTLIST, where EVENT is
#
# (time, (type, ARG1, [ARG2])) # time = cumulative delta time
# MIDI event:
# type = MIDI status+channel >= x80
# META-event = xFF:
# type = meta-event type <= x7F
# ARG1 = length
# ARG2 = data
import array
import struct
class error (Exception):
pass
# class warning (Exception): pass
# TODO: should use enum?
# Channel voice messages
NOTE_OFF = 0x80
NOTE_ON = 0x90
POLYPHONIC_KEY_PRESSURE = 0xa0
CONTROLLER_CHANGE = 0xb0
PROGRAM_CHANGE = 0xc0
CHANNEL_KEY_PRESSURE = 0xd0
PITCH_BEND = 0xe0
# Channel mode messages
ALL_SOUND_OFF = 0x78
RESET_ALL_CONTROLLERS = 0x79
LOCAL_CONTROL = 0x7a
ALL_NOTES_OFF = 0x7b
OMNI_MODE_OFF = 0x7c
OMNI_MODE_ON = 0x7d
MONO_MODE_ON = 0x7e
POLY_MODE_ON = 0x7f
# Meta events
SEQUENCE_NUMBER = 0x0
TEXT_EVENT = 0x1
COPYRIGHT_NOTICE = 0x2
SEQUENCE_TRACK_NAME = 0x3
INSTRUMENT_NAME = 0x4
LYRIC = 0x5
MARKER = 0x6
CUE_POINT = 0x7
PROGRAM_NAME = 0x8
DEVICE_NAME = 0x9
MIDI_CHANNEL_PREFIX = 0x20
MIDI_PORT = 0x21
END_OF_TRACK = 0x2f
SET_TEMPO = 0x51
SMTPE_OFFSET = 0x54
TIME_SIGNATURE = 0x58
KEY_SIGNATURE = 0x59
XMF_PATCH_TYPE_PREFIX = 0x60
SEQUENCER_SPECIFIC_META_EVENT = 0x7f
META_EVENT = 0xff
def _get_variable_length_number(nextbyte, getbyte):
sum = 0
while nextbyte >= 0x80:
sum = (sum + (nextbyte & 0x7F)) << 7
nextbyte = getbyte()
return sum + nextbyte
def _first_command_is_repeat(status, nextbyte, getbyte):
raise error('the first midi command in the track is a repeat')
def _read_two_bytes(status, nextbyte, getbyte):
return status, nextbyte
def _read_three_bytes(status, nextbyte, getbyte):
return status, nextbyte, getbyte()
def _read_string(nextbyte, getbyte):
length = _get_variable_length_number(nextbyte, getbyte)
return ''.join(chr(getbyte()) for i in range(length))
def _read_f0_byte(status, nextbyte, getbyte):
if status == 0xff:
return status, nextbyte, _read_string(getbyte(), getbyte)
return status, _read_string(nextbyte, getbyte)
_read_midi_event = (
_first_command_is_repeat, # 0
None, # 10
None, # 20
None, # 30
None, # 40
None, # 50
None, # 60 data entry???
None, # 70 all notes off???
_read_three_bytes, # 80 note off
_read_three_bytes, # 90 note on
_read_three_bytes, # a0 poly aftertouch
_read_three_bytes, # b0 control
_read_two_bytes, # c0 prog change
_read_two_bytes, # d0 ch aftertouch
_read_three_bytes, # e0 pitchwheel range
_read_f0_byte, # f0
)
def _parse_track_body(data, clocks_max):
# This seems to be the fastest way of getting bytes in order as integers.
dataiter = iter(array.array('B', data))
getbyte = dataiter.__next__
time = 0
status = 0
try:
for nextbyte in dataiter:
time += _get_variable_length_number(nextbyte, getbyte)
if clocks_max and time > clocks_max:
break
nextbyte = getbyte()
if nextbyte >= 0x80:
status = nextbyte
nextbyte = getbyte()
yield time, _read_midi_event[status >> 4](status, nextbyte, getbyte)
except StopIteration:
# If the track ended just before the start of an event, the for loop
# will exit normally. If it ends anywhere else, we end up here.
print(len(list(dataiter)))
raise error('a track ended in the middle of a MIDI command')
def _parse_hunk(data, pos, type, magic):
if data[pos:pos+4] != magic:
raise error('expected %r, got %r' % (magic, data[pos:pos+4]))
try:
length, = struct.unpack('>I', data[pos+4:pos+8])
except struct.error:
raise error(
'the %s header is truncated (may be an incomplete download)' % type)
endpos = pos + 8 + length
data = data[pos+8:endpos]
if len(data) != length:
raise error(
'the %s is truncated (may be an incomplete download)' % type)
return data, endpos
def _parse_tracks(midi, pos, num_tracks, clocks_max):
if num_tracks > 256:
raise error('too many tracks: %d' % num_tracks)
for i in range(num_tracks):
trackdata, pos = _parse_hunk(midi, pos, 'track', b'MTrk')
yield list(_parse_track_body(trackdata, clocks_max))
# if pos < len(midi):
# warn
def parse_track(track, clocks_max=None):
track_body, end = _parse_hunk(track, 0, 'track', b'MTrk')
# if end < len(track):
# warn
return list(_parse_track_body(track_body, clocks_max))
def parse(midi, clocks_max=None):
header, first_track_pos = _parse_hunk(midi, 0, 'file', b'MThd')
try:
format, num_tracks, division = struct.unpack('>3H', header[:6])
except struct.error:
raise error('the file header is too short')
# if division < 0:
# raise error ('cannot handle non-metrical time')
tracks = list(_parse_tracks(midi, first_track_pos, num_tracks, clocks_max))
return (format, division*4), tracks
|