MuGeminorum commited on
Commit
0e22503
1 Parent(s): 0ce1c0b
app.py CHANGED
@@ -4,6 +4,7 @@ import torch
4
  import shutil
5
  import gradio as gr
6
  from piano_transcription_inference import PianoTranscription, sample_rate, load_audio
 
7
  from convert import *
8
 
9
 
@@ -38,8 +39,8 @@ def audio2midi(audio_path: str):
38
 
39
  def inference(audio_path: str):
40
  midi_path = audio2midi(audio_path)
41
- xml_path = midi2xml(midi_path)
42
- abc = xml2abc(xml_path)
43
  mxl_path = xml2mxl(xml_path)
44
  pdf_path, jpg_path = mxl2jpg(mxl_path)
45
  return midi_path, pdf_path, xml_path, mxl_path, abc, jpg_path
@@ -47,14 +48,17 @@ def inference(audio_path: str):
47
 
48
  iface = gr.Interface(
49
  fn=inference,
50
- inputs=gr.Audio(label="上传音频100%后再点提交", type="filepath"),
 
 
 
51
  outputs=[
52
- gr.components.File(label="下载 MIDI"),
53
- gr.components.File(label="下载 PDF 乐谱"),
54
- gr.components.File(label="下载 MusicXML"),
55
- gr.components.File(label="下载 MXL"),
56
- gr.Textbox(label="abc 乐谱", show_copy_button=True),
57
- gr.Image(label="五线谱", type="filepath"),
58
  ],
59
  )
60
 
 
4
  import shutil
5
  import gradio as gr
6
  from piano_transcription_inference import PianoTranscription, sample_rate, load_audio
7
+ from midi2abc import midi2abc
8
  from convert import *
9
 
10
 
 
39
 
40
  def inference(audio_path: str):
41
  midi_path = audio2midi(audio_path)
42
+ xml_path, title, artist = midi2xml(midi_path)
43
+ abc = midi2abc(midi_path, title, artist)
44
  mxl_path = xml2mxl(xml_path)
45
  pdf_path, jpg_path = mxl2jpg(mxl_path)
46
  return midi_path, pdf_path, xml_path, mxl_path, abc, jpg_path
 
48
 
49
  iface = gr.Interface(
50
  fn=inference,
51
+ inputs=gr.Audio(
52
+ label="Please make sure that the audio has finished uploading before clicking submit.",
53
+ type="filepath",
54
+ ),
55
  outputs=[
56
+ gr.components.File(label="Download MIDI"),
57
+ gr.components.File(label="Download PDF score"),
58
+ gr.components.File(label="Download MusicXML"),
59
+ gr.components.File(label="Download MXL"),
60
+ gr.Textbox(label="abc notation", show_copy_button=True),
61
+ gr.Image(label="Staff", type="filepath"),
62
  ],
63
  )
64
 
convert.py CHANGED
@@ -4,6 +4,7 @@ import fitz
4
  import requests
5
  import subprocess
6
  from PIL import Image
 
7
 
8
 
9
  def download(url: str, directory: str, filename: str):
@@ -56,6 +57,27 @@ else:
56
  mscore = "D:/Program Files/MuseScore 3/bin/MuseScore3.exe"
57
 
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  def xml2abc(xml_path: str):
60
  result = subprocess.run(
61
  f"python xml2abc.py {xml_path}", stdout=subprocess.PIPE, text=True
@@ -78,8 +100,17 @@ def midi2xml(mid_file: str):
78
  xml_file = mid_file.replace(".mid", ".musicxml")
79
  command = [mscore, "-o", xml_file, mid_file]
80
  result = subprocess.run(command)
 
 
 
 
 
 
 
 
 
81
  print(result)
82
- return xml_file
83
 
84
 
85
  def pdf2img(pdf_path: str):
 
4
  import requests
5
  import subprocess
6
  from PIL import Image
7
+ from music21 import converter
8
 
9
 
10
  def download(url: str, directory: str, filename: str):
 
57
  mscore = "D:/Program Files/MuseScore 3/bin/MuseScore3.exe"
58
 
59
 
60
+ def add_title_to_xml(xml_path: str):
61
+ """
62
+ 给 MIDI 文件添加标题,标题为去后缀的文件名。
63
+ 参数:
64
+ - midi_file_path: MIDI 文件的路径
65
+ """
66
+ # 获取文件名(不带路径)
67
+ file_name = os.path.basename(xml_path)
68
+ title = file_name.split(".")[0]
69
+ artist = "Transcripted by AI"
70
+ # 读取 MIDI 文件
71
+ midi_data = converter.parse(xml_path)
72
+ # 将标题添加到 MIDI 文件中
73
+ midi_data.metadata.title = title
74
+ midi_data.metadata.movementName = title
75
+ midi_data.metadata.composer = artist
76
+ # 保存修改后的 MIDI 文件
77
+ midi_data.write("musicxml", fp=xml_path)
78
+ return title, artist
79
+
80
+
81
  def xml2abc(xml_path: str):
82
  result = subprocess.run(
83
  f"python xml2abc.py {xml_path}", stdout=subprocess.PIPE, text=True
 
100
  xml_file = mid_file.replace(".mid", ".musicxml")
101
  command = [mscore, "-o", xml_file, mid_file]
102
  result = subprocess.run(command)
103
+ title, artist = add_title_to_xml(xml_file)
104
+ print(result)
105
+ return xml_file, title, artist
106
+
107
+
108
+ def xml2midi(xml_file: str):
109
+ midi_file = xml_file.replace(".musicxml", ".mid")
110
+ command = [mscore, "-o", midi_file, xml_file]
111
+ result = subprocess.run(command)
112
  print(result)
113
+ return midi_file
114
 
115
 
116
  def pdf2img(pdf_path: str):
midi/DataTypeConverters.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ from struct import pack, unpack
4
+
5
+ """
6
+ This module contains functions for reading and writing the special data types
7
+ that a midi file contains.
8
+ """
9
+
10
+ """
11
+ nibbles are four bits. A byte consists of two nibles.
12
+ hiBits==0xF0, loBits==0x0F Especially used for setting
13
+ channel and event in 1. byte of musical midi events
14
+ """
15
+
16
+
17
+
18
+ def getNibbles(byte):
19
+ """
20
+ Returns hi and lo bits in a byte as a tuple
21
+ >>> getNibbles(142)
22
+ (8, 14)
23
+
24
+ Asserts byte value in byte range
25
+ >>> getNibbles(256)
26
+ Traceback (most recent call last):
27
+ ...
28
+ ValueError: Byte value out of range 0-255: 256
29
+ """
30
+ if not 0 <= byte <= 255:
31
+ raise ValueError('Byte value out of range 0-255: %s' % byte)
32
+ return (byte >> 4 & 0xF, byte & 0xF)
33
+
34
+
35
+ def setNibbles(hiNibble, loNibble):
36
+ """
37
+ Returns byte with value set according to hi and lo bits
38
+ Asserts hiNibble and loNibble in range(16)
39
+ >>> setNibbles(8, 14)
40
+ 142
41
+
42
+ >>> setNibbles(8, 16)
43
+ Traceback (most recent call last):
44
+ ...
45
+ ValueError: Nible value out of range 0-15: (8, 16)
46
+ """
47
+ if not (0 <= hiNibble <= 15) or not (0 <= loNibble <= 15):
48
+ raise ValueError('Nible value out of range 0-15: (%s, %s)' % (hiNibble, loNibble))
49
+ return (hiNibble << 4) + loNibble
50
+
51
+
52
+
53
+ def readBew(value):
54
+ """
55
+ Reads string as big endian word, (asserts len(value) in [1,2,4])
56
+ >>> readBew('a���')
57
+ 1642193635L
58
+ >>> readBew('a�')
59
+ 25057
60
+ """
61
+ return unpack('>%s' % {1:'B', 2:'H', 4:'L'}[len(value)], value)[0]
62
+
63
+
64
+ def writeBew(value, length):
65
+ """
66
+ Write int as big endian formatted string, (asserts length in [1,2,4])
67
+ Difficult to print the result in doctest, so I do a simple roundabout test.
68
+ >>> readBew(writeBew(25057, 2))
69
+ 25057
70
+ >>> readBew(writeBew(1642193635L, 4))
71
+ 1642193635L
72
+ """
73
+ return pack('>%s' % {1:'B', 2:'H', 4:'L'}[length], value)
74
+
75
+
76
+
77
+ """
78
+ Variable Length Data (varlen) is a data format sprayed liberally throughout
79
+ a midi file. It can be anywhere from 1 to 4 bytes long.
80
+ If the 8'th bit is set in a byte another byte follows. The value is stored
81
+ in the lowest 7 bits of each byte. So max value is 4x7 bits = 28 bits.
82
+ """
83
+
84
+
85
+ def readVar(value):
86
+ """
87
+ Converts varlength format to integer. Just pass it 0 or more chars that
88
+ might be a varlen and it will only use the relevant chars.
89
+ use varLen(readVar(value)) to see how many bytes the integer value takes.
90
+ asserts len(value) >= 0
91
+ >>> readVar('�@')
92
+ 64
93
+ >>> readVar('���a')
94
+ 205042145
95
+ """
96
+ sum = 0
97
+ for byte in unpack('%sB' % len(value), value):
98
+ sum = (sum << 7) + (byte & 0x7F)
99
+ if not 0x80 & byte: break # stop after last byte
100
+ return sum
101
+
102
+
103
+
104
+ def varLen(value):
105
+ """
106
+ Returns the the number of bytes an integer will be when
107
+ converted to varlength
108
+ """
109
+ if value <= 127:
110
+ return 1
111
+ elif value <= 16383:
112
+ return 2
113
+ elif value <= 2097151:
114
+ return 3
115
+ else:
116
+ return 4
117
+
118
+
119
+ def writeVar(value):
120
+ "Converts an integer to varlength format"
121
+ sevens = to_n_bits(value, varLen(value))
122
+ for i in range(len(sevens)-1):
123
+ sevens[i] = sevens[i] | 0x80
124
+ return fromBytes(sevens)
125
+
126
+
127
+ def to_n_bits(value, length=1, nbits=7):
128
+ "returns the integer value as a sequence of nbits bytes"
129
+ bytes = [(value >> (i*nbits)) & 0x7F for i in range(length)]
130
+ bytes.reverse()
131
+ return bytes
132
+
133
+
134
+ def toBytes(value):
135
+ "Turns a string into a list of byte values"
136
+ return unpack('%sB' % len(value), value)
137
+
138
+
139
+ def fromBytes(value):
140
+ "Turns a list of bytes into a string"
141
+ if not value:
142
+ return ''
143
+ return pack('%sB' % len(value), *value)
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
midi/EventDispatcher.py ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ # std library
4
+ from struct import unpack
5
+
6
+ # custom
7
+ from .DataTypeConverters import readBew, readVar, varLen, toBytes
8
+
9
+ # uhh I don't really like this, but there are so many constants to
10
+ # import otherwise
11
+ from .constants import *
12
+
13
+
14
+ class EventDispatcher:
15
+
16
+
17
+ def __init__(self, outstream):
18
+
19
+ """
20
+
21
+ The event dispatcher generates events on the outstream.
22
+
23
+ """
24
+
25
+ # internal values, don't mess with 'em directly
26
+ self.outstream = outstream
27
+
28
+ # public flags
29
+
30
+ # A note_on with a velocity of 0x00 is actually the same as a
31
+ # note_off with a velocity of 0x40. When
32
+ # "convert_zero_velocity" is set, the zero velocity note_on's
33
+ # automatically gets converted into note_off's. This is a less
34
+ # suprising behaviour for those that are not into the intimate
35
+ # details of the midi spec.
36
+ self.convert_zero_velocity = 1
37
+
38
+ # If dispatch_continuos_controllers is true, continuos
39
+ # controllers gets dispatched to their defined handlers. Else
40
+ # they just trigger the "continuous_controller" event handler.
41
+ self.dispatch_continuos_controllers = 1 # NOT IMPLEMENTED YET
42
+
43
+ # If dispatch_meta_events is true, meta events get's dispatched
44
+ # to their defined events. Else they all they trigger the
45
+ # "meta_event" handler.
46
+ self.dispatch_meta_events = 1
47
+
48
+
49
+
50
+ def header(self, format, nTracks, division):
51
+ "Triggers the header event"
52
+ self.outstream.header(format, nTracks, division)
53
+
54
+
55
+ def start_of_track(self, current_track):
56
+ "Triggers the start of track event"
57
+
58
+ # I do this twice so that users can overwrite the
59
+ # start_of_track event handler without worrying whether the
60
+ # track number is updated correctly.
61
+ self.outstream.set_current_track(current_track)
62
+ self.outstream.start_of_track(current_track)
63
+
64
+
65
+ def sysex_event(self, data):
66
+ "Dispatcher for sysex events"
67
+ self.outstream.sysex_event(data)
68
+
69
+
70
+ def eof(self):
71
+ "End of file!"
72
+ self.outstream.eof()
73
+
74
+
75
+ def update_time(self, new_time=0, relative=1):
76
+ "Updates relative/absolute time."
77
+ self.outstream.update_time(new_time, relative)
78
+
79
+
80
+ def reset_time(self):
81
+ "Updates relative/absolute time."
82
+ self.outstream.reset_time()
83
+
84
+
85
+ # Event dispatchers for similar types of events
86
+
87
+
88
+ def channel_messages(self, hi_nible, channel, data):
89
+
90
+ "Dispatches channel messages"
91
+
92
+ stream = self.outstream
93
+ data = toBytes(data)
94
+
95
+ if (NOTE_ON & 0xF0) == hi_nible:
96
+ note, velocity = data
97
+ # note_on with velocity 0x00 are same as note
98
+ # off with velocity 0x40 according to spec!
99
+ if velocity==0 and self.convert_zero_velocity:
100
+ stream.note_off(channel, note, 0x40)
101
+ else:
102
+ stream.note_on(channel, note, velocity)
103
+
104
+ elif (NOTE_OFF & 0xF0) == hi_nible:
105
+ note, velocity = data
106
+ stream.note_off(channel, note, velocity)
107
+
108
+ elif (AFTERTOUCH & 0xF0) == hi_nible:
109
+ note, velocity = data
110
+ stream.aftertouch(channel, note, velocity)
111
+
112
+ elif (CONTINUOUS_CONTROLLER & 0xF0) == hi_nible:
113
+ controller, value = data
114
+ # A lot of the cc's are defined, so we trigger those directly
115
+ if self.dispatch_continuos_controllers:
116
+ self.continuous_controllers(channel, controller, value)
117
+ else:
118
+ stream.continuous_controller(channel, controller, value)
119
+
120
+ elif (PATCH_CHANGE & 0xF0) == hi_nible:
121
+ program = data[0]
122
+ stream.patch_change(channel, program)
123
+
124
+ elif (CHANNEL_PRESSURE & 0xF0) == hi_nible:
125
+ pressure = data[0]
126
+ stream.channel_pressure(channel, pressure)
127
+
128
+ elif (PITCH_BEND & 0xF0) == hi_nible:
129
+ hibyte, lobyte = data
130
+ value = (hibyte<<7) + lobyte
131
+ stream.pitch_bend(channel, value)
132
+
133
+ else:
134
+
135
+ raise ValueError('Illegal channel message!')
136
+
137
+
138
+
139
+ def continuous_controllers(self, channel, controller, value):
140
+
141
+ "Dispatches channel messages"
142
+
143
+ stream = self.outstream
144
+
145
+ # I am not really shure if I ought to dispatch continuous controllers
146
+ # There's so many of them that it can clutter up the OutStream
147
+ # classes.
148
+
149
+ # So I just trigger the default event handler
150
+ stream.continuous_controller(channel, controller, value)
151
+
152
+
153
+
154
+ def system_commons(self, common_type, common_data):
155
+
156
+ "Dispatches system common messages"
157
+
158
+ stream = self.outstream
159
+
160
+ # MTC Midi time code Quarter value
161
+ if common_type == MTC:
162
+ data = readBew(common_data)
163
+ msg_type = (data & 0x07) >> 4
164
+ values = (data & 0x0F)
165
+ stream.midi_time_code(msg_type, values)
166
+
167
+ elif common_type == SONG_POSITION_POINTER:
168
+ hibyte, lobyte = toBytes(common_data)
169
+ value = (hibyte<<7) + lobyte
170
+ stream.song_position_pointer(value)
171
+
172
+ elif common_type == SONG_SELECT:
173
+ data = readBew(common_data)
174
+ stream.song_select(data)
175
+
176
+ elif common_type == TUNING_REQUEST:
177
+ # no data then
178
+ stream.tuning_request(time=None)
179
+
180
+
181
+
182
+ def meta_event(self, meta_type, data):
183
+
184
+ "Dispatches meta events"
185
+
186
+ stream = self.outstream
187
+
188
+ # SEQUENCE_NUMBER = 0x00 (00 02 ss ss (seq-number))
189
+ if meta_type == SEQUENCE_NUMBER:
190
+ number = readBew(data)
191
+ stream.sequence_number(number)
192
+
193
+ # TEXT = 0x01 (01 len text...)
194
+ elif meta_type == TEXT:
195
+ stream.text(data)
196
+
197
+ # COPYRIGHT = 0x02 (02 len text...)
198
+ elif meta_type == COPYRIGHT:
199
+ stream.copyright(data)
200
+
201
+ # SEQUENCE_NAME = 0x03 (03 len text...)
202
+ elif meta_type == SEQUENCE_NAME:
203
+ stream.sequence_name(data)
204
+
205
+ # INSTRUMENT_NAME = 0x04 (04 len text...)
206
+ elif meta_type == INSTRUMENT_NAME:
207
+ stream.instrument_name(data)
208
+
209
+ # LYRIC = 0x05 (05 len text...)
210
+ elif meta_type == LYRIC:
211
+ stream.lyric(data)
212
+
213
+ # MARKER = 0x06 (06 len text...)
214
+ elif meta_type == MARKER:
215
+ stream.marker(data)
216
+
217
+ # CUEPOINT = 0x07 (07 len text...)
218
+ elif meta_type == CUEPOINT:
219
+ stream.cuepoint(data)
220
+
221
+ # PROGRAM_NAME = 0x08 (05 len text...)
222
+ elif meta_type == PROGRAM_NAME:
223
+ stream.program_name(data)
224
+
225
+ # DEVICE_NAME = 0x09 (09 len text...)
226
+ elif meta_type == DEVICE_NAME:
227
+ try: #p08 patch
228
+ stream.device_name(data)
229
+ except:
230
+ pass
231
+ # MIDI_CH_PREFIX = 0x20 (20 01 channel)
232
+ elif meta_type == MIDI_CH_PREFIX:
233
+ channel = readBew(data)
234
+ stream.midi_ch_prefix(channel)
235
+
236
+ # MIDI_PORT = 0x21 (21 01 port (legacy stuff))
237
+ elif meta_type == MIDI_PORT:
238
+ port = readBew(data)
239
+ stream.midi_port(port)
240
+
241
+ # END_OFF_TRACK = 0x2F (2F 00)
242
+ elif meta_type == END_OF_TRACK:
243
+ stream.end_of_track()
244
+
245
+ # TEMPO = 0x51 (51 03 tt tt tt (tempo in us/quarternote))
246
+ elif meta_type == TEMPO:
247
+ b1, b2, b3 = toBytes(data)
248
+ # uses 3 bytes to represent time between quarter
249
+ # notes in microseconds
250
+ stream.tempo((b1<<16) + (b2<<8) + b3)
251
+
252
+ # SMTP_OFFSET = 0x54 (54 05 hh mm ss ff xx)
253
+ elif meta_type == SMTP_OFFSET:
254
+ hour, minute, second, frame, framePart = toBytes(data)
255
+ stream.smtp_offset(
256
+ hour, minute, second, frame, framePart)
257
+
258
+ # TIME_SIGNATURE = 0x58 (58 04 nn dd cc bb)
259
+ elif meta_type == TIME_SIGNATURE:
260
+ nn, dd, cc, bb = toBytes(data)
261
+ stream.time_signature(nn, dd, cc, bb)
262
+
263
+ # KEY_SIGNATURE = 0x59 (59 02 sf mi)
264
+ elif meta_type == KEY_SIGNATURE:
265
+ sf, mi = toBytes(data)
266
+ stream.key_signature(sf, mi)
267
+
268
+ # SPECIFIC = 0x7F (Sequencer specific event)
269
+ elif meta_type == SPECIFIC:
270
+ meta_data = toBytes(data)
271
+ stream.sequencer_specific(meta_data)
272
+
273
+ # Handles any undefined meta events
274
+ else: # undefined meta type
275
+ meta_data = toBytes(data)
276
+ stream.meta_event(meta_type, meta_data)
277
+
278
+
279
+
280
+
281
+
282
+ if __name__ == '__main__':
283
+
284
+
285
+ from MidiToText import MidiToText
286
+
287
+ outstream = MidiToText()
288
+ dispatcher = EventDispatcher(outstream)
289
+ dispatcher.channel_messages(NOTE_ON, 0x00, '\x40\x40')
midi/MidiFileParser.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ # std library
4
+ from struct import unpack
5
+
6
+ # uhh I don't really like this, but there are so many constants to
7
+ # import otherwise
8
+ from .constants import *
9
+
10
+ from .EventDispatcher import EventDispatcher
11
+
12
+ class MidiFileParser:
13
+
14
+ """
15
+
16
+ The MidiFileParser is the lowest level parser that see the data as
17
+ midi data. It generates events that gets triggered on the outstream.
18
+
19
+ """
20
+
21
+ def __init__(self, raw_in, outstream):
22
+
23
+ """
24
+ raw_data is the raw content of a midi file as a string.
25
+ """
26
+
27
+ # internal values, don't mess with 'em directly
28
+ self.raw_in = raw_in
29
+ self.dispatch = EventDispatcher(outstream)
30
+
31
+ # Used to keep track of stuff
32
+ self._running_status = None
33
+
34
+
35
+
36
+
37
+ def parseMThdChunk(self):
38
+
39
+ "Parses the header chunk"
40
+
41
+ raw_in = self.raw_in
42
+
43
+ header_chunk_type = raw_in.nextSlice(4)
44
+ header_chunk_zise = raw_in.readBew(4)
45
+ # check if it is a proper midi file
46
+ if header_chunk_type != b'MThd':
47
+ raise TypeError("It is not a valid midi file!")
48
+
49
+ # Header values are at fixed locations, so no reason to be clever
50
+ self.format = raw_in.readBew(2)
51
+ self.nTracks = raw_in.readBew(2)
52
+ self.division = raw_in.readBew(2)
53
+
54
+ # Theoretically a header larger than 6 bytes can exist
55
+ # but no one has seen one in the wild
56
+ # But correctly ignore unknown data if it is though
57
+ if header_chunk_zise > 6:
58
+ raw_in.moveCursor(header_chunk_zise-6)
59
+
60
+ # call the header event handler on the stream
61
+ self.dispatch.header(self.format, self.nTracks, self.division)
62
+
63
+
64
+
65
+ def parseMTrkChunk(self):
66
+
67
+ "Parses a track chunk. This is the most important part of the parser."
68
+
69
+ # set time to 0 at start of a track
70
+ self.dispatch.reset_time()
71
+
72
+ dispatch = self.dispatch
73
+ raw_in = self.raw_in
74
+
75
+ # Trigger event at the start of a track
76
+ dispatch.start_of_track(self._current_track)
77
+ # position cursor after track header
78
+ raw_in.moveCursor(4)
79
+ # unsigned long is 4 bytes
80
+ tracklength = raw_in.readBew(4)
81
+ track_endposition = raw_in.getCursor() + tracklength # absolute position!
82
+
83
+ while raw_in.getCursor() < track_endposition:
84
+
85
+ # find relative time of the event
86
+ time = raw_in.readVarLen()
87
+ dispatch.update_time(time)
88
+
89
+ # be aware of running status!!!!
90
+ peak_ahead = raw_in.readBew(move_cursor=0)
91
+ if (peak_ahead & 0x80):
92
+ # the status byte has the high bit set, so it
93
+ # was not running data but proper status byte
94
+ status = self._running_status = raw_in.readBew()
95
+ else:
96
+ # use that darn running status
97
+ status = self._running_status
98
+ # could it be illegal data ?? Do we need to test for that?
99
+ # I need more example midi files to be shure.
100
+
101
+ # Also, while I am almost certain that no realtime
102
+ # messages will pop up in a midi file, I might need to
103
+ # change my mind later.
104
+
105
+ # we need to look at nibbles here
106
+ hi_nible, lo_nible = status & 0xF0, status & 0x0F
107
+
108
+ # match up with events
109
+
110
+ # Is it a meta_event ??
111
+ # these only exists in midi files, not in transmitted midi data
112
+ # In transmitted data META_EVENT (0xFF) is a system reset
113
+ if status == META_EVENT:
114
+ meta_type = raw_in.readBew()
115
+ meta_length = raw_in.readVarLen()
116
+ meta_data = raw_in.nextSlice(meta_length)
117
+ dispatch.meta_event(meta_type, meta_data)
118
+
119
+
120
+ # Is it a sysex_event ??
121
+ elif status == SYSTEM_EXCLUSIVE:
122
+ # ignore sysex events
123
+ sysex_length = raw_in.readVarLen()
124
+ # don't read sysex terminator
125
+ sysex_data = raw_in.nextSlice(sysex_length-1)
126
+ # only read last data byte if it is a sysex terminator
127
+ # It should allways be there, but better safe than sorry
128
+ if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE:
129
+ eo_sysex = raw_in.readBew()
130
+ dispatch.sysex_event(sysex_data)
131
+ # the sysex code has not been properly tested, and might be fishy!
132
+
133
+
134
+ # is it a system common event?
135
+ elif hi_nible == 0xF0: # Hi bits are set then
136
+ data_sizes = {
137
+ MTC:1,
138
+ SONG_POSITION_POINTER:2,
139
+ SONG_SELECT:1,
140
+ }
141
+ data_size = data_sizes.get(hi_nible, 0)
142
+ common_data = raw_in.nextSlice(data_size)
143
+ common_type = lo_nible
144
+ dispatch.system_common(common_type, common_data)
145
+
146
+
147
+ # Oh! Then it must be a midi event (channel voice message)
148
+ else:
149
+ data_sizes = {
150
+ PATCH_CHANGE:1,
151
+ CHANNEL_PRESSURE:1,
152
+ NOTE_OFF:2,
153
+ NOTE_ON:2,
154
+ AFTERTOUCH:2,
155
+ CONTINUOUS_CONTROLLER:2,
156
+ PITCH_BEND:2,
157
+ }
158
+ data_size = data_sizes.get(hi_nible, 0)
159
+ channel_data = raw_in.nextSlice(data_size)
160
+ event_type, channel = hi_nible, lo_nible
161
+ dispatch.channel_messages(event_type, channel, channel_data)
162
+
163
+
164
+ def parseMTrkChunks(self):
165
+ "Parses all track chunks."
166
+ for t in range(self.nTracks):
167
+ self._current_track = t
168
+ self.parseMTrkChunk() # this is where it's at!
169
+ self.dispatch.eof()
170
+
171
+
172
+
173
+ if __name__ == '__main__':
174
+
175
+ # get data
176
+ test_file = 'test/midifiles/minimal.mid'
177
+ test_file = 'test/midifiles/cubase-minimal.mid'
178
+ test_file = 'test/midifiles/Lola.mid'
179
+ # f = open(test_file, 'rb')
180
+ # raw_data = f.read()
181
+ # f.close()
182
+ #
183
+ #
184
+ # # do parsing
185
+ from MidiToText import MidiToText
186
+ from RawInstreamFile import RawInstreamFile
187
+
188
+ midi_in = MidiFileParser(RawInstreamFile(test_file), MidiToText())
189
+ midi_in.parseMThdChunk()
190
+ midi_in.parseMTrkChunks()
191
+
midi/MidiInFile.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ from .RawInstreamFile import RawInstreamFile
4
+ from .MidiFileParser import MidiFileParser
5
+
6
+
7
+ class MidiInFile:
8
+
9
+ """
10
+
11
+ Parses a midi file, and triggers the midi events on the outStream
12
+ object.
13
+
14
+ Get example data from a minimal midi file, generated with cubase.
15
+ >>> test_file = 'C:/Documents and Settings/maxm/Desktop/temp/midi/src/midi/tests/midifiles/minimal-cubase-type0.mid'
16
+
17
+ Do parsing, and generate events with MidiToText,
18
+ so we can see what a minimal midi file contains
19
+ >>> from MidiToText import MidiToText
20
+ >>> midi_in = MidiInFile(MidiToText(), test_file)
21
+ >>> midi_in.read()
22
+ format: 0, nTracks: 1, division: 480
23
+ ----------------------------------
24
+ <BLANKLINE>
25
+ Start - track #0
26
+ sequence_name: Type 0
27
+ tempo: 500000
28
+ time_signature: 4 2 24 8
29
+ note_on - ch:00, note:48, vel:64 time:0
30
+ note_off - ch:00, note:48, vel:40 time:480
31
+ End of track
32
+ <BLANKLINE>
33
+ End of file
34
+
35
+
36
+ """
37
+
38
+ def __init__(self, outStream, infile):
39
+ # these could also have been mixins, would that be better? Nah!
40
+ self.raw_in = RawInstreamFile(infile)
41
+ self.parser = MidiFileParser(self.raw_in, outStream)
42
+
43
+
44
+ def read(self):
45
+ "Start parsing the file"
46
+ p = self.parser
47
+ p.parseMThdChunk()
48
+ p.parseMTrkChunks()
49
+
50
+
51
+ def setData(self, data=''):
52
+ "Sets the data from a plain string"
53
+ self.raw_in.setData(data)
54
+
55
+
midi/MidiInStream.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ from MidiOutStream import MidiOutStream
4
+
5
+ class MidiInStream:
6
+
7
+ """
8
+ Takes midi events from the midi input and calls the apropriate
9
+ method in the eventhandler object
10
+ """
11
+
12
+ def __init__(self, midiOutStream, device):
13
+
14
+ """
15
+
16
+ Sets a default output stream, and sets the device from where
17
+ the input comes
18
+
19
+ """
20
+
21
+ if midiOutStream is None:
22
+ self.midiOutStream = MidiOutStream()
23
+ else:
24
+ self.midiOutStream = midiOutStream
25
+
26
+
27
+ def close(self):
28
+
29
+ """
30
+ Stop the MidiInstream
31
+ """
32
+
33
+
34
+ def read(self, time=0):
35
+
36
+ """
37
+
38
+ Start the MidiInstream.
39
+
40
+ "time" sets timer to specific start value.
41
+
42
+ """
43
+
44
+
45
+ def resetTimer(self, time=0):
46
+ """
47
+
48
+ Resets the timer, probably a good idea if there is some kind
49
+ of looping going on
50
+
51
+ """
52
+
midi/MidiOutFile.py ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ from MidiOutStream import MidiOutStream
4
+ from RawOutstreamFile import RawOutstreamFile
5
+
6
+ from constants import *
7
+ from DataTypeConverters import fromBytes, writeVar
8
+
9
+ class MidiOutFile(MidiOutStream):
10
+
11
+
12
+ """
13
+ MidiOutFile is an eventhandler that subclasses MidiOutStream.
14
+ """
15
+
16
+
17
+ def __init__(self, raw_out=''):
18
+
19
+ self.raw_out = RawOutstreamFile(raw_out)
20
+ MidiOutStream.__init__(self)
21
+
22
+
23
+ def write(self):
24
+ self.raw_out.write()
25
+
26
+
27
+ def event_slice(self, slc):
28
+ """
29
+ Writes the slice of an event to the current track. Correctly
30
+ inserting a varlen timestamp too.
31
+ """
32
+ trk = self._current_track_buffer
33
+ trk.writeVarLen(self.rel_time())
34
+ trk.writeSlice(slc)
35
+
36
+
37
+ #####################
38
+ ## Midi events
39
+
40
+
41
+ def note_on(self, channel=0, note=0x40, velocity=0x40):
42
+
43
+ """
44
+ channel: 0-15
45
+ note, velocity: 0-127
46
+ """
47
+ slc = fromBytes([NOTE_ON + channel, note, velocity])
48
+ self.event_slice(slc)
49
+
50
+
51
+ def note_off(self, channel=0, note=0x40, velocity=0x40):
52
+
53
+ """
54
+ channel: 0-15
55
+ note, velocity: 0-127
56
+ """
57
+ slc = fromBytes([NOTE_OFF + channel, note, velocity])
58
+ self.event_slice(slc)
59
+
60
+
61
+ def aftertouch(self, channel=0, note=0x40, velocity=0x40):
62
+
63
+ """
64
+ channel: 0-15
65
+ note, velocity: 0-127
66
+ """
67
+ slc = fromBytes([AFTERTOUCH + channel, note, velocity])
68
+ self.event_slice(slc)
69
+
70
+
71
+ def continuous_controller(self, channel, controller, value):
72
+
73
+ """
74
+ channel: 0-15
75
+ controller, value: 0-127
76
+ """
77
+ slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value])
78
+ self.event_slice(slc)
79
+ # These should probably be implemented
80
+ # http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums
81
+
82
+
83
+ def patch_change(self, channel, patch):
84
+
85
+ """
86
+ channel: 0-15
87
+ patch: 0-127
88
+ """
89
+ slc = fromBytes([PATCH_CHANGE + channel, patch])
90
+ self.event_slice(slc)
91
+
92
+
93
+ def channel_pressure(self, channel, pressure):
94
+
95
+ """
96
+ channel: 0-15
97
+ pressure: 0-127
98
+ """
99
+ slc = fromBytes([CHANNEL_PRESSURE + channel, pressure])
100
+ self.event_slice(slc)
101
+
102
+
103
+ def pitch_bend(self, channel, value):
104
+
105
+ """
106
+ channel: 0-15
107
+ value: 0-16383
108
+ """
109
+ msb = (value>>7) & 0xFF
110
+ lsb = value & 0xFF
111
+ slc = fromBytes([PITCH_BEND + channel, msb, lsb])
112
+ self.event_slice(slc)
113
+
114
+
115
+
116
+
117
+ #####################
118
+ ## System Exclusive
119
+
120
+ # def sysex_slice(sysex_type, data):
121
+ # ""
122
+ # sysex_len = writeVar(len(data)+1)
123
+ # self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE)
124
+ #
125
+ def system_exclusive(self, data):
126
+
127
+ """
128
+ data: list of values in range(128)
129
+ """
130
+ sysex_len = writeVar(len(data)+1)
131
+ self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE))
132
+
133
+
134
+ #####################
135
+ ## Common events
136
+
137
+ def midi_time_code(self, msg_type, values):
138
+ """
139
+ msg_type: 0-7
140
+ values: 0-15
141
+ """
142
+ value = (msg_type<<4) + values
143
+ self.event_slice(fromBytes([MIDI_TIME_CODE, value]))
144
+
145
+
146
+ def song_position_pointer(self, value):
147
+
148
+ """
149
+ value: 0-16383
150
+ """
151
+ lsb = (value & 0x7F)
152
+ msb = (value >> 7) & 0x7F
153
+ self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb]))
154
+
155
+
156
+ def song_select(self, songNumber):
157
+
158
+ """
159
+ songNumber: 0-127
160
+ """
161
+ self.event_slice(fromBytes([SONG_SELECT, songNumber]))
162
+
163
+
164
+ def tuning_request(self):
165
+
166
+ """
167
+ No values passed
168
+ """
169
+ self.event_slice(chr(TUNING_REQUEST))
170
+
171
+
172
+ #########################
173
+ # header does not really belong here. But anyhoo!!!
174
+
175
+ def header(self, format=0, nTracks=1, division=96):
176
+
177
+ """
178
+ format: type of midi file in [0,1,2]
179
+ nTracks: number of tracks. 1 track for type 0 file
180
+ division: timing division ie. 96 ppq.
181
+
182
+ """
183
+ raw = self.raw_out
184
+ raw.writeSlice('MThd')
185
+ bew = raw.writeBew
186
+ bew(6, 4) # header size
187
+ bew(format, 2)
188
+ bew(nTracks, 2)
189
+ bew(division, 2)
190
+
191
+
192
+ def eof(self):
193
+
194
+ """
195
+ End of file. No more events to be processed.
196
+ """
197
+ # just write the file then.
198
+ self.write()
199
+
200
+
201
+ #####################
202
+ ## meta events
203
+
204
+
205
+ def meta_slice(self, meta_type, data_slice):
206
+ "Writes a meta event"
207
+ slc = fromBytes([META_EVENT, meta_type]) + \
208
+ writeVar(len(data_slice)) + data_slice
209
+ self.event_slice(slc)
210
+
211
+
212
+ def meta_event(self, meta_type, data):
213
+ """
214
+ Handles any undefined meta events
215
+ """
216
+ self.meta_slice(meta_type, fromBytes(data))
217
+
218
+
219
+ def start_of_track(self, n_track=0):
220
+ """
221
+ n_track: number of track
222
+ """
223
+ self._current_track_buffer = RawOutstreamFile()
224
+ self.reset_time()
225
+ self._current_track += 1
226
+
227
+
228
+ def end_of_track(self):
229
+ """
230
+ Writes the track to the buffer.
231
+ """
232
+ raw = self.raw_out
233
+ raw.writeSlice(TRACK_HEADER)
234
+ track_data = self._current_track_buffer.getvalue()
235
+ # wee need to know size of track data.
236
+ eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0])
237
+ raw.writeBew(len(track_data)+len(eot_slice), 4)
238
+ # then write
239
+ raw.writeSlice(track_data)
240
+ raw.writeSlice(eot_slice)
241
+
242
+
243
+
244
+ def sequence_number(self, value):
245
+
246
+ """
247
+ value: 0-65535
248
+ """
249
+ self.meta_slice(meta_type, writeBew(value, 2))
250
+
251
+
252
+ def text(self, text):
253
+ """
254
+ Text event
255
+ text: string
256
+ """
257
+ self.meta_slice(TEXT, text)
258
+
259
+
260
+ def copyright(self, text):
261
+
262
+ """
263
+ Copyright notice
264
+ text: string
265
+ """
266
+ self.meta_slice(COPYRIGHT, text)
267
+
268
+
269
+ def sequence_name(self, text):
270
+ """
271
+ Sequence/track name
272
+ text: string
273
+ """
274
+ self.meta_slice(SEQUENCE_NAME, text)
275
+
276
+
277
+ def instrument_name(self, text):
278
+
279
+ """
280
+ text: string
281
+ """
282
+ self.meta_slice(INSTRUMENT_NAME, text)
283
+
284
+
285
+ def lyric(self, text):
286
+
287
+ """
288
+ text: string
289
+ """
290
+ self.meta_slice(LYRIC, text)
291
+
292
+
293
+ def marker(self, text):
294
+
295
+ """
296
+ text: string
297
+ """
298
+ self.meta_slice(MARKER, text)
299
+
300
+
301
+ def cuepoint(self, text):
302
+
303
+ """
304
+ text: string
305
+ """
306
+ self.meta_slice(CUEPOINT, text)
307
+
308
+
309
+ def midi_ch_prefix(self, channel):
310
+
311
+ """
312
+ channel: midi channel for subsequent data
313
+ (deprecated in the spec)
314
+ """
315
+ self.meta_slice(MIDI_CH_PREFIX, chr(channel))
316
+
317
+
318
+ def midi_port(self, value):
319
+
320
+ """
321
+ value: Midi port (deprecated in the spec)
322
+ """
323
+ self.meta_slice(MIDI_CH_PREFIX, chr(value))
324
+
325
+
326
+ def tempo(self, value):
327
+
328
+ """
329
+ value: 0-2097151
330
+ tempo in us/quarternote
331
+ (to calculate value from bpm: int(60,000,000.00 / BPM))
332
+ """
333
+ hb, mb, lb = (value>>16 & 0xff), (value>>8 & 0xff), (value & 0xff)
334
+ self.meta_slice(TEMPO, fromBytes([hb, mb, lb]))
335
+
336
+
337
+ def smtp_offset(self, hour, minute, second, frame, framePart):
338
+
339
+ """
340
+ hour,
341
+ minute,
342
+ second: 3 bytes specifying the hour (0-23), minutes (0-59) and
343
+ seconds (0-59), respectively. The hour should be
344
+ encoded with the SMPTE format, just as it is in MIDI
345
+ Time Code.
346
+ frame: A byte specifying the number of frames per second (one
347
+ of : 24, 25, 29, 30).
348
+ framePart: A byte specifying the number of fractional frames,
349
+ in 100ths of a frame (even in SMPTE-based tracks
350
+ using a different frame subdivision, defined in the
351
+ MThd chunk).
352
+ """
353
+ self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart]))
354
+
355
+
356
+
357
+ def time_signature(self, nn, dd, cc, bb):
358
+
359
+ """
360
+ nn: Numerator of the signature as notated on sheet music
361
+ dd: Denominator of the signature as notated on sheet music
362
+ The denominator is a negative power of 2: 2 = quarter
363
+ note, 3 = eighth, etc.
364
+ cc: The number of MIDI clocks in a metronome click
365
+ bb: The number of notated 32nd notes in a MIDI quarter note
366
+ (24 MIDI clocks)
367
+ """
368
+ self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb]))
369
+
370
+
371
+
372
+
373
+ def key_signature(self, sf, mi):
374
+
375
+ """
376
+ sf: is a byte specifying the number of flats (-ve) or sharps
377
+ (+ve) that identifies the key signature (-7 = 7 flats, -1
378
+ = 1 flat, 0 = key of C, 1 = 1 sharp, etc).
379
+ mi: is a byte specifying a major (0) or minor (1) key.
380
+ """
381
+ self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi]))
382
+
383
+
384
+
385
+ def sequencer_specific(self, data):
386
+
387
+ """
388
+ data: The data as byte values
389
+ """
390
+ self.meta_slice(SEQUENCER_SPECIFIC, data)
391
+
392
+
393
+
394
+
395
+
396
+ # #####################
397
+ # ## realtime events
398
+
399
+ # These are of no use in a midi file, so they are ignored!!!
400
+
401
+ # def timing_clock(self):
402
+ # def song_start(self):
403
+ # def song_stop(self):
404
+ # def song_continue(self):
405
+ # def active_sensing(self):
406
+ # def system_reset(self):
407
+
408
+
409
+
410
+ if __name__ == '__main__':
411
+
412
+ out_file = 'test/midifiles/midiout.mid'
413
+ midi = MidiOutFile(out_file)
414
+
415
+ #format: 0, nTracks: 1, division: 480
416
+ #----------------------------------
417
+ #
418
+ #Start - track #0
419
+ #sequence_name: Type 0
420
+ #tempo: 500000
421
+ #time_signature: 4 2 24 8
422
+ #note_on - ch:00, note:48, vel:64 time:0
423
+ #note_off - ch:00, note:48, vel:40 time:480
424
+ #End of track
425
+ #
426
+ #End of file
427
+
428
+
429
+ midi.header(0, 1, 480)
430
+
431
+ midi.start_of_track()
432
+ midi.sequence_name('Type 0')
433
+ midi.tempo(750000)
434
+ midi.time_signature(4, 2, 24, 8)
435
+ ch = 0
436
+ for i in range(127):
437
+ midi.note_on(ch, i, 0x64)
438
+ midi.update_time(96)
439
+ midi.note_off(ch, i, 0x40)
440
+ midi.update_time(0)
441
+
442
+ midi.update_time(0)
443
+ midi.end_of_track()
444
+
445
+ midi.eof() # currently optional, should it do the write instead of write??
446
+
447
+
448
+ midi.write()
midi/MidiOutStream.py ADDED
@@ -0,0 +1,471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ class MidiOutStream:
4
+
5
+
6
+ """
7
+
8
+ MidiOutstream is Basically an eventhandler. It is the most central
9
+ class in the Midi library. You use it both for writing events to
10
+ an output stream, and as an event handler for an input stream.
11
+
12
+ This makes it extremely easy to take input from one stream and
13
+ send it to another. Ie. if you want to read a Midi file, do some
14
+ processing, and send it to a midiport.
15
+
16
+ All time values are in absolute values from the opening of a
17
+ stream. To calculate time values, please use the MidiTime and
18
+ MidiDeltaTime classes.
19
+
20
+ """
21
+
22
+ def __init__(self):
23
+
24
+ # the time is rather global, so it needs to be stored
25
+ # here. Otherwise there would be no really simple way to
26
+ # calculate it. The alternative would be to have each event
27
+ # handler do it. That sucks even worse!
28
+ self._absolute_time = 0
29
+ self._relative_time = 0
30
+ self._current_track = 0
31
+ self._running_status = None
32
+
33
+ # time handling event handlers. They should be overwritten with care
34
+
35
+ def update_time(self, new_time=0, relative=1):
36
+ """
37
+ Updates the time, if relative is true, new_time is relative,
38
+ else it's absolute.
39
+ """
40
+ if relative:
41
+ self._relative_time = new_time
42
+ self._absolute_time += new_time
43
+ else:
44
+ self._relative_time = new_time - self._absolute_time
45
+ self._absolute_time = new_time
46
+
47
+ def reset_time(self):
48
+ """
49
+ reset time to 0
50
+ """
51
+ self._relative_time = 0
52
+ self._absolute_time = 0
53
+
54
+ def rel_time(self):
55
+ "Returns the relative time"
56
+ return self._relative_time
57
+
58
+ def abs_time(self):
59
+ "Returns the absolute time"
60
+ return self._absolute_time
61
+
62
+ # running status methods
63
+
64
+ def reset_run_stat(self):
65
+ "Invalidates the running status"
66
+ self._running_status = None
67
+
68
+ def set_run_stat(self, new_status):
69
+ "Set the new running status"
70
+ self._running_status = new_status
71
+
72
+ def get_run_stat(self):
73
+ "Set the new running status"
74
+ return self._running_status
75
+
76
+ # track handling event handlers
77
+
78
+ def set_current_track(self, new_track):
79
+ "Sets the current track number"
80
+ self._current_track = new_track
81
+
82
+ def get_current_track(self):
83
+ "Returns the current track number"
84
+ return self._current_track
85
+
86
+
87
+ #####################
88
+ ## Midi events
89
+
90
+
91
+ def channel_message(self, message_type, channel, data):
92
+ """The default event handler for channel messages"""
93
+ pass
94
+
95
+
96
+ def note_on(self, channel=0, note=0x40, velocity=0x40):
97
+
98
+ """
99
+ channel: 0-15
100
+ note, velocity: 0-127
101
+ """
102
+ pass
103
+
104
+
105
+ def note_off(self, channel=0, note=0x40, velocity=0x40):
106
+
107
+ """
108
+ channel: 0-15
109
+ note, velocity: 0-127
110
+ """
111
+ pass
112
+
113
+
114
+ def aftertouch(self, channel=0, note=0x40, velocity=0x40):
115
+
116
+ """
117
+ channel: 0-15
118
+ note, velocity: 0-127
119
+ """
120
+ pass
121
+
122
+
123
+ def continuous_controller(self, channel, controller, value):
124
+
125
+ """
126
+ channel: 0-15
127
+ controller, value: 0-127
128
+ """
129
+ pass
130
+
131
+
132
+ def patch_change(self, channel, patch):
133
+
134
+ """
135
+ channel: 0-15
136
+ patch: 0-127
137
+ """
138
+ pass
139
+
140
+
141
+ def channel_pressure(self, channel, pressure):
142
+
143
+ """
144
+ channel: 0-15
145
+ pressure: 0-127
146
+ """
147
+ pass
148
+
149
+
150
+ def pitch_bend(self, channel, value):
151
+
152
+ """
153
+ channel: 0-15
154
+ value: 0-16383
155
+
156
+ """
157
+ pass
158
+
159
+
160
+
161
+
162
+ #####################
163
+ ## System Exclusive
164
+
165
+ def system_exclusive(self, data):
166
+
167
+ """
168
+ data: list of values in range(128)
169
+ """
170
+ pass
171
+
172
+
173
+ #####################
174
+ ## Common events
175
+
176
+ def song_position_pointer(self, value):
177
+
178
+ """
179
+ value: 0-16383
180
+ """
181
+ pass
182
+
183
+
184
+ def song_select(self, songNumber):
185
+
186
+ """
187
+ songNumber: 0-127
188
+ """
189
+ pass
190
+
191
+
192
+ def tuning_request(self):
193
+
194
+ """
195
+ No values passed
196
+ """
197
+ pass
198
+
199
+
200
+ def midi_time_code(self, msg_type, values):
201
+ """
202
+ msg_type: 0-7
203
+ values: 0-15
204
+ """
205
+ pass
206
+
207
+
208
+ #########################
209
+ # header does not really belong here. But anyhoo!!!
210
+
211
+ def header(self, format=0, nTracks=1, division=96):
212
+
213
+ """
214
+ format: type of midi file in [1,2]
215
+ nTracks: number of tracks
216
+ division: timing division
217
+ """
218
+ pass
219
+
220
+
221
+ def eof(self):
222
+
223
+ """
224
+ End of file. No more events to be processed.
225
+ """
226
+ pass
227
+
228
+
229
+ #####################
230
+ ## meta events
231
+
232
+
233
+ def meta_event(self, meta_type, data):
234
+
235
+ """
236
+ Handles any undefined meta events
237
+ """
238
+ pass
239
+
240
+
241
+ def start_of_track(self, n_track=0):
242
+
243
+ """
244
+ n_track: number of track
245
+ """
246
+ pass
247
+
248
+
249
+ def end_of_track(self):
250
+
251
+ """
252
+ n_track: number of track
253
+ """
254
+ pass
255
+
256
+
257
+ def sequence_number(self, value):
258
+
259
+ """
260
+ value: 0-16383
261
+ """
262
+ pass
263
+
264
+
265
+ def text(self, text):
266
+
267
+ """
268
+ Text event
269
+ text: string
270
+ """
271
+ pass
272
+
273
+
274
+ def copyright(self, text):
275
+
276
+ """
277
+ Copyright notice
278
+ text: string
279
+ """
280
+ pass
281
+
282
+
283
+ def sequence_name(self, text):
284
+
285
+ """
286
+ Sequence/track name
287
+ text: string
288
+ """
289
+ pass
290
+
291
+
292
+ def instrument_name(self, text):
293
+
294
+ """
295
+ text: string
296
+ """
297
+ pass
298
+
299
+
300
+ def lyric(self, text):
301
+
302
+ """
303
+ text: string
304
+ """
305
+ pass
306
+
307
+
308
+ def marker(self, text):
309
+
310
+ """
311
+ text: string
312
+ """
313
+ pass
314
+
315
+
316
+ def cuepoint(self, text):
317
+
318
+ """
319
+ text: string
320
+ """
321
+ pass
322
+
323
+
324
+ def midi_ch_prefix(self, channel):
325
+
326
+ """
327
+ channel: midi channel for subsequent data (deprecated in the spec)
328
+ """
329
+ pass
330
+
331
+
332
+ def midi_port(self, value):
333
+
334
+ """
335
+ value: Midi port (deprecated in the spec)
336
+ """
337
+ pass
338
+
339
+
340
+ def tempo(self, value):
341
+
342
+ """
343
+ value: 0-2097151
344
+ tempo in us/quarternote
345
+ (to calculate value from bpm: int(60,000,000.00 / BPM))
346
+ """
347
+ pass
348
+
349
+
350
+ def smtp_offset(self, hour, minute, second, frame, framePart):
351
+
352
+ """
353
+ hour,
354
+ minute,
355
+ second: 3 bytes specifying the hour (0-23), minutes (0-59) and
356
+ seconds (0-59), respectively. The hour should be
357
+ encoded with the SMPTE format, just as it is in MIDI
358
+ Time Code.
359
+ frame: A byte specifying the number of frames per second (one
360
+ of : 24, 25, 29, 30).
361
+ framePart: A byte specifying the number of fractional frames,
362
+ in 100ths of a frame (even in SMPTE-based tracks
363
+ using a different frame subdivision, defined in the
364
+ MThd chunk).
365
+ """
366
+ pass
367
+
368
+
369
+
370
+ def time_signature(self, nn, dd, cc, bb):
371
+
372
+ """
373
+ nn: Numerator of the signature as notated on sheet music
374
+ dd: Denominator of the signature as notated on sheet music
375
+ The denominator is a negative power of 2: 2 = quarter
376
+ note, 3 = eighth, etc.
377
+ cc: The number of MIDI clocks in a metronome click
378
+ bb: The number of notated 32nd notes in a MIDI quarter note
379
+ (24 MIDI clocks)
380
+ """
381
+ pass
382
+
383
+
384
+
385
+ def key_signature(self, sf, mi):
386
+
387
+ """
388
+ sf: is a byte specifying the number of flats (-ve) or sharps
389
+ (+ve) that identifies the key signature (-7 = 7 flats, -1
390
+ = 1 flat, 0 = key of C, 1 = 1 sharp, etc).
391
+ mi: is a byte specifying a major (0) or minor (1) key.
392
+ """
393
+ pass
394
+
395
+
396
+
397
+ def sequencer_specific(self, data):
398
+
399
+ """
400
+ data: The data as byte values
401
+ """
402
+ pass
403
+
404
+
405
+
406
+
407
+ #####################
408
+ ## realtime events
409
+
410
+ def timing_clock(self):
411
+
412
+ """
413
+ No values passed
414
+ """
415
+ pass
416
+
417
+
418
+
419
+ def song_start(self):
420
+
421
+ """
422
+ No values passed
423
+ """
424
+ pass
425
+
426
+
427
+
428
+ def song_stop(self):
429
+
430
+ """
431
+ No values passed
432
+ """
433
+ pass
434
+
435
+
436
+
437
+ def song_continue(self):
438
+
439
+ """
440
+ No values passed
441
+ """
442
+ pass
443
+
444
+
445
+
446
+ def active_sensing(self):
447
+
448
+ """
449
+ No values passed
450
+ """
451
+ pass
452
+
453
+
454
+
455
+ def system_reset(self):
456
+
457
+ """
458
+ No values passed
459
+ """
460
+ pass
461
+
462
+
463
+
464
+ if __name__ == '__main__':
465
+
466
+ midiOut = MidiOutStream()
467
+ midiOut.update_time(0,0)
468
+ midiOut.note_on(0, 63, 127)
469
+ midiOut.note_off(0, 63, 127)
470
+
471
+
midi/MidiToText.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ from MidiOutStream import MidiOutStream
4
+
5
+
6
+ class MidiToText(MidiOutStream):
7
+
8
+ """
9
+ This class renders a midi file as text. It is mostly used for debugging
10
+ """
11
+
12
+ #############################
13
+ # channel events
14
+
15
+ def channel_message(self, message_type, channel, data):
16
+ """The default event handler for channel messages"""
17
+ print('message_type:%X, channel:%X, data size:%X' %
18
+ (message_type, channel, len(data)))
19
+
20
+ def note_on(self, channel=0, note=0x40, velocity=0x40):
21
+ print('note_on - ch:%02X, note:%02X, vel:%02X time:%s' %
22
+ (channel, note, velocity, self.rel_time()))
23
+
24
+ def note_off(self, channel=0, note=0x40, velocity=0x40):
25
+ print('note_off - ch:%02X, note:%02X, vel:%02X time:%s' %
26
+ (channel, note, velocity, self.rel_time()))
27
+
28
+ def aftertouch(self, channel=0, note=0x40, velocity=0x40):
29
+ print('aftertouch', channel, note, velocity)
30
+
31
+ def continuous_controller(self, channel, controller, value):
32
+ print('controller - ch: %02X, cont: #%02X, value: %02X' %
33
+ (channel, controller, value))
34
+
35
+ def patch_change(self, channel, patch):
36
+ print('patch_change - ch:%02X, patch:%02X' % (channel, patch))
37
+
38
+ def channel_pressure(self, channel, pressure):
39
+ print('channel_pressure', channel, pressure)
40
+
41
+ def pitch_bend(self, channel, value):
42
+ print('pitch_bend ch:%s, value:%s' % (channel, value))
43
+
44
+ #####################
45
+ # Common events
46
+
47
+ def system_exclusive(self, data):
48
+ print('system_exclusive - data size: %s' % len(data))
49
+
50
+ def song_position_pointer(self, value):
51
+ print('song_position_pointer: %s' % value)
52
+
53
+ def song_select(self, songNumber):
54
+ print('song_select: %s' % songNumber)
55
+
56
+ def tuning_request(self):
57
+ print('tuning_request')
58
+
59
+ def midi_time_code(self, msg_type, values):
60
+ print('midi_time_code - msg_type: %s, values: %s' % (msg_type, values))
61
+
62
+ #########################
63
+ # header does not really belong here. But anyhoo!!!
64
+
65
+ def header(self, format=0, nTracks=1, division=96):
66
+ print('format: %s, nTracks: %s, division: %s' %
67
+ (format, nTracks, division))
68
+ print('----------------------------------')
69
+ print('')
70
+
71
+ def eof(self):
72
+ print('End of file')
73
+
74
+ def start_of_track(self, n_track=0):
75
+ print('Start - track #%s' % n_track)
76
+
77
+ def end_of_track(self):
78
+ print('End of track')
79
+ print('')
80
+
81
+ ###############
82
+ # sysex event
83
+
84
+ def sysex_event(self, data):
85
+ print('sysex_event - datasize: %X' % len(data))
86
+
87
+ #####################
88
+ # meta events
89
+
90
+ def meta_event(self, meta_type, data):
91
+ print('undefined_meta_event:', meta_type, len(data))
92
+
93
+ def sequence_number(self, value):
94
+ print('sequence_number', value)
95
+
96
+ def text(self, text):
97
+ print('text', text)
98
+
99
+ def copyright(self, text):
100
+ print('copyright', text)
101
+
102
+ def sequence_name(self, text):
103
+ print('sequence_name:', text)
104
+
105
+ def instrument_name(self, text):
106
+ print('instrument_name:', text)
107
+
108
+ def lyric(self, text):
109
+ print('lyric', text)
110
+
111
+ def marker(self, text):
112
+ print('marker', text)
113
+
114
+ def cuepoint(self, text):
115
+ print('cuepoint', text)
116
+
117
+ def midi_ch_prefix(self, channel):
118
+ print('midi_ch_prefix', channel)
119
+
120
+ def midi_port(self, value):
121
+ print('midi_port:', value)
122
+
123
+ def tempo(self, value):
124
+ print('tempo:', value)
125
+
126
+ def smtp_offset(self, hour, minute, second, frame, framePart):
127
+ print('smtp_offset', hour, minute, second, frame, framePart)
128
+
129
+ def time_signature(self, nn, dd, cc, bb):
130
+ print('time_signature:', nn, dd, cc, bb)
131
+
132
+ def key_signature(self, sf, mi):
133
+ print('key_signature', sf, mi)
134
+
135
+ def sequencer_specific(self, data):
136
+ print('sequencer_specific', len(data))
137
+
138
+
139
+ if __name__ == '__main__':
140
+
141
+ # get data
142
+ test_file = 'test/midifiles/minimal.mid'
143
+ f = open(test_file, 'rb')
144
+
145
+ # do parsing
146
+ from MidiInFile import MidiInFile
147
+ midiIn = MidiInFile(MidiToText(), f)
148
+ midiIn.read()
149
+ f.close()
midi/RawInstreamFile.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ import sys
4
+ PY3 = sys.version_info.major > 2
5
+
6
+ if PY3:
7
+ unichr = chr
8
+ xrange = range
9
+ def unicode(s):
10
+ return s
11
+ max_int = sys.maxsize
12
+ basestring = str
13
+
14
+ # standard library imports
15
+ from struct import unpack
16
+
17
+ # custom import
18
+ from .DataTypeConverters import readBew, readVar, varLen
19
+
20
+
21
+ class RawInstreamFile:
22
+
23
+ """
24
+
25
+ It parses and reads data from an input file. It takes care of big
26
+ endianess, and keeps track of the cursor position. The midi parser
27
+ only reads from this object. Never directly from the file.
28
+
29
+ """
30
+
31
+ def __init__(self, infile=''):
32
+ """
33
+ If 'file' is a string we assume it is a path and read from
34
+ that file.
35
+ If it is a file descriptor we read from the file, but we don't
36
+ close it.
37
+ Midi files are usually pretty small, so it should be safe to
38
+ copy them into memory.
39
+ """
40
+ if infile:
41
+ if isinstance(infile, basestring):
42
+ infile = open(infile, 'rb')
43
+ self.data = infile.read()
44
+ infile.close()
45
+ else:
46
+ # don't close the f
47
+ self.data = infile.read()
48
+ else:
49
+ self.data = ''
50
+ # start at beginning ;-)
51
+ self.cursor = 0
52
+
53
+
54
+ # setting up data manually
55
+
56
+ def setData(self, data=''):
57
+ "Sets the data from a string."
58
+ self.data = data
59
+
60
+ # cursor operations
61
+
62
+ def setCursor(self, position=0):
63
+ "Sets the absolute position if the cursor"
64
+ self.cursor = position
65
+
66
+
67
+ def getCursor(self):
68
+ "Returns the value of the cursor"
69
+ return self.cursor
70
+
71
+
72
+ def moveCursor(self, relative_position=0):
73
+ "Moves the cursor to a new relative position"
74
+ self.cursor += relative_position
75
+
76
+ # native data reading functions
77
+
78
+ def nextSlice(self, length, move_cursor=1):
79
+ "Reads the next text slice from the raw data, with length"
80
+ c = self.cursor
81
+ slc = self.data[c:c+length]
82
+ if move_cursor:
83
+ self.moveCursor(length)
84
+ return slc
85
+
86
+
87
+ def readBew(self, n_bytes=1, move_cursor=1):
88
+ """
89
+ Reads n bytes of date from the current cursor position.
90
+ Moves cursor if move_cursor is true
91
+ """
92
+ return readBew(self.nextSlice(n_bytes, move_cursor))
93
+
94
+
95
+ def readVarLen(self):
96
+ """
97
+ Reads a variable length value from the current cursor position.
98
+ Moves cursor if move_cursor is true
99
+ """
100
+ MAX_VARLEN = 4 # Max value varlen can be
101
+ var = readVar(self.nextSlice(MAX_VARLEN, 0))
102
+ # only move cursor the actual bytes in varlen
103
+ self.moveCursor(varLen(var))
104
+ return var
105
+
106
+
107
+
108
+ if __name__ == '__main__':
109
+
110
+ test_file = 'test/midifiles/minimal.mid'
111
+ fis = RawInstreamFile(test_file)
112
+ print(fis.nextSlice(len(fis.data)))
113
+
114
+ test_file = 'test/midifiles/cubase-minimal.mid'
115
+ cubase_minimal = open(test_file, 'rb')
116
+ fis2 = RawInstreamFile(cubase_minimal)
117
+ print(fis2.nextSlice(len(fis2.data)))
118
+ cubase_minimal.close()
midi/RawOutstreamFile.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ # standard library imports
4
+ import sys
5
+ from types import StringType
6
+ from struct import unpack
7
+ from cStringIO import StringIO
8
+
9
+ # custom import
10
+ from DataTypeConverters import writeBew, writeVar, fromBytes
11
+
12
+ class RawOutstreamFile:
13
+
14
+ """
15
+
16
+ Writes a midi file to disk.
17
+
18
+ """
19
+
20
+ def __init__(self, outfile=''):
21
+ self.buffer = StringIO()
22
+ self.outfile = outfile
23
+
24
+
25
+ # native data reading functions
26
+
27
+
28
+ def writeSlice(self, str_slice):
29
+ "Writes the next text slice to the raw data"
30
+ self.buffer.write(str_slice)
31
+
32
+
33
+ def writeBew(self, value, length=1):
34
+ "Writes a value to the file as big endian word"
35
+ self.writeSlice(writeBew(value, length))
36
+
37
+
38
+ def writeVarLen(self, value):
39
+ "Writes a variable length word to the file"
40
+ var = self.writeSlice(writeVar(value))
41
+
42
+
43
+ def write(self):
44
+ "Writes to disc"
45
+ if self.outfile:
46
+ if isinstance(self.outfile, StringType):
47
+ outfile = open(self.outfile, 'wb')
48
+ outfile.write(self.getvalue())
49
+ outfile.close()
50
+ else:
51
+ self.outfile.write(self.getvalue())
52
+ else:
53
+ sys.stdout.write(self.getvalue())
54
+
55
+ def getvalue(self):
56
+ return self.buffer.getvalue()
57
+
58
+
59
+ if __name__ == '__main__':
60
+
61
+ out_file = 'test/midifiles/midiout.mid'
62
+ out_file = ''
63
+ rawOut = RawOutstreamFile(out_file)
64
+ rawOut.writeSlice('MThd')
65
+ rawOut.writeBew(6, 4)
66
+ rawOut.writeBew(1, 2)
67
+ rawOut.writeBew(2, 2)
68
+ rawOut.writeBew(15360, 2)
69
+ rawOut.write()
midi/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ #import MidiOutStream
4
+ #import MidiInStream
5
+ #import MidiInFile
6
+ #import MidiToText
midi/changes.txt ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ------------------------------------------------------------------------
2
+ r409 | maxm | 2006-01-05 16:37:29 +0100 (to, 05 jan 2006) | 1 line
3
+
4
+ Made RawOutstreamFile.py write to std out if no outfile is given.
5
+ ------------------------------------------------------------------------
6
+ r403 | maxm | 2006-01-05 13:34:11 +0100 (to, 05 jan 2006) | 1 line
7
+
8
+
9
+ ------------------------------------------------------------------------
10
+ r402 | maxm | 2006-01-05 13:33:56 +0100 (to, 05 jan 2006) | 1 line
11
+
12
+ - Fixed minor bugs, added coding headers
13
+ ------------------------------------------------------------------------
14
+ r401 | maxm | 2006-01-01 23:09:17 +0100 (s_, 01 jan 2006) | 1 line
15
+
16
+ Fixed sysex dispathcer bug.
17
+ ------------------------------------------------------------------------
18
+ r268 | maxm | 2005-02-04 12:26:59 +0100 (fr, 04 feb 2005) | 1 line
19
+
20
+
21
+ ------------------------------------------------------------------------
22
+ r128 | maxm | 2004-12-18 14:05:27 +0100 (l_, 18 dec 2004) | 1 line
23
+
24
+ Fixed bug when using relative time
25
+ ------------------------------------------------------------------------
26
+ r15 | maxm | 2004-03-09 15:01:41 +0100 (ti, 09 mar 2004) | 1 line
27
+
28
+ made a copy to meta folder
29
+ ------------------------------------------------------------------------
30
+ r13 | maxm | 2004-03-09 09:17:23 +0100 (ti, 09 mar 2004) | 1 line
31
+
32
+ Deleted .pyc files
33
+ ------------------------------------------------------------------------
34
+ r12 | maxm | 2004-03-09 09:15:54 +0100 (ti, 09 mar 2004) | 1 line
35
+
36
+ Removed file/folder
37
+ ------------------------------------------------------------------------
38
+ r3 | maxm | 2004-03-08 23:16:25 +0100 (ma, 08 mar 2004) | 1 line
39
+
40
+ Adde midi
41
+ ------------------------------------------------------------------------
42
+ r1 | maxm | 2004-03-08 22:49:23 +0100 (ma, 08 mar 2004) | 1 line
43
+
44
+ Initial Import
45
+ ------------------------------------------------------------------------
midi/constants.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: ISO-8859-1 -*-
2
+
3
+ ###################################################
4
+ ## Definitions of the different midi events
5
+
6
+
7
+
8
+ ###################################################
9
+ ## Midi channel events (The most usual events)
10
+ ## also called "Channel Voice Messages"
11
+
12
+ NOTE_OFF = 0x80
13
+ # 1000cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)
14
+
15
+ NOTE_ON = 0x90
16
+ # 1001cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)
17
+
18
+ AFTERTOUCH = 0xA0
19
+ # 1010cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)
20
+
21
+ CONTINUOUS_CONTROLLER = 0xB0 # see Channel Mode Messages!!!
22
+ # 1011cccc 0ccccccc 0vvvvvvv (channel, controller, value)
23
+
24
+ PATCH_CHANGE = 0xC0
25
+ # 1100cccc 0ppppppp (channel, program)
26
+
27
+ CHANNEL_PRESSURE = 0xD0
28
+ # 1101cccc 0ppppppp (channel, pressure)
29
+
30
+ PITCH_BEND = 0xE0
31
+ # 1110cccc 0vvvvvvv 0wwwwwww (channel, value-lo, value-hi)
32
+
33
+
34
+ ###################################################
35
+ ## Channel Mode Messages (Continuous Controller)
36
+ ## They share a status byte.
37
+ ## The controller makes the difference here
38
+
39
+ # High resolution continuous controllers (MSB)
40
+
41
+ BANK_SELECT = 0x00
42
+ MODULATION_WHEEL = 0x01
43
+ BREATH_CONTROLLER = 0x02
44
+ FOOT_CONTROLLER = 0x04
45
+ PORTAMENTO_TIME = 0x05
46
+ DATA_ENTRY = 0x06
47
+ CHANNEL_VOLUME = 0x07
48
+ BALANCE = 0x08
49
+ PAN = 0x0A
50
+ EXPRESSION_CONTROLLER = 0x0B
51
+ EFFECT_CONTROL_1 = 0x0C
52
+ EFFECT_CONTROL_2 = 0x0D
53
+ GEN_PURPOSE_CONTROLLER_1 = 0x10
54
+ GEN_PURPOSE_CONTROLLER_2 = 0x11
55
+ GEN_PURPOSE_CONTROLLER_3 = 0x12
56
+ GEN_PURPOSE_CONTROLLER_4 = 0x13
57
+
58
+ # High resolution continuous controllers (LSB)
59
+
60
+ BANK_SELECT = 0x20
61
+ MODULATION_WHEEL = 0x21
62
+ BREATH_CONTROLLER = 0x22
63
+ FOOT_CONTROLLER = 0x24
64
+ PORTAMENTO_TIME = 0x25
65
+ DATA_ENTRY = 0x26
66
+ CHANNEL_VOLUME = 0x27
67
+ BALANCE = 0x28
68
+ PAN = 0x2A
69
+ EXPRESSION_CONTROLLER = 0x2B
70
+ EFFECT_CONTROL_1 = 0x2C
71
+ EFFECT_CONTROL_2 = 0x2D
72
+ GENERAL_PURPOSE_CONTROLLER_1 = 0x30
73
+ GENERAL_PURPOSE_CONTROLLER_2 = 0x31
74
+ GENERAL_PURPOSE_CONTROLLER_3 = 0x32
75
+ GENERAL_PURPOSE_CONTROLLER_4 = 0x33
76
+
77
+ # Switches
78
+
79
+ SUSTAIN_ONOFF = 0x40
80
+ PORTAMENTO_ONOFF = 0x41
81
+ SOSTENUTO_ONOFF = 0x42
82
+ SOFT_PEDAL_ONOFF = 0x43
83
+ LEGATO_ONOFF = 0x44
84
+ HOLD_2_ONOFF = 0x45
85
+
86
+ # Low resolution continuous controllers
87
+
88
+ SOUND_CONTROLLER_1 = 0x46 # (TG: Sound Variation; FX: Exciter On/Off)
89
+ SOUND_CONTROLLER_2 = 0x47 # (TG: Harmonic Content; FX: Compressor On/Off)
90
+ SOUND_CONTROLLER_3 = 0x48 # (TG: Release Time; FX: Distortion On/Off)
91
+ SOUND_CONTROLLER_4 = 0x49 # (TG: Attack Time; FX: EQ On/Off)
92
+ SOUND_CONTROLLER_5 = 0x4A # (TG: Brightness; FX: Expander On/Off)75 SOUND_CONTROLLER_6 (TG: Undefined; FX: Reverb OnOff)
93
+ SOUND_CONTROLLER_7 = 0x4C # (TG: Undefined; FX: Delay OnOff)
94
+ SOUND_CONTROLLER_8 = 0x4D # (TG: Undefined; FX: Pitch Transpose OnOff)
95
+ SOUND_CONTROLLER_9 = 0x4E # (TG: Undefined; FX: Flange/Chorus OnOff)
96
+ SOUND_CONTROLLER_10 = 0x4F # (TG: Undefined; FX: Special Effects OnOff)
97
+ GENERAL_PURPOSE_CONTROLLER_5 = 0x50
98
+ GENERAL_PURPOSE_CONTROLLER_6 = 0x51
99
+ GENERAL_PURPOSE_CONTROLLER_7 = 0x52
100
+ GENERAL_PURPOSE_CONTROLLER_8 = 0x53
101
+ PORTAMENTO_CONTROL = 0x54 # (PTC) (0vvvvvvv is the source Note number) (Detail)
102
+ EFFECTS_1 = 0x5B # (Ext. Effects Depth)
103
+ EFFECTS_2 = 0x5C # (Tremelo Depth)
104
+ EFFECTS_3 = 0x5D # (Chorus Depth)
105
+ EFFECTS_4 = 0x5E # (Celeste Depth)
106
+ EFFECTS_5 = 0x5F # (Phaser Depth)
107
+ DATA_INCREMENT = 0x60 # (0vvvvvvv is n/a; use 0)
108
+ DATA_DECREMENT = 0x61 # (0vvvvvvv is n/a; use 0)
109
+ NON_REGISTERED_PARAMETER_NUMBER = 0x62 # (LSB)
110
+ NON_REGISTERED_PARAMETER_NUMBER = 0x63 # (MSB)
111
+ REGISTERED_PARAMETER_NUMBER = 0x64 # (LSB)
112
+ REGISTERED_PARAMETER_NUMBER = 0x65 # (MSB)
113
+
114
+ # Channel Mode messages - (Detail)
115
+
116
+ ALL_SOUND_OFF = 0x78
117
+ RESET_ALL_CONTROLLERS = 0x79
118
+ LOCAL_CONTROL_ONOFF = 0x7A
119
+ ALL_NOTES_OFF = 0x7B
120
+ OMNI_MODE_OFF = 0x7C # (also causes ANO)
121
+ OMNI_MODE_ON = 0x7D # (also causes ANO)
122
+ MONO_MODE_ON = 0x7E # (Poly Off; also causes ANO)
123
+ POLY_MODE_ON = 0x7F # (Mono Off; also causes ANO)
124
+
125
+
126
+
127
+ ###################################################
128
+ ## System Common Messages, for all channels
129
+
130
+ SYSTEM_EXCLUSIVE = 0xF0
131
+ # 11110000 0iiiiiii 0ddddddd ... 11110111
132
+
133
+ MTC = 0xF1 # MIDI Time Code Quarter Frame
134
+ # 11110001
135
+
136
+ SONG_POSITION_POINTER = 0xF2
137
+ # 11110010 0vvvvvvv 0wwwwwww (lo-position, hi-position)
138
+
139
+ SONG_SELECT = 0xF3
140
+ # 11110011 0sssssss (songnumber)
141
+
142
+ #UNDEFINED = 0xF4
143
+ ## 11110100
144
+
145
+ #UNDEFINED = 0xF5
146
+ ## 11110101
147
+
148
+ TUNING_REQUEST = 0xF6
149
+ # 11110110
150
+
151
+ END_OFF_EXCLUSIVE = 0xF7 # terminator
152
+ # 11110111 # End of system exclusive
153
+
154
+
155
+ ###################################################
156
+ ## Midifile meta-events
157
+
158
+ SEQUENCE_NUMBER = 0x00 # 00 02 ss ss (seq-number)
159
+ TEXT = 0x01 # 01 len text...
160
+ COPYRIGHT = 0x02 # 02 len text...
161
+ SEQUENCE_NAME = 0x03 # 03 len text...
162
+ INSTRUMENT_NAME = 0x04 # 04 len text...
163
+ LYRIC = 0x05 # 05 len text...
164
+ MARKER = 0x06 # 06 len text...
165
+ CUEPOINT = 0x07 # 07 len text...
166
+ PROGRAM_NAME = 0x08 # 08 len text...
167
+ DEVICE_NAME = 0x09 # 09 len text...
168
+
169
+ MIDI_CH_PREFIX = 0x20 # MIDI channel prefix assignment (unofficial)
170
+
171
+ MIDI_PORT = 0x21 # 21 01 port, legacy stuff but still used
172
+ END_OF_TRACK = 0x2F # 2f 00
173
+ TEMPO = 0x51 # 51 03 tt tt tt (tempo in us/quarternote)
174
+ SMTP_OFFSET = 0x54 # 54 05 hh mm ss ff xx
175
+ TIME_SIGNATURE = 0x58 # 58 04 nn dd cc bb
176
+ KEY_SIGNATURE = 0x59 # ??? len text...
177
+ SPECIFIC = 0x7F # Sequencer specific event
178
+
179
+ FILE_HEADER = 'MThd'
180
+ TRACK_HEADER = 'MTrk'
181
+
182
+ ###################################################
183
+ ## System Realtime messages
184
+ ## I don't supose these are to be found in midi files?!
185
+
186
+ TIMING_CLOCK = 0xF8
187
+ # undefined = 0xF9
188
+ SONG_START = 0xFA
189
+ SONG_CONTINUE = 0xFB
190
+ SONG_STOP = 0xFC
191
+ # undefined = 0xFD
192
+ ACTIVE_SENSING = 0xFE
193
+ SYSTEM_RESET = 0xFF
194
+
195
+
196
+ ###################################################
197
+ ## META EVENT, it is used only in midi files.
198
+ ## In transmitted data it means system reset!!!
199
+
200
+ META_EVENT = 0xFF
201
+ # 11111111
202
+
203
+
204
+ ###################################################
205
+ ## Helper functions
206
+
207
+ def is_status(byte):
208
+ return (byte & 0x80) == 0x80 # 1000 0000
209
+
210
+
midi/example_mimimal_type0.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from MidiOutFile import MidiOutFile
2
+
3
+ """
4
+ This is an example of the smallest possible type 0 midi file, where
5
+ all the midi events are in the same track.
6
+ """
7
+
8
+ out_file = 'midiout/minimal_type0.mid'
9
+ midi = MidiOutFile(out_file)
10
+
11
+ # non optional midi framework
12
+ midi.header()
13
+ midi.start_of_track()
14
+
15
+
16
+ # musical events
17
+
18
+ midi.update_time(0)
19
+ midi.note_on(channel=0, note=0x40)
20
+
21
+ midi.update_time(192)
22
+ midi.note_off(channel=0, note=0x40)
23
+
24
+
25
+ # non optional midi framework
26
+ midi.update_time(0)
27
+ midi.end_of_track()
28
+
29
+ midi.eof()
midi/example_print_channel_0.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from MidiOutStream import MidiOutStream
2
+ from MidiInFile import MidiInFile
3
+
4
+ """
5
+ This prints all note on events on midi channel 0
6
+ """
7
+
8
+
9
+ class Transposer(MidiOutStream):
10
+
11
+ "Transposes all notes by 1 octave"
12
+
13
+ def note_on(self, channel=0, note=0x40, velocity=0x40):
14
+ if channel == 0:
15
+ print(channel, note, velocity, self.rel_time())
16
+
17
+
18
+ event_handler = Transposer()
19
+
20
+ in_file = 'midiout/minimal_type0.mid'
21
+ midi_in = MidiInFile(event_handler, in_file)
22
+ midi_in.read()
midi/example_print_events.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from MidiToText import MidiToText
2
+
3
+ """
4
+ This is an example that uses the MidiToText eventhandler. When an
5
+ event is triggered on it, it prints the event to the console.
6
+ """
7
+
8
+ midi = MidiToText()
9
+
10
+ # non optional midi framework
11
+ midi.header()
12
+ midi.start_of_track()
13
+
14
+
15
+ # musical events
16
+
17
+ midi.update_time(0)
18
+ midi.note_on(channel=0, note=0x40)
19
+
20
+ midi.update_time(192)
21
+ midi.note_off(channel=0, note=0x40)
22
+
23
+
24
+ # non optional midi framework
25
+ midi.update_time(0)
26
+ midi.end_of_track() # not optional!
27
+
28
+ midi.eof()
midi/example_print_file.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This is an example that uses the MidiToText eventhandler. When an
3
+ event is triggered on it, it prints the event to the console.
4
+
5
+ It gets the events from the MidiInFile.
6
+
7
+ So it prints all the events from the infile to the console. great for
8
+ debugging :-s
9
+ """
10
+
11
+
12
+ # get data
13
+ test_file = 'test/midifiles/minimal-cubase-type0.mid'
14
+
15
+ # do parsing
16
+ from MidiInFile import MidiInFile
17
+ from MidiToText import MidiToText # the event handler
18
+ midiIn = MidiInFile(MidiToText(), test_file)
19
+ midiIn.read()
midi/example_transpose_octave.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from MidiOutFile import MidiOutFile
2
+ from MidiInFile import MidiInFile
3
+
4
+ """
5
+ This is an example of the smallest possible type 0 midi file, where
6
+ all the midi events are in the same track.
7
+ """
8
+
9
+
10
+ class Transposer(MidiOutFile):
11
+
12
+ "Transposes all notes by 1 octave"
13
+
14
+ def _transp(self, ch, note):
15
+ if ch != 9: # not the drums!
16
+ note += 12
17
+ if note > 127:
18
+ note = 127
19
+ return note
20
+
21
+
22
+ def note_on(self, channel=0, note=0x40, velocity=0x40):
23
+ note = self._transp(channel, note)
24
+ MidiOutFile.note_on(self, channel, note, velocity)
25
+
26
+
27
+ def note_off(self, channel=0, note=0x40, velocity=0x40):
28
+ note = self._transp(channel, note)
29
+ MidiOutFile.note_off(self, channel, note, velocity)
30
+
31
+
32
+ out_file = 'midiout/transposed.mid'
33
+ midi_out = Transposer(out_file)
34
+
35
+ #in_file = 'midiout/minimal_type0.mid'
36
+ #in_file = 'test/midifiles/Lola.mid'
37
+ in_file = 'test/midifiles/tennessee_waltz.mid'
38
+ midi_in = MidiInFile(midi_out, in_file)
39
+ midi_in.read()
40
+
midi/experimental/EventDispatcherBase.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class EventDispatcherBase:
2
+
3
+
4
+ def __init__(self, outstream):
5
+ """
6
+ The event dispatcher generates events on the outstream. This
7
+ is the base implementation. It is more like an interface for
8
+ how the EventDispatcher. It has the methods that are used by
9
+ the Midi Parser.
10
+ """
11
+ # internal values, don't mess with 'em directly
12
+ self.outstream = outstream
13
+
14
+
15
+ def eof(self):
16
+ "End of file!"
17
+ self.outstream.eof()
18
+
19
+
20
+ def update_time(self, new_time=0, relative=1):
21
+ "Updates relative/absolute time."
22
+ self.outstream.update_time(new_time, relative)
23
+
24
+ # 'official' midi events
25
+
26
+ def header(self, format, nTracks, division):
27
+ "Triggers the header event"
28
+ self.outstream.header(format, nTracks, division)
29
+
30
+
31
+ def start_of_track(self, current_track):
32
+ "Triggers the start of track event"
33
+
34
+ # I do this twice so that users can overwrite the
35
+ # start_of_track event handler without worrying whether the
36
+ # track number is updated correctly.
37
+ self.outstream.set_current_track(current_track)
38
+ self.outstream.start_of_track(current_track)
39
+
40
+ # Event dispatchers for midi events
41
+
42
+ def channel_messages(self, hi_nible, channel, data):
43
+ "Dispatches channel messages"
44
+ self.outstream.channel_message(hi_nible, channel, data)
45
+
46
+
47
+ def continuous_controllers(self, channel, controller, value):
48
+ "Dispatches channel messages"
49
+ self.outstream.continuous_controller(channel, controller, value)
50
+
51
+
52
+ def system_commons(self, common_type, common_data):
53
+ "Dispatches system common messages"
54
+ self.outstream.system_common(common_type, common_data)
55
+
56
+
57
+ def meta_event(self, meta_type, data):
58
+ "Dispatches meta events"
59
+ self.outstream.meta_event(meta_type, data)
60
+
61
+
62
+ def sysex_events(self, data):
63
+ "Dispatcher for sysex events"
64
+ self.outstream.sysex_event(data)
65
+
66
+
67
+
68
+ if __name__ == '__main__':
69
+
70
+
71
+ from MidiToText import MidiToText
72
+ from constants import NOTE_ON
73
+
74
+ outstream = MidiToText()
75
+ dispatcher = EventDispatcherBase(outstream)
76
+ dispatcher.channel_messages(NOTE_ON, 0x00, '\x40\x40')
midi/experimental/MidiOutPassThrough.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from MidiOutStream import MidiOutStream
2
+
3
+ class MidiOutPassThrough(MidiOutStream):
4
+
5
+
6
+ """
7
+
8
+ This class i mainly used for testing the event dispatcher. The
9
+ methods just returns the passed parameters as a tupple.
10
+
11
+ """
12
+
13
+
14
+ #####################
15
+ ## Midi channel events
16
+
17
+
18
+ def note_on(self, channel, note, velocity, time=None):
19
+ return channel, note, velocity, time
20
+
21
+
22
+ def note_off(self, channel, note, velocity, time=None):
23
+ return channel, note, velocity, time
24
+
25
+
26
+ def aftertouch(self, channel, note, velocity, time=None):
27
+ return channel, note, velocity, time
28
+
29
+
30
+ def continuous_controller(self, channel, controller, value, time=None):
31
+ return channel, controller, value, time
32
+
33
+
34
+ def patch_change(self, channel, patch, time=None):
35
+ return channel, patch, time
36
+
37
+
38
+ def channel_pressure(self, channel, pressure, time=None):
39
+ return channel, pressure, time
40
+
41
+
42
+ #####################
43
+ ## defined continuous controller events
44
+
45
+ # def cc_
46
+
47
+ #####################
48
+ ## Common events
49
+
50
+ def system_exclusive(self, data, time=None):
51
+ return data, time
52
+
53
+
54
+ def song_position_pointer(self, hiPos, loPos, time=None):
55
+ return hiPos, loPos, time
56
+
57
+
58
+ def song_select(self, songNumber, time=None):
59
+ return songNumber, time
60
+
61
+
62
+ def tuning_request(self, time=None):
63
+ return time
64
+
65
+
66
+
67
+ #########################
68
+ # header does not really belong here. But anyhoo!!!
69
+
70
+ def header(self, format, nTracks, division):
71
+ return format, nTracks, division
72
+
73
+
74
+ def eof(self):
75
+ return 'eof'
76
+
77
+
78
+ #####################
79
+ ## meta events
80
+
81
+ def start_of_track(self, n_track=0):
82
+ return n_track
83
+
84
+
85
+ def end_of_track(self, n_track=0, time=None):
86
+ return n_track, time
87
+
88
+
89
+ def sequence_number(self, hiVal, loVal, time=None):
90
+ return hiVal, loVal, time
91
+
92
+
93
+ def text(self, text, time=None):
94
+ return text, time
95
+
96
+
97
+ def copyright(self, text, time=None):
98
+ return text, time
99
+
100
+
101
+ def sequence_name(self, text, time=None):
102
+ return text, time
103
+
104
+
105
+ def instrument_name(self, text, time=None):
106
+ return text, time
107
+
108
+
109
+ def lyric(self, text, time=None):
110
+ return text, time
111
+
112
+
113
+ def marker(self, text, time=None):
114
+ return text, time
115
+
116
+
117
+ def cuepoint(self, text, time=None):
118
+ return text, time
119
+
120
+
121
+ def midi_port(self, value, time=None):
122
+ return value, time
123
+
124
+
125
+ def tempo(self, value, time=None):
126
+ return value, time
127
+
128
+ def smtp_offset(self, hour, minute, second, frame, framePart, time=None):
129
+ return hour, minute, second, frame, framePart, time
130
+
131
+
132
+ def time_signature(self, nn, dd, cc, bb, time=None):
133
+ return nn, dd, cc, bb, time
134
+
135
+
136
+ def key_signature(self, sf, mi, time=None):
137
+ return sf, mi, time
138
+
139
+
140
+ def sequencer_specific(self, data, time=None):
141
+ return data, time
142
+
143
+
144
+
145
+
146
+ #####################
147
+ ## realtime events
148
+
149
+ def timing_clock(self, time=None):
150
+ return time
151
+
152
+
153
+ def song_start(self, time=None):
154
+ return time
155
+
156
+
157
+ def song_stop(self, time=None):
158
+ return time
159
+
160
+
161
+ def song_continue(self, time=None):
162
+ return time
163
+
164
+
165
+ def active_sensing(self, time=None):
166
+ return time
167
+
168
+
169
+ def system_reset(self, time=None):
170
+ return time
171
+
172
+
173
+
174
+
175
+
176
+ if __name__ == '__main__':
177
+
178
+ midiOut = MidiOutStream()
179
+ midiOut.note_on(0, 63, 127, 0)
180
+ midiOut.note_off(0, 63, 127, 384)
181
+
182
+
midi/experimental/MidiOutStreamBase.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class MidiOutStreamBase:
2
+
3
+
4
+ """
5
+
6
+ MidiOutStreamBase is Basically an eventhandler. It is the most central
7
+ class in the Midi library. You use it both for writing events to
8
+ an output stream, and as an event handler for an input stream.
9
+
10
+ This makes it extremely easy to take input from one stream and
11
+ send it to another. Ie. if you want to read a Midi file, do some
12
+ processing, and send it to a midiport.
13
+
14
+ All time values are in absolute values from the opening of a
15
+ stream. To calculate time values, please use the MidiTime and
16
+ MidiDeltaTime classes.
17
+
18
+ """
19
+
20
+ def __init__(self):
21
+
22
+ # the time is rather global, so it needs to be stored
23
+ # here. Otherwise there would be no really simple way to
24
+ # calculate it. The alternative would be to have each event
25
+ # handler do it. That sucks even worse!
26
+ self._absolute_time = 0
27
+ self._relative_time = 0
28
+ self._current_track = 0
29
+
30
+ # time handling event handlers. They should overwritten with care
31
+
32
+ def update_time(self, new_time=0, relative=1):
33
+ """
34
+ Updates the time, if relative is true, new_time is relative,
35
+ else it's absolute.
36
+ """
37
+ if relative:
38
+ self._relative_time = new_time
39
+ self._absolute_time += new_time
40
+ else:
41
+ self._absolute_time = new_time
42
+ self._relative_time = new_time - self._absolute_time
43
+
44
+ def rel_time(self):
45
+ "Returns the relative time"
46
+ return self._relative_time
47
+
48
+ def abs_time(self):
49
+ "Returns the absolute time"
50
+ return self._absolute_time
51
+
52
+ # track handling event handlers
53
+
54
+ def set_current_track(self, new_track):
55
+ "Sets the current track number"
56
+ self._current_track = new_track
57
+
58
+ def get_current_track(self):
59
+ "Returns the current track number"
60
+ return self._current_track
61
+
62
+
63
+ #####################
64
+ ## Midi events
65
+
66
+
67
+ def channel_message(self, message_type, channel, data):
68
+ """The default event handler for channel messages"""
69
+ pass
70
+
71
+
72
+ #####################
73
+ ## Common events
74
+
75
+ def system_exclusive(self, data):
76
+
77
+ """The default event handler for system_exclusive messages"""
78
+ pass
79
+
80
+
81
+ def system_common(self, common_type, common_data):
82
+
83
+ """The default event handler for system common messages"""
84
+ pass
85
+
86
+
87
+ #########################
88
+ # header does not really belong here. But anyhoo!!!
89
+
90
+ def header(self, format, nTracks, division):
91
+
92
+ """
93
+ format: type of midi file in [1,2]
94
+ nTracks: number of tracks
95
+ division: timing division
96
+ """
97
+ pass
98
+
99
+
100
+ def start_of_track(self, n_track=0):
101
+
102
+ """
103
+ n_track: number of track
104
+ """
105
+ pass
106
+
107
+
108
+ def eof(self):
109
+
110
+ """
111
+ End of file. No more events to be processed.
112
+ """
113
+ pass
114
+
115
+
116
+ #####################
117
+ ## meta events
118
+
119
+
120
+ def meta_event(self, meta_type, data, time):
121
+
122
+ """The default event handler for meta_events"""
123
+ pass
124
+
125
+
126
+
127
+
128
+ if __name__ == '__main__':
129
+
130
+ midiOut = MidiOutStreamBase()
131
+ midiOut.update_time(0,0)
132
+ midiOut.note_on(0, 63, 127)
133
+ midiOut.note_off(0, 63, 127)
134
+
135
+
midi/experimental/readme.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Stuff that I am just playing around with
midi/readme.txt ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is the documentation for the midi package
2
+ ==============================================
3
+
4
+
5
+ The modules follows the following naming convention:
6
+
7
+
8
+ MidiIn<StreamType>.py
9
+ ---------------------
10
+
11
+ The MidiIn modules reads midi content for a specific type of stream. Ie. a file or a midi port. It then generates events and triggers them on a MidiOutStream.
12
+
13
+
14
+ MidiOut<StreamType>.py
15
+ ----------------------
16
+
17
+ The MidiOut modules are event handlers, that reacts to events generated by a a Midi in module.
18
+
19
+
20
+ MidiInBase.py
21
+ ---------------
22
+
23
+ The base class for input streams.
24
+
25
+
26
+ MidiOutBase.py
27
+ ----------------
28
+
29
+ The base class for the output streams.
30
+
31
+
32
+
33
+
34
+
35
+
36
+ Internal modules
37
+ ================
38
+
39
+
40
+ DataTypeConverters.py
41
+ ---------------------
42
+
43
+ A collection of functions that converts the special data types used in midi files to and from strings.
44
+
45
+
46
+ constants.py
47
+ ------------
48
+
49
+ A collection of constants from the midi spec.
50
+
midi/version.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ 0.1.4
midi2abc.py ADDED
@@ -0,0 +1,619 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/python
2
+ #
3
+ # Copyright (C) 2011 Nils Liberg (mail: kotorinl at yahoo.co.uk)
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+ #
19
+ # New in version 0.2:
20
+ # better handling of accidentals in keys with many flats and sharps
21
+
22
+ from midi.MidiOutStream import MidiOutStream
23
+ from midi.MidiInFile import MidiInFile
24
+ from fractions import Fraction
25
+ import os
26
+ import os.path
27
+ import sys
28
+ import getopt
29
+ import math
30
+ from simple_abc_parser import get_best_key_for_midi_notes, get_accidentals_for_key
31
+ from io import StringIO
32
+
33
+ num_quarter_notes_per_bar = 3
34
+ bars_per_line = 4
35
+
36
+ usage = """PyMidi2Abc version 0.1 May 30 2008, usage:
37
+
38
+ python pymidi2abc.py <options>
39
+ [-f] <input file>
40
+ -o <output file>
41
+ -k <key signature> key name, or -6 to 6 sharps
42
+ -l <default note length (L: field)>
43
+ -m <time signature>
44
+ --nbb no beam breaks
45
+ --aux=<denominator of L: unit length>
46
+ --nt Do not look for triplets or broken rhythm
47
+ --s8 insert slurs on groups of 8th notes
48
+ --s16 insert slurs on groups of 16th notes
49
+ --bpl=<number> of bars printed per line
50
+ --title=<string> Adds T: field containing string
51
+ --origin=<string> Adds O: field containing string
52
+ """
53
+
54
+
55
+ def time_to_note_length(time):
56
+ # corresponds to the length of a 16th note (one forth of a quarter which corresponds to 1.0)
57
+ unit_time = 0.25
58
+ num = int(round(time / unit_time))
59
+ length = Fraction(1, 16) * num
60
+ return length
61
+
62
+
63
+ class Note:
64
+ def __init__(self, start, end, note):
65
+ self.start = start
66
+ self.end = end
67
+ self.note = note
68
+ self.length = time_to_note_length(self.end - self.start)
69
+
70
+
71
+ class MidiHandler(MidiOutStream):
72
+ def __init__(self, first_channel, last_channel):
73
+ MidiOutStream.__init__(self)
74
+ self.division = None
75
+ self.first_channel = first_channel
76
+ self.last_channel = last_channel
77
+ self.noteon_time = {}
78
+ self.notes = []
79
+
80
+ def sysex_event(self, data):
81
+ pass
82
+
83
+ def time_signature(self, nn, dd, cc, bb):
84
+ sig = Fraction(nn, 2**dd)
85
+
86
+ def header(self, format=0, nTracks=1, division=96):
87
+ self.division = division
88
+
89
+ def note_on(self, channel=0, note=0x40, velocity=0x40):
90
+ if self.first_channel <= channel <= self.last_channel:
91
+ self.noteon_time[note] = float(self.abs_time()) / self.division
92
+
93
+ def note_off(self, channel=0, note=0x40, velocity=0x40):
94
+ if self.first_channel <= channel <= self.last_channel:
95
+ if note in self.noteon_time:
96
+ end_time = float(self.abs_time()) / self.division
97
+ self.notes.append(Note(self.noteon_time[note], end_time, note))
98
+
99
+
100
+ def is_triplet(notes):
101
+ if len(notes) < 3:
102
+ return False
103
+ tolerance = 0.025
104
+ for total_len in [1.0, 0.5]:
105
+ if (
106
+ abs(notes[0].end - notes[1].start) < tolerance
107
+ and abs(notes[1].end - notes[2].start) < tolerance
108
+ and abs(notes[2].end - notes[0].start - total_len) < tolerance
109
+ and abs(notes[1].start - notes[0].start - total_len / 3) < tolerance
110
+ ):
111
+ return True
112
+ return False
113
+
114
+
115
+ def bar(time):
116
+ global num_quarter_notes_per_bar
117
+ return int(time / num_quarter_notes_per_bar + 0.003)
118
+
119
+
120
+ def bar_residue(time):
121
+ global num_quarter_notes_per_bar
122
+ return time - bar(time) * float(num_quarter_notes_per_bar)
123
+
124
+
125
+ def duration2abc(f):
126
+ if f == Fraction(1, 1):
127
+ return ""
128
+ elif f == Fraction(1, 2):
129
+ return "/"
130
+ elif f == Fraction(1, 4):
131
+ return "//"
132
+ elif f.numerator == "1":
133
+ return "/%d" % f.denumerator
134
+ else:
135
+ return str(f)
136
+
137
+
138
+ def note_to_string(note, duration, default_len, key_accidentals, cur_accidentals):
139
+ n_accidentals = sum(key_accidentals)
140
+ accidentals_to_scale = {
141
+ 7: "^B ^C ^^C ^D ^^D ^E ^F ^^F ^G ^^G ^A =B",
142
+ 6: "^B ^C ^^C ^D =E ^E ^F ^^F ^G ^^G ^A B",
143
+ 5: "^B ^C ^^C ^D E ^E ^F ^^F ^G =A ^A B",
144
+ 4: "^B ^C =D ^D E ^E ^F ^^F ^G A ^A B",
145
+ 3: "^B ^C D ^D E ^E ^F =G ^G A ^A B",
146
+ 2: "=C ^C D ^D E ^E ^F G ^G A ^A B",
147
+ 1: "C ^C D ^D E =F ^F G ^G A ^A B",
148
+ 0: "C ^C D ^D E F ^F G ^G A _B =B",
149
+ -1: "C ^C D _E =E F ^F G ^G A _B =B",
150
+ -2: "C ^C D _E =E F ^F G _A =A _B =B",
151
+ -3: "C _D =D _E =E F ^F G _A =A _B =B",
152
+ -4: "C _D =D _E =E F _G =G _A =A _B =B",
153
+ -5: "=C _D =D _E =E F _G =G _A =A _B _C",
154
+ -6: "=C _D =D _E _F =F _G =G _A =A _B _C",
155
+ -7: "=C _D =D _E _F =F _G =G _A __B _B _C",
156
+ }
157
+
158
+ scale = accidentals_to_scale[n_accidentals].split()
159
+ notes = [
160
+ n.lower().translate(str.maketrans("", "", "_=^")) for n in scale
161
+ ] # for semitone: the name of the note
162
+ # for semitone: 0 if white key, -1 or -2 if flat, 1 or 2 if sharp
163
+ accidentals = [n.count("^") - n.count("_") for n in scale]
164
+
165
+ note_scale = "CDEFGAB"
166
+ middle_C = 60
167
+ octave_note = (note - middle_C) % 12
168
+
169
+ n = notes[octave_note].upper()
170
+ accidental_num = accidentals[octave_note]
171
+ accidental_string = ""
172
+ scale_number = note_scale.index(n)
173
+ if cur_accidentals[scale_number] != accidental_num:
174
+ cur_accidentals[scale_number] = accidental_num
175
+ accidental_string = {-1: "_", -2: "__", 0: "=", 1: "^", 2: "^^"}[accidental_num]
176
+
177
+ octave = (note - middle_C) // 12
178
+
179
+ # handle the border cases of Cb and B# to make sure that we use the right octave
180
+ if octave_note == 11 and accidental_num == -1:
181
+ octave += 1
182
+ elif octave_note == 0 and accidental_num == 1:
183
+ octave -= 1
184
+
185
+ if octave > 0:
186
+ if octave >= 1:
187
+ n = n.lower()
188
+ if octave > 1:
189
+ n = n + "'" * int(octave - 1)
190
+ elif octave < 0:
191
+ if abs(octave) >= 1:
192
+ n = n + "," * abs(octave)
193
+ result = accidental_string + n
194
+ shown_duration = duration / default_len
195
+ if shown_duration != Fraction(1, 1):
196
+ result = result + duration2abc(shown_duration)
197
+ return result
198
+
199
+
200
+ def is_at_even(time, note_value):
201
+ offset_in_16ths = time_to_note_length(bar_residue(time)) / note_value
202
+ return offset_in_16ths.denominator == 1
203
+
204
+
205
+ def fix_lengths(notes):
206
+ for i1 in range(0, len(notes)):
207
+ for i2 in range(i1 + 1, len(notes)):
208
+ # if the i2 note starts at a later time and there is a sufficiently large difference between either start times or end times
209
+ if (
210
+ notes[i2].start > notes[i1].start
211
+ and abs(notes[i2].start - notes[i1].start)
212
+ + abs(notes[i2].end - notes[i1].end)
213
+ > 0.25
214
+ ):
215
+ notes[i1].end = notes[i2].start
216
+ notes[i1].length = time_to_note_length(
217
+ notes[i1].end - notes[i1].start
218
+ ) # update length
219
+ break
220
+
221
+
222
+ def midi_to_abc(
223
+ filename=None,
224
+ notes=None,
225
+ key=None,
226
+ metre=Fraction(3, 4),
227
+ default_len=Fraction(1, 16),
228
+ bars_per_line=4,
229
+ title="",
230
+ artist="",
231
+ source="",
232
+ no_triplets=False,
233
+ no_broken_rythms=False,
234
+ slur_8th_pairs=False,
235
+ slur_16th_pairs=False,
236
+ slur_triplets=True,
237
+ no_beam_breaks=False,
238
+ index="1",
239
+ anacrusis_notes=0,
240
+ ):
241
+ global num_quarter_notes_per_bar
242
+ num_quarter_notes_per_bar = metre * 4 # int(metre * 4)
243
+
244
+ if filename and not notes:
245
+ # read midi notes
246
+ handler1 = MidiHandler(0, 15) # channels 0-15
247
+ # handler1 = MidiHandler(0, 0) # channels 0-15
248
+ MidiInFile(handler1, filename).read()
249
+ notes = handler1.notes
250
+ elif not filename and not notes:
251
+ raise Exception(
252
+ "midi_to_abc needs to be passed either a filename or a notes argument"
253
+ )
254
+
255
+ # sequence of Note(start, end, note)
256
+ notes = sorted(notes, key=lambda n: n.start)
257
+ fix_lengths(notes)
258
+
259
+ # determine key and accidentals
260
+ if not key:
261
+ key = get_best_key_for_midi_notes([note.note for note in notes])
262
+ key_accidentals = get_accidentals_for_key(key)
263
+ cur_accidentals = key_accidentals[:]
264
+
265
+ output = StringIO()
266
+ output.write("X:%s\n" % index)
267
+ if source:
268
+ output.write("S:%s\n" % source)
269
+ if title:
270
+ output.write("T:%s\n" % title)
271
+ if artist:
272
+ output.write("A:%s\n" % artist)
273
+ output.write("M:%s\n" % metre)
274
+ output.write("L:%s\n" % default_len)
275
+ output.write("K:%s\n" % key.capitalize())
276
+
277
+ # initialize variables used in loop
278
+ last_note_start = -1.0
279
+ bow_started = False
280
+ broken_rythm_factor = Fraction(1, 1)
281
+ num_notes = len(notes)
282
+ bar_num = -1
283
+
284
+ if anacrusis_notes:
285
+ time_shift = 4 * float(metre) - notes[anacrusis_notes].start
286
+ # print notes[0].start, notes[1].start, notes[2].start
287
+ # print 'shift', time_shift
288
+ for n in notes:
289
+ n.start += time_shift
290
+ n.end += time_shift
291
+
292
+ # don't count the first bar if it's an upbeat (to get equal number of bars on each line)
293
+ inside_upbeat = (notes and bar_residue(notes[0].start) > 1.0) or anacrusis_notes
294
+
295
+ while notes:
296
+ # if current note is in a different bar than the last one, emit '|'
297
+ if bar(notes[0].start) > bar(last_note_start):
298
+ if len(notes) != num_notes:
299
+ output.write(" |")
300
+ cur_accidentals = key_accidentals[:]
301
+ if not inside_upbeat:
302
+ bar_num += 1
303
+ inside_upbeat = False
304
+ if bar_num % bars_per_line == 0 and bar_num > 0:
305
+ output.write("\n")
306
+
307
+ # if we have advanced the length of a quarter note, emit space (for note grouping)
308
+ br = bar_residue(notes[0].start)
309
+ if is_at_even(notes[0].start, Fraction(1, 4)) and not no_beam_breaks:
310
+ output.write(" ")
311
+
312
+ # check if next three notes can be interpreted as a triplet
313
+ if is_triplet(notes) and not no_triplets:
314
+ length = time_to_note_length(notes[2].end - notes[0].start)
315
+ _notes = [n.note for n in notes[:3]]
316
+
317
+ # convert notes to string representation
318
+ s = "(3" + "".join(
319
+ [
320
+ note_to_string(
321
+ n.note,
322
+ n.length * 2,
323
+ default_len,
324
+ key_accidentals,
325
+ cur_accidentals,
326
+ )
327
+ for n in notes[:3]
328
+ ]
329
+ )
330
+ if slur_triplets:
331
+ s = "(" + s + ")"
332
+ output.write(s)
333
+
334
+ last_note_start = notes[0].start
335
+ notes = notes[3:]
336
+
337
+ # else handle notes one by one or as a chord
338
+ else:
339
+ is_four_16th_notes = (
340
+ len([n for n in notes[0:4] if n.length == Fraction(1, 16)]) == 4
341
+ )
342
+ # either two eights or two eights with broken rythm
343
+ is_two_8th_notes = (
344
+ len([n for n in notes[0:2] if n.length == Fraction(1, 8)]) == 2
345
+ or len(notes) >= 2
346
+ and (notes[0].length, notes[1].length)
347
+ in [
348
+ (Fraction(3, 16), Fraction(1, 16)),
349
+ (Fraction(1, 16), Fraction(3, 16)),
350
+ ]
351
+ )
352
+
353
+ note = notes.pop(0)
354
+ last_note_start = note.start
355
+
356
+ # build a chord from notes near each other in time (will result in just one element in the non-chord case)
357
+ chord_notes = [note.note]
358
+ while notes and abs(notes[0].start - note.start) < 1.0 / 50:
359
+ chord_notes.append(notes.pop(0).note)
360
+
361
+ # let the duration of the first note determine the chord's duration (for simplicity)
362
+ length = note.length
363
+
364
+ # if the current note is the first of four 16th notes then add a bow on the two first notes
365
+ bow_started_here = False
366
+ if (
367
+ notes
368
+ and abs(br - int(br)) < 1.0 / 20
369
+ and not bow_started
370
+ and is_four_16th_notes
371
+ and slur_16th_pairs
372
+ ):
373
+ bow_started_here = True
374
+ bow_started = True
375
+ output.write("(")
376
+ elif (
377
+ notes
378
+ and abs(br - int(br)) < 1.0 / 20
379
+ and not bow_started
380
+ and is_two_8th_notes
381
+ and slur_8th_pairs
382
+ and br < 2.0
383
+ ):
384
+ bow_started_here = True
385
+ bow_started = True
386
+ output.write("(")
387
+
388
+ # check if it's possible to use a broken rytm (< or >) between the current and next note/chord
389
+ broken_rythm_symbol = ""
390
+ if not no_broken_rythms:
391
+ # a broken rythm was activated at the previous note
392
+ if broken_rythm_factor != Fraction(1, 1):
393
+ length = length * broken_rythm_factor
394
+ broken_rythm_factor = Fraction(1, 1)
395
+ elif (
396
+ notes
397
+ and is_at_even(last_note_start, Fraction(1, 8))
398
+ and bar(last_note_start) == bar(notes[0].start)
399
+ ):
400
+ # use > between this and next note
401
+ if notes[0].length == length / 3:
402
+ broken_rythm_symbol = ">"
403
+ length = length * Fraction(2, 3)
404
+ broken_rythm_factor = Fraction(2, 1)
405
+ # use < between this and next note
406
+ if notes[0].length == length * 3:
407
+ broken_rythm_symbol = "<"
408
+ length = length * Fraction(2, 1)
409
+ broken_rythm_factor = Fraction(2, 3)
410
+
411
+ # convert notes to string representation and output
412
+ s = "".join(
413
+ [
414
+ note_to_string(
415
+ n, length, default_len, key_accidentals, cur_accidentals
416
+ )
417
+ for n in chord_notes
418
+ ]
419
+ )
420
+ if len(chord_notes) > 1:
421
+ s = "[" + s + "]" # wrap chord
422
+ output.write(s)
423
+
424
+ # output broken rythm symbol if set
425
+ if broken_rythm_symbol:
426
+ output.write(str(broken_rythm_symbol))
427
+
428
+ # if a bow was previously started end it here
429
+ if bow_started and not bow_started_here:
430
+ output.write(")")
431
+ bow_started = False
432
+
433
+ # print 'note', note.start, length, chord_notes, '%.2f' % last_note_start, bar(last_note_start), '%.2f' % bar_residue(last_note_start)#, note.note
434
+
435
+ output.write(" |")
436
+ output.write("\n")
437
+
438
+ # left strip lines
439
+ lines = output.getvalue().split("\n")
440
+ lines = [l.lstrip() for l in lines]
441
+ return "\n".join(lines)
442
+
443
+
444
+ def main(argv):
445
+ # setup default values
446
+ filename = ""
447
+ output_filename = ""
448
+ key = None
449
+ default_len = Fraction(1, 16)
450
+ metre = Fraction(3, 4)
451
+ bars_per_line = 4
452
+ title = ""
453
+ source = ""
454
+ no_triplets = False
455
+ no_broken_rythms = False
456
+ slur_8th_pairs = False
457
+ slur_16th_pairs = False
458
+ no_beam_breaks = False
459
+
460
+ # if a single parameter was given and it's a path to an existing file use it as filename
461
+ if len(argv) == 1 and os.path.exists(argv[0]):
462
+ filename = argv[0]
463
+
464
+ # otherwise interpret options
465
+ else:
466
+ try:
467
+ opts, args = getopt.getopt(
468
+ argv,
469
+ "hm:k:f:o:l:",
470
+ [
471
+ "help",
472
+ "metre=",
473
+ "key=",
474
+ "bpl=",
475
+ "file=",
476
+ "outfile=",
477
+ "length=",
478
+ "bpl=",
479
+ "title=",
480
+ "source=",
481
+ "nbb",
482
+ "nt",
483
+ "s8",
484
+ "s16",
485
+ "aux=",
486
+ ],
487
+ )
488
+ except getopt.GetoptError:
489
+ print(usage)
490
+ sys.exit(2)
491
+
492
+ for opt, arg in opts:
493
+ try:
494
+ if opt in ("-h", "--help"):
495
+ print(usage)
496
+ sys.exit()
497
+ elif opt in ("-m", "--metre"):
498
+ x, y = map(int, arg.split("/"))
499
+ metre = Fraction(x, y)
500
+ elif opt in ("-k", "--key"):
501
+ try:
502
+ k = int(arg)
503
+ key = {
504
+ -7: "cb",
505
+ -6: "gb",
506
+ -5: "db",
507
+ -4: "ab",
508
+ -3: "eb",
509
+ -2: "bb",
510
+ -1: "f",
511
+ 0: "c",
512
+ 1: "g",
513
+ 2: "d",
514
+ 3: "a",
515
+ 4: "e",
516
+ 5: "b",
517
+ 6: "f#",
518
+ 7: "c#",
519
+ }[k]
520
+ except ValueError:
521
+ key = arg.lower()
522
+ elif opt in ("--bpl",):
523
+ bars_per_line = int(arg)
524
+ elif opt in ("--title",):
525
+ title = arg
526
+ elif opt in ("--source",):
527
+ source = arg
528
+ elif opt in ("--aux",):
529
+ default_len = Fraction(1, int(arg))
530
+ elif opt in (
531
+ "-l",
532
+ "--length",
533
+ ):
534
+ x, y = map(int, arg.split("/"))
535
+ default_len = Fraction(x, y)
536
+ elif opt in ("--nt"):
537
+ no_triplets = True
538
+ no_broken_rythms = True
539
+ elif opt in ("--nbb"):
540
+ no_beam_breaks = True
541
+ elif opt in ("--s8"):
542
+ slur_8th_pairs = True
543
+ elif opt in ("--s16"):
544
+ slur_16th_pairs = True
545
+ elif opt in ("-f",):
546
+ filename = arg
547
+ elif opt in ("-o",):
548
+ output_filename = arg
549
+ except Exception:
550
+ print("Error parsing argument: %s" % opt)
551
+ print(usage)
552
+ sys.exit(2)
553
+
554
+ if not filename:
555
+ print("Error: filename (-f) required!")
556
+ print(usage)
557
+ sys.exit(2)
558
+
559
+ # do midi to abc conversion
560
+ result = midi_to_abc(
561
+ filename,
562
+ None,
563
+ key,
564
+ metre,
565
+ default_len,
566
+ bars_per_line,
567
+ title,
568
+ source,
569
+ no_triplets,
570
+ no_broken_rythms,
571
+ slur_8th_pairs,
572
+ slur_16th_pairs,
573
+ no_beam_breaks=no_beam_breaks,
574
+ )
575
+
576
+ # output to stdout or file
577
+ if output_filename:
578
+ open(output_filename, "w").write(result)
579
+ else:
580
+ print(result)
581
+
582
+
583
+ def midi2abc(filename, title, artist):
584
+ key = None
585
+ default_len = Fraction(1, 16)
586
+ metre = Fraction(3, 4)
587
+ bars_per_line = 4
588
+ no_triplets = False
589
+ no_broken_rythms = False
590
+ slur_8th_pairs = False
591
+ slur_16th_pairs = False
592
+ no_beam_breaks = False
593
+
594
+ if not filename:
595
+ print("Error: filename (-f) required!")
596
+ print(usage)
597
+ sys.exit(2)
598
+
599
+ # do midi to abc conversion
600
+ return midi_to_abc(
601
+ filename,
602
+ None,
603
+ key,
604
+ metre,
605
+ default_len,
606
+ bars_per_line,
607
+ title,
608
+ artist,
609
+ "",
610
+ no_triplets,
611
+ no_broken_rythms,
612
+ slur_8th_pairs,
613
+ slur_16th_pairs,
614
+ no_beam_breaks=no_beam_breaks,
615
+ )
616
+
617
+
618
+ if __name__ == "__main__":
619
+ main(sys.argv[1:])
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
  librosa==0.9.2
2
  piano_transcription_inference
3
  pymupdf
4
- torch
 
1
  librosa==0.9.2
2
  piano_transcription_inference
3
  pymupdf
4
+ music21
simple_abc_parser.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2011 Nils Liberg (mail: kotorinl at yahoo.co.uk)
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ from fractions import Fraction
17
+
18
+ key_data = \
19
+ {'c': ('#', 0), 'am': ('#', 0), 'ddor': ('#', 0),
20
+ 'f': ('b', 1), 'dm': ('b', 1), 'gdor': ('#', 1),
21
+ 'bb': ('b', 2), 'gm': ('b', 2), 'cdor': ('#', 2),
22
+ 'eb': ('b', 3), 'cm': ('b', 3), 'bbdor': ('#', 3),
23
+ 'ab': ('b', 4), 'fm': ('b', 4), 'ebdor': ('#', 4),
24
+ 'db': ('b', 5), 'bbm': ('b', 5), 'abdor': ('#', 5),
25
+ 'gb': ('b', 6), 'ebm': ('b', 6), 'abdor': ('#', 6),
26
+ 'cb': ('b', 7), 'abm': ('b', 7), 'dbdor': ('#', 7),
27
+ 'g': ('#', 1), 'em': ('#', 1), 'ador': ('#', 0),
28
+ 'd': ('#', 2), 'bm': ('#', 2), 'edor': ('#', 0),
29
+ 'a': ('#', 3), 'f#m': ('#', 3), 'bdor': ('#', 0),
30
+ 'e': ('#', 4), 'c#m': ('#', 4), 'f#dor': ('#', 0),
31
+ 'b': ('#', 5), 'g#m': ('#', 5), 'c#dor': ('#', 0),
32
+ 'f#': ('#', 6), 'd#m': ('#', 6), 'g#dor': ('#', 0),
33
+ 'c#': ('#', 7), 'a#m': ('#', 7), 'c#dor': ('#', 0), }
34
+
35
+
36
+ def get_base_note_for_key(key):
37
+ key = key[:2]
38
+ # print key, notes_sharp, notes_flat
39
+ if key in notes_sharp:
40
+ return notes_sharp.index(key)
41
+ if key in notes_flat:
42
+ return notes_flat.index(key)
43
+ key = key[:1]
44
+ if key in notes_sharp:
45
+ return notes_sharp.index(key)
46
+ if key in notes_flat:
47
+ return notes_flat.index(key)
48
+ raise Exception("error, incorrect key:%s" % key)
49
+
50
+
51
+ def get_best_key_for_midi_notes(notes):
52
+ notes = [n % 12 for n in notes]
53
+ penalty_key_tuples = []
54
+ for key in key_data:
55
+ accidentals = get_accidentals_for_key(key)
56
+ basic_notes_in_key = [x+y for x,
57
+ y in zip(doremi2chromatic, accidentals)]
58
+ penalty = len(
59
+ [note for note in notes if not note in basic_notes_in_key])
60
+ if notes[-1] != get_base_note_for_key(key):
61
+ penalty += 2
62
+ if key.endswith('dor'):
63
+ penalty += 1
64
+ penalty_key_tuples.append((penalty, key))
65
+ penalty_key_tuples.sort()
66
+ # print '\n'.join((str(x) for x in penalty_key_tuples))
67
+ # print 'notes[-1]=', notes[-1]
68
+ # print 'penalty', penalty_key_tuples[0][0]
69
+ return penalty_key_tuples[0][1] # key of the first (and best) item
70
+
71
+
72
+ def update_extra_accidentals_for_note(basic_accidentals, extra_accidentals, note):
73
+ # merge the basic and extra accidentals and let the later have precedence over the former
74
+ accidentals = [b if b is not None else a for a,
75
+ b in zip(basic_accidentals, extra_accidentals)]
76
+
77
+ basic_notes_in_key = [x+y for x,
78
+ y in zip(doremi2chromatic, basic_accidentals)]
79
+ current_notes_in_key = [x+y for x, y in zip(doremi2chromatic, accidentals)]
80
+
81
+ change = None
82
+
83
+ # if not note in current_notes_in_key:
84
+ # if
85
+
86
+ return {None: '',
87
+ 0: '=',
88
+ -1: '_',
89
+ 1: '^'}[change]
90
+
91
+
92
+ # map from do-re-mi scale index to chromatic interval
93
+ doremi2chromatic = [0, 2, 4, 5, 7, 9, 11, 13]
94
+
95
+ # map from note name to note number in a C scale
96
+ note2number = 'cdefgab'
97
+
98
+ notes_sharp = 'c c# d d# e f f# g g# a a# b'.split()
99
+ notes_flat = 'c db d eb e f gb g ab a bb b'.split()
100
+
101
+
102
+ def get_accidentals(fifths):
103
+ # determine the accidentals for one octave
104
+ steps = 'C D E F G A B'.split()
105
+ one_octave = [0] * 7
106
+ if fifths > 0:
107
+ for i in range(fifths):
108
+ one_octave[(3 + i*4) % 7] = 1
109
+ else:
110
+ for i in range(fifths+1, 1):
111
+ one_octave[(6 + i*4) % 7] = -1
112
+ scale = []
113
+ for i in range(7):
114
+ scale.append(steps[i] + {-1: 'b', 0: '', 1: '#'}[one_octave[i]])
115
+ print(' '.join(scale))
116
+
117
+ # use the same for all octaves
118
+ accidentals = {}
119
+ for octave in range(1, 7):
120
+ for step in range(7):
121
+ note = '%s%s' % (steps[step], octave) # eg. G4
122
+ accidentals[note] = one_octave[step]
123
+ return accidentals
124
+
125
+
126
+ def get_accidentals_for_key(key):
127
+ ''' returns a list of seven values corresponding to the notes in the C scale. 1 means #, -1 means b, and 0 means no change. '''
128
+ key = key.lower().strip().strip()
129
+ sharp_or_flat, number = key_data[key]
130
+ # initially no of the seven notes are flat or sharp (0=neutral)
131
+ accidentals = [0] * 7
132
+ # add accidentals
133
+ for i in range(number):
134
+ if sharp_or_flat == '#':
135
+ accidentals[(3 - 3*i) % 7] = 1
136
+ else:
137
+ accidentals[(6 + 3*i) % 7] = -1
138
+ return accidentals
139
+
140
+
141
+ class Note:
142
+ def __init__(self, note, duration, ties_to_next=False, broken_rythm=''):
143
+ self.note = note # number of semitones from centre note
144
+ self.duration = duration # duration expressed as a Fraction object
145
+ # whether the abc representation of this note ended with '-' representing a tie to the next note
146
+ self.ties_to_next = ties_to_next
147
+ self.broken_rythm = broken_rythm # the ABC broken rythm symbol or an empty string
148
+ # (used when the duration of the next note is established)
149
+
150
+ def __repr__(self):
151
+ ''' returns a string representation of this Note object '''
152
+ notes = ('c', 'c#', 'd', 'd#', 'e', 'f',
153
+ 'f#', 'g', 'g#', 'a', 'a#', 'b')
154
+ n = notes[self.note % 12].upper()
155
+ octave = self.note / 12
156
+ if octave > 0:
157
+ if octave >= 1:
158
+ n = n.lower()
159
+ if octave > 1:
160
+ n = n + "'" * (octave - 1)
161
+ elif octave < 0:
162
+ if abs(octave) > 1:
163
+ n = n + "," * abs(octave)
164
+ return n # + str(self.duration)
165
+
166
+ def __str__(self):
167
+ return repr(self)
168
+
169
+ # print get_best_key_for_midi_notes([0, 2, 4, 6, 6, 6, 7, 0])
170
+ # print get_base_note_for_key('c')