-
Notifications
You must be signed in to change notification settings - Fork 0
/
midi-buzzer-alsa.py
623 lines (596 loc) · 23.2 KB
/
midi-buzzer-alsa.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
#!/usr/bin/env python
# ALSA MIDI Buzzer
# Version 1.4, (c) 2019-2020 Freddy Feuerstein License: DWTFYW
# ALSA MIDI Buzzer is a Python program to play MIDI by beeping
# through the computer's soundcard. It should work on any machine that has the
# "ffmpeg" and "alsa" Linux package.
force_monophonic = 1
# ----------------------------------------------------
import os
frequency=0
# NSLU2 hack:
try: event=open("/proc/bus/input/devices").read()
except IOError: event=""
if "ixp4xx beeper" in event:
h=event[event.find("Handlers=",event.index("ixp4xx beeper")):]
event="-e /dev/input/"+(h[:h.find("\n")].split()[-1])
import os ; os.system("sync") # just in case (beep has been known to crash NSLU2 Debian Etch in rare conditions)
else: event=""
min_pulseLength, max_pulseLength = 10,20 # milliseconds
repetitions_to_aim_for = 1 # arpeggiating each chord only once will do if it's brief
def chord(freqList,millisecs):
if not freqList: return " -D %d" % (millisecs,) # rest
elif len(freqList)==1: return " -n -f %d -l %d" % (freqList[0],millisecs) # one note
else:
pulseLength = max(min(millisecs/len(freqList)/repetitions_to_aim_for,max_pulseLength),min_pulseLength)
return (" -D 0".join([chord([f],pulseLength) for f in freqList]))*max(1,millisecs/pulseLength/len(freqList)) # (max with 1 means at least 1 repetition - prefer a slight slow-down to missing a chord out)
# (the above -D 0 is necessary because Debian 5's beep adds a default delay otherwise)
command_line_len = 2 # reduce this if you get "argument list too long" (NB the real limit is slightly more than this value)
def validateSleepBefore(sleepbefore):
sleepbefore = sleepbefore.replace("D ", "")
if int(sleepbefore) < 100 and int(sleepbefore) > 9:
sleepbefore=(str("0") + sleepbefore)
if int(sleepbefore) < 10:
sleepbefore=(str("00") + sleepbefore)
return(sleepbefore)
def validateTimeOut(timeout):
timeout = timeout.replace("l ", "")
if int(timeout) < 100 and int(timeout) > 9:
timeout=(str("0") + timeout)
if int(timeout) < 10:
timeout=(str("00") + timeout)
if int(timeout) == 0:
timeout=(str("020"))
# if int(timeout) >= 1000:
# timeout=(str("999"))
return(timeout)
def runBeep(params):
while " -n" in params: # not entirely silence
params=params[params.find(" -n")+3:] # discard the initial "-n" and any delay before it
brkAt = params.find(" -n",command_line_len)
if brkAt>-1: thisP,params = params[:brkAt],params[brkAt:]
else: thisP,params = params,""
# print("args:"+thisP)
argssplit = 0
argssplit = thisP.split("-")
argssplit.pop(0)
# print(argssplit)
if len(argssplit) >= 3:
freqout, timeout, sleepbefore = argssplit
else:
freqout, timeout = argssplit
freqout = freqout.replace("f ", "")
sleepbefore = validateSleepBefore(sleepbefore)
timeout = validateTimeOut(timeout)
print("Frequency of note: "+freqout+"\nLength of note: "+timeout+"\nSleep : "+sleepbefore)
if int(timeout) <= 999:
os.system("ffplay -f lavfi -i \"sine=frequency="+ freqout +":duration=0."+ timeout +"\" -autoexit -nodisp -loglevel quiet -stats")
else:
os.system("ffplay -f lavfi -i \"sine=frequency="+ freqout +":duration="+ str(int(timeout)/1000) +"\" -autoexit -nodisp -loglevel quiet -stats")
if int(sleepbefore) > 0:
os.system("sleep 0."+sleepbefore)
A=440
midi_note_to_freq = []
import math
for i in range(128): midi_note_to_freq.append((A/32.0)*math.pow(2,(len(midi_note_to_freq)-9)/12.0))
assert midi_note_to_freq[69] == A
cumulative_params = []
current_chord = [[],0]
def add_midi_note_chord(noteNos,microsecs):
millisecs = microsecs / 1000
global current_chord
if force_monophonic and noteNos: noteNos=[max(noteNos)]
else: noteNos.sort()
if noteNos == current_chord[0]:
current_chord[1] += millisecs
return
else:
add_midi_note_chord_real(current_chord[0],current_chord[1])
current_chord = [noteNos,millisecs]
def add_midi_note_chord_real(noteNos,millisecs):
def to_freq(n):
if n==int(n): return midi_note_to_freq[int(n)]
else: return (A/32.0)*math.pow(2,(n-9)/12.0)
if noteNos and cumulative_params and not "-D" in cumulative_params[-1].split()[-2:]: cumulative_params.append("-D 0") # necessary because Debian 5's beep adds a default delay otherwise
cumulative_params.append(chord(map(lambda x:to_freq(x),noteNos),millisecs))
# Some of the code below is taken from Python Midi Package by Max M,
# http://www.mxm.dk/products/public/pythonmidi
# with much cutting-down and modifying
import sys
from types import StringType
from cStringIO import StringIO
from struct import pack, unpack
def getNibbles(byte): return (byte >> 4 & 0xF, byte & 0xF)
def setNibbles(hiNibble, loNibble):
return (hiNibble << 4) + loNibble
def readBew(value):
return unpack('>%s' % {1:'B', 2:'H', 4:'L'}[len(value)], value)[0]
def readVar(value):
sum = 0
for byte in unpack('%sB' % len(value), value):
sum = (sum << 7) + (byte & 0x7F)
if not 0x80 & byte: break
return sum
def varLen(value):
if value <= 127:
return 1
elif value <= 16383:
return 2
elif value <= 2097151:
return 3
else:
return 4
def to_n_bits(value, length=1, nbits=7):
bytes = [(value >> (i*nbits)) & 0x7F for i in range(length)]
bytes.reverse()
return bytes
def toBytes(value):
return unpack('%sB' % len(value), value)
def fromBytes(value):
if not value:
return ''
return pack('%sB' % len(value), *value)
NOTE_OFF = 0x80
NOTE_ON = 0x90
AFTERTOUCH = 0xA0
CONTINUOUS_CONTROLLER = 0xB0
PATCH_CHANGE = 0xC0
CHANNEL_PRESSURE = 0xD0
PITCH_BEND = 0xE0
BANK_SELECT = 0x00
MODULATION_WHEEL = 0x01
BREATH_CONTROLLER = 0x02
FOOT_CONTROLLER = 0x04
PORTAMENTO_TIME = 0x05
DATA_ENTRY = 0x06
CHANNEL_VOLUME = 0x07
BALANCE = 0x08
PAN = 0x0A
EXPRESSION_CONTROLLER = 0x0B
EFFECT_CONTROL_1 = 0x0C
EFFECT_CONTROL_2 = 0x0D
GEN_PURPOSE_CONTROLLER_1 = 0x10
GEN_PURPOSE_CONTROLLER_2 = 0x11
GEN_PURPOSE_CONTROLLER_3 = 0x12
GEN_PURPOSE_CONTROLLER_4 = 0x13
BANK_SELECT = 0x20
MODULATION_WHEEL = 0x21
BREATH_CONTROLLER = 0x22
FOOT_CONTROLLER = 0x24
PORTAMENTO_TIME = 0x25
DATA_ENTRY = 0x26
CHANNEL_VOLUME = 0x27
BALANCE = 0x28
PAN = 0x2A
EXPRESSION_CONTROLLER = 0x2B
EFFECT_CONTROL_1 = 0x2C
EFFECT_CONTROL_2 = 0x2D
GENERAL_PURPOSE_CONTROLLER_1 = 0x30
GENERAL_PURPOSE_CONTROLLER_2 = 0x31
GENERAL_PURPOSE_CONTROLLER_3 = 0x32
GENERAL_PURPOSE_CONTROLLER_4 = 0x33
SUSTAIN_ONOFF = 0x40
PORTAMENTO_ONOFF = 0x41
SOSTENUTO_ONOFF = 0x42
SOFT_PEDAL_ONOFF = 0x43
LEGATO_ONOFF = 0x44
HOLD_2_ONOFF = 0x45
SOUND_CONTROLLER_1 = 0x46
SOUND_CONTROLLER_2 = 0x47
SOUND_CONTROLLER_3 = 0x48
SOUND_CONTROLLER_4 = 0x49
SOUND_CONTROLLER_5 = 0x4A
SOUND_CONTROLLER_7 = 0x4C
SOUND_CONTROLLER_8 = 0x4D
SOUND_CONTROLLER_9 = 0x4E
SOUND_CONTROLLER_10 = 0x4F
GENERAL_PURPOSE_CONTROLLER_5 = 0x50
GENERAL_PURPOSE_CONTROLLER_6 = 0x51
GENERAL_PURPOSE_CONTROLLER_7 = 0x52
GENERAL_PURPOSE_CONTROLLER_8 = 0x53
PORTAMENTO_CONTROL = 0x54
EFFECTS_1 = 0x5B
EFFECTS_2 = 0x5C
EFFECTS_3 = 0x5D
EFFECTS_4 = 0x5E
EFFECTS_5 = 0x5F
DATA_INCREMENT = 0x60
DATA_DECREMENT = 0x61
NON_REGISTERED_PARAMETER_NUMBER = 0x62
NON_REGISTERED_PARAMETER_NUMBER = 0x63
REGISTERED_PARAMETER_NUMBER = 0x64
REGISTERED_PARAMETER_NUMBER = 0x65
ALL_SOUND_OFF = 0x78
RESET_ALL_CONTROLLERS = 0x79
LOCAL_CONTROL_ONOFF = 0x7A
ALL_NOTES_OFF = 0x7B
OMNI_MODE_OFF = 0x7C
OMNI_MODE_ON = 0x7D
MONO_MODE_ON = 0x7E
POLY_MODE_ON = 0x7F
SYSTEM_EXCLUSIVE = 0xF0
MTC = 0xF1
SONG_POSITION_POINTER = 0xF2
SONG_SELECT = 0xF3
TUNING_REQUEST = 0xF6
END_OFF_EXCLUSIVE = 0xF7
SEQUENCE_NUMBER = 0x00
TEXT = 0x01
COPYRIGHT = 0x02
SEQUENCE_NAME = 0x03
INSTRUMENT_NAME = 0x04
LYRIC = 0x05
MARKER = 0x06
CUEPOINT = 0x07
PROGRAM_NAME = 0x08
DEVICE_NAME = 0x09
MIDI_CH_PREFIX = 0x20
MIDI_PORT = 0x21
END_OF_TRACK = 0x2F
TEMPO = 0x51
SMTP_OFFSET = 0x54
TIME_SIGNATURE = 0x58
KEY_SIGNATURE = 0x59
SPECIFIC = 0x7F
FILE_HEADER = 'MThd'
TRACK_HEADER = 'MTrk'
TIMING_CLOCK = 0xF8
SONG_START = 0xFA
SONG_CONTINUE = 0xFB
SONG_STOP = 0xFC
ACTIVE_SENSING = 0xFE
SYSTEM_RESET = 0xFF
META_EVENT = 0xFF
def is_status(byte):
return (byte & 0x80) == 0x80
class MidiToBeep:
def update_time(self, new_time=0, relative=1):
if relative:
self._relative_time = new_time
self._absolute_time += new_time
else:
self._relative_time = new_time - self._absolute_time
self._absolute_time = new_time
if self._relative_time:
# time was advanced, so output something
d = {}
for c,v in self.current_notes_on: d[v+self.semitonesAdd[c]]=1
if self.need_to_interleave_tracks: self.tracks[-1].append([d.keys(),self._relative_time*self.microsecsPerDivision])
else: add_midi_note_chord(d.keys(),self._relative_time*self.microsecsPerDivision)
def reset_time(self):
self._relative_time = 0
self._absolute_time = 0
def rel_time(self): return self._relative_time
def abs_time(self): return self._absolute_time
def reset_run_stat(self): self._running_status = None
def set_run_stat(self, new_status): self._running_status = new_status
def get_run_stat(self): return self._running_status
def set_current_track(self, new_track): self._current_track = new_track
def get_current_track(self): return self._current_track
def __init__(self):
self._absolute_time = 0
self._relative_time = 0
self._current_track = 0
self._running_status = None
self.current_notes_on = []
self.rpnLsb = [0]*16
self.rpnMsb = [0]*16
self.semitoneRange = [1]*16
self.semitonesAdd = [0]*16
self.microsecsPerDivision = 10000
def note_on(self, channel=0, note=0x40, velocity=0x40):
if velocity and not channel==9: self.current_notes_on.append((channel,note))
def note_off(self, channel=0, note=0x40, velocity=0x40):
try: self.current_notes_on.remove((channel,note))
except ValueError: pass
def aftertouch(self, channel=0, note=0x40, velocity=0x40): pass
def continuous_controller(self, channel, controller, value):
# Interpret "pitch bend range":
if controller==64: self.rpnLsb[channel] = value
elif controller==65: self.rpnMsb[channel] = value
elif controller==6 and self.rpnLsb[channel]==self.rpnMsb[channel]==0:
self.semitoneRange[channel]=value
def patch_change(self, channel, patch): pass
def channel_pressure(self, channel, pressure): pass
def pitch_bend(self, channel, value):
# Pitch bend is sometimes used for slurs
# so we'd better interpret it (only MSB for now; full range is over 8192)
self.semitonesAdd[channel] = (value-64)*self.semitoneRange[channel]/64.0
def sysex_event(self, data): pass
def midi_time_code(self, msg_type, values): pass
def song_position_pointer(self, value): pass
def song_select(self, songNumber): pass
def tuning_request(self): pass
def header(self, format=0, nTracks=1, division=96):
self.division=division
self.need_to_interleave_tracks = (format==1)
self.tracks = [[]][:]
def eof(self):
if self.need_to_interleave_tracks:
while True: # delete empty tracks
try: self.tracks.remove([])
except ValueError: break
while self.tracks:
minLen = min([t[0][1] for t in self.tracks])
d = {}
for t in self.tracks: d.update([(n,1) for n in t[0][0]])
add_midi_note_chord(d.keys(),minLen)
for t in self.tracks:
t[0][1] -= minLen
if t[0][1]==0: del t[0]
while True: # delete empty tracks
try: self.tracks.remove([])
except ValueError: break
def meta_event(self, meta_type, data): pass
def start_of_track(self, n_track=0):
self.reset_time()
self._current_track += 1
if self.need_to_interleave_tracks: self.tracks.append([])
def end_of_track(self): pass
def sequence_number(self, value): pass
def text(self, text): pass
def copyright(self, text): pass
def sequence_name(self, text): pass
def instrument_name(self, text): pass
def lyric(self, text): pass
def marker(self, text): pass
def cuepoint(self, text): pass
def program_name(self,progname): pass
def device_name(self,devicename): pass
def midi_ch_prefix(self, channel): pass
def midi_port(self, value): pass
def tempo(self, value):
# TODO if need_to_interleave_tracks, and tempo is not already put in on all tracks, and there's a tempo command that's not at the start and/or not on 1st track, we may need to do something
self.microsecsPerDivision = value/self.division
def smtp_offset(self, hour, minute, second, frame, framePart): pass
def time_signature(self, nn, dd, cc, bb): pass
def key_signature(self, sf, mi): pass
def sequencer_specific(self, data): pass
class RawInstreamFile:
def __init__(self, infile=''):
if infile:
if isinstance(infile, StringType):
infile = open(infile, 'rb')
self.data = infile.read()
infile.close()
else:
self.data = infile.read()
else:
self.data = ''
self.cursor = 0
def setData(self, data=''):
self.data = data
def setCursor(self, position=0):
self.cursor = position
def getCursor(self):
return self.cursor
def moveCursor(self, relative_position=0):
self.cursor += relative_position
def nextSlice(self, length, move_cursor=1):
c = self.cursor
slc = self.data[c:c+length]
if move_cursor:
self.moveCursor(length)
return slc
def readBew(self, n_bytes=1, move_cursor=1):
return readBew(self.nextSlice(n_bytes, move_cursor))
def readVarLen(self):
MAX_VARLEN = 4
var = readVar(self.nextSlice(MAX_VARLEN, 0))
self.moveCursor(varLen(var))
return var
class EventDispatcher:
def __init__(self, outstream):
self.outstream = outstream
self.convert_zero_velocity = 1
self.dispatch_continuos_controllers = 1
self.dispatch_meta_events = 1
def header(self, format, nTracks, division):
self.outstream.header(format, nTracks, division)
def start_of_track(self, current_track):
self.outstream.set_current_track(current_track)
self.outstream.start_of_track(current_track)
def sysex_event(self, data):
self.outstream.sysex_event(data)
def eof(self):
self.outstream.eof()
def update_time(self, new_time=0, relative=1):
self.outstream.update_time(new_time, relative)
def reset_time(self):
self.outstream.reset_time()
def channel_messages(self, hi_nible, channel, data):
stream = self.outstream
data = toBytes(data)
if (NOTE_ON & 0xF0) == hi_nible:
note, velocity = data
if velocity==0 and self.convert_zero_velocity:
stream.note_off(channel, note, 0x40)
else:
stream.note_on(channel, note, velocity)
elif (NOTE_OFF & 0xF0) == hi_nible:
note, velocity = data
stream.note_off(channel, note, velocity)
elif (AFTERTOUCH & 0xF0) == hi_nible:
note, velocity = data
stream.aftertouch(channel, note, velocity)
elif (CONTINUOUS_CONTROLLER & 0xF0) == hi_nible:
controller, value = data
if self.dispatch_continuos_controllers:
self.continuous_controllers(channel, controller, value)
else:
stream.continuous_controller(channel, controller, value)
elif (PATCH_CHANGE & 0xF0) == hi_nible:
program = data[0]
stream.patch_change(channel, program)
elif (CHANNEL_PRESSURE & 0xF0) == hi_nible:
pressure = data[0]
stream.channel_pressure(channel, pressure)
elif (PITCH_BEND & 0xF0) == hi_nible:
hibyte, lobyte = data
value = (hibyte<<7) + lobyte
stream.pitch_bend(channel, value)
else:
raise ValueError, 'Illegal channel message!'
def continuous_controllers(self, channel, controller, value):
stream = self.outstream
stream.continuous_controller(channel, controller, value)
def system_commons(self, common_type, common_data):
stream = self.outstream
if common_type == MTC:
data = readBew(common_data)
msg_type = (data & 0x07) >> 4
values = (data & 0x0F)
stream.midi_time_code(msg_type, values)
elif common_type == SONG_POSITION_POINTER:
hibyte, lobyte = toBytes(common_data)
value = (hibyte<<7) + lobyte
stream.song_position_pointer(value)
elif common_type == SONG_SELECT:
data = readBew(common_data)
stream.song_select(data)
elif common_type == TUNING_REQUEST:
stream.tuning_request(time=None)
def meta_events(self, meta_type, data):
stream = self.outstream
if meta_type == SEQUENCE_NUMBER:
number = readBew(data)
stream.sequence_number(number)
elif meta_type == TEXT:
stream.text(data)
elif meta_type == COPYRIGHT:
stream.copyright(data)
elif meta_type == SEQUENCE_NAME:
stream.sequence_name(data)
elif meta_type == INSTRUMENT_NAME:
stream.instrument_name(data)
elif meta_type == LYRIC:
stream.lyric(data)
elif meta_type == MARKER:
stream.marker(data)
elif meta_type == CUEPOINT:
stream.cuepoint(data)
elif meta_type == PROGRAM_NAME:
stream.program_name(data)
elif meta_type == DEVICE_NAME:
stream.device_name(data)
elif meta_type == MIDI_CH_PREFIX:
channel = readBew(data)
stream.midi_ch_prefix(channel)
elif meta_type == MIDI_PORT:
port = readBew(data)
stream.midi_port(port)
elif meta_type == END_OF_TRACK:
stream.end_of_track()
elif meta_type == TEMPO:
b1, b2, b3 = toBytes(data)
stream.tempo((b1<<16) + (b2<<8) + b3)
elif meta_type == SMTP_OFFSET:
hour, minute, second, frame, framePart = toBytes(data)
stream.smtp_offset(
hour, minute, second, frame, framePart)
elif meta_type == TIME_SIGNATURE:
nn, dd, cc, bb = toBytes(data)
stream.time_signature(nn, dd, cc, bb)
elif meta_type == KEY_SIGNATURE:
sf, mi = toBytes(data)
stream.key_signature(sf, mi)
elif meta_type == SPECIFIC:
meta_data = toBytes(data)
stream.sequencer_specific(meta_data)
else:
meta_data = toBytes(data)
stream.meta_event(meta_type, meta_data)
class MidiFileParser:
def __init__(self, raw_in, outstream):
self.raw_in = raw_in
self.dispatch = EventDispatcher(outstream)
self._running_status = None
def parseMThdChunk(self):
raw_in = self.raw_in
header_chunk_type = raw_in.nextSlice(4)
header_chunk_zise = raw_in.readBew(4)
if header_chunk_type != 'MThd': raise TypeError, "It is not a valid midi file!"
self.format = raw_in.readBew(2)
self.nTracks = raw_in.readBew(2)
self.division = raw_in.readBew(2)
if header_chunk_zise > 6:
raw_in.moveCursor(header_chunk_zise-6)
self.dispatch.header(self.format, self.nTracks, self.division)
def parseMTrkChunk(self):
self.dispatch.reset_time()
dispatch = self.dispatch
raw_in = self.raw_in
dispatch.start_of_track(self._current_track)
raw_in.moveCursor(4)
tracklength = raw_in.readBew(4)
track_endposition = raw_in.getCursor() + tracklength
while raw_in.getCursor() < track_endposition:
time = raw_in.readVarLen()
dispatch.update_time(time)
peak_ahead = raw_in.readBew(move_cursor=0)
if (peak_ahead & 0x80):
status = self._running_status = raw_in.readBew()
else:
status = self._running_status
hi_nible, lo_nible = status & 0xF0, status & 0x0F
if status == META_EVENT:
meta_type = raw_in.readBew()
meta_length = raw_in.readVarLen()
meta_data = raw_in.nextSlice(meta_length)
dispatch.meta_events(meta_type, meta_data)
elif status == SYSTEM_EXCLUSIVE:
sysex_length = raw_in.readVarLen()
sysex_data = raw_in.nextSlice(sysex_length-1)
if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE:
eo_sysex = raw_in.readBew()
dispatch.sysex_event(sysex_data)
elif hi_nible == 0xF0:
data_sizes = {
MTC:1,
SONG_POSITION_POINTER:2,
SONG_SELECT:1,
}
data_size = data_sizes.get(hi_nible, 0)
common_data = raw_in.nextSlice(data_size)
common_type = lo_nible
dispatch.system_common(common_type, common_data)
else:
data_sizes = {
PATCH_CHANGE:1,
CHANNEL_PRESSURE:1,
NOTE_OFF:2,
NOTE_ON:2,
AFTERTOUCH:2,
CONTINUOUS_CONTROLLER:2,
PITCH_BEND:2,
}
data_size = data_sizes.get(hi_nible, 0)
channel_data = raw_in.nextSlice(data_size)
event_type, channel = hi_nible, lo_nible
dispatch.channel_messages(event_type, channel, channel_data)
def parseMTrkChunks(self):
for t in range(self.nTracks):
self._current_track = t
self.parseMTrkChunk()
self.dispatch.eof()
class MidiInFile:
def __init__(self, outStream, infile=''):
self.raw_in = RawInstreamFile(infile)
self.parser = MidiFileParser(self.raw_in, outStream)
def read(self):
p = self.parser
p.parseMThdChunk()
p.parseMTrkChunks()
def setData(self, data=''):
self.raw_in.setData(data)
print "ALSA MIDI Buzzer (c) 2019-2020 Freddy F. License: DWTFYW"
if len(sys.argv)<2:
print "Syntax: python midi-buzzer-alsa.py MIDI-filename ..."
for midiFile in sys.argv[1:]:
cumulative_params = []
current_chord = [[],0][:]
print "Parsing MIDI file",midiFile
MidiInFile(MidiToBeep(), open(midiFile,"rb")).read()
print "Playing",midiFile
add_midi_note_chord([],0) # ensure flushed
runBeep(" ".join(cumulative_params))