asigalov61 commited on
Commit
7649c26
1 Parent(s): 3888ab7

Upload MIDI.py

Browse files
Files changed (1) hide show
  1. MIDI.py +1732 -0
MIDI.py ADDED
@@ -0,0 +1,1732 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #! /usr/bin/python3
2
+ # unsupported 20091104 ...
3
+ # ['set_sequence_number', dtime, sequence]
4
+ # ['raw_data', dtime, raw]
5
+
6
+ # 20150914 jimbo1qaz MIDI.py str/bytes bug report
7
+ # I found a MIDI file which had Shift-JIS titles. When midi.py decodes it as
8
+ # latin-1, it produces a string which cannot even be accessed without raising
9
+ # a UnicodeDecodeError. Maybe, when converting raw byte strings from MIDI,
10
+ # you should keep them as bytes, not improperly decode them. However, this
11
+ # would change the API. (ie: text = a "string" ? of 0 or more bytes). It
12
+ # could break compatiblity, but there's not much else you can do to fix the bug
13
+ # https://en.wikipedia.org/wiki/Shift_JIS
14
+
15
+ r'''
16
+ This module offers functions: concatenate_scores(), grep(),
17
+ merge_scores(), mix_scores(), midi2opus(), midi2score(), opus2midi(),
18
+ opus2score(), play_score(), score2midi(), score2opus(), score2stats(),
19
+ score_type(), segment(), timeshift() and to_millisecs(),
20
+ where "midi" means the MIDI-file bytes (as can be put in a .mid file,
21
+ or piped into aplaymidi), and "opus" and "score" are list-structures
22
+ as inspired by Sean Burke's MIDI-Perl CPAN module.
23
+
24
+ Warning: Version 6.4 is not necessarily backward-compatible with
25
+ previous versions, in that text-data is now bytes, not strings.
26
+ This reflects the fact that many MIDI files have text data in
27
+ encodings other that ISO-8859-1, for example in Shift-JIS.
28
+
29
+ Download MIDI.py from http://www.pjb.com.au/midi/free/MIDI.py
30
+ and put it in your PYTHONPATH. MIDI.py depends on Python3.
31
+
32
+ There is also a call-compatible translation into Lua of this
33
+ module: see http://www.pjb.com.au/comp/lua/MIDI.html
34
+
35
+ Backup web site: https://peterbillam.gitlab.io/miditools/
36
+
37
+ The "opus" is a direct translation of the midi-file-events, where
38
+ the times are delta-times, in ticks, since the previous event.
39
+
40
+ The "score" is more human-centric; it uses absolute times, and
41
+ combines the separate note_on and note_off events into one "note"
42
+ event, with a duration:
43
+ ['note', start_time, duration, channel, note, velocity] # in a "score"
44
+
45
+ EVENTS (in an "opus" structure)
46
+ ['note_off', dtime, channel, note, velocity] # in an "opus"
47
+ ['note_on', dtime, channel, note, velocity] # in an "opus"
48
+ ['key_after_touch', dtime, channel, note, velocity]
49
+ ['control_change', dtime, channel, controller(0-127), value(0-127)]
50
+ ['patch_change', dtime, channel, patch]
51
+ ['channel_after_touch', dtime, channel, velocity]
52
+ ['pitch_wheel_change', dtime, channel, pitch_wheel]
53
+ ['text_event', dtime, text]
54
+ ['copyright_text_event', dtime, text]
55
+ ['track_name', dtime, text]
56
+ ['instrument_name', dtime, text]
57
+ ['lyric', dtime, text]
58
+ ['marker', dtime, text]
59
+ ['cue_point', dtime, text]
60
+ ['text_event_08', dtime, text]
61
+ ['text_event_09', dtime, text]
62
+ ['text_event_0a', dtime, text]
63
+ ['text_event_0b', dtime, text]
64
+ ['text_event_0c', dtime, text]
65
+ ['text_event_0d', dtime, text]
66
+ ['text_event_0e', dtime, text]
67
+ ['text_event_0f', dtime, text]
68
+ ['end_track', dtime]
69
+ ['set_tempo', dtime, tempo]
70
+ ['smpte_offset', dtime, hr, mn, se, fr, ff]
71
+ ['time_signature', dtime, nn, dd, cc, bb]
72
+ ['key_signature', dtime, sf, mi]
73
+ ['sequencer_specific', dtime, raw]
74
+ ['raw_meta_event', dtime, command(0-255), raw]
75
+ ['sysex_f0', dtime, raw]
76
+ ['sysex_f7', dtime, raw]
77
+ ['song_position', dtime, song_pos]
78
+ ['song_select', dtime, song_number]
79
+ ['tune_request', dtime]
80
+
81
+ DATA TYPES
82
+ channel = a value 0 to 15
83
+ controller = 0 to 127 (see http://www.pjb.com.au/muscript/gm.html#cc )
84
+ dtime = time measured in "ticks", 0 to 268435455
85
+ velocity = a value 0 (soft) to 127 (loud)
86
+ note = a value 0 to 127 (middle-C is 60)
87
+ patch = 0 to 127 (see http://www.pjb.com.au/muscript/gm.html )
88
+ pitch_wheel = a value -8192 to 8191 (0x1FFF)
89
+ raw = bytes, of length 0 or more (for sysex events see below)
90
+ sequence_number = a value 0 to 65,535 (0xFFFF)
91
+ song_pos = a value 0 to 16,383 (0x3FFF)
92
+ song_number = a value 0 to 127
93
+ tempo = microseconds per crochet (quarter-note), 0 to 16777215
94
+ text = bytes, of length 0 or more
95
+ ticks = the number of ticks per crochet (quarter-note)
96
+
97
+ In sysex_f0 events, the raw data must not start with a \xF0 byte,
98
+ since this gets added automatically;
99
+ but it must end with an explicit \xF7 byte!
100
+ In the very unlikely case that you ever need to split sysex data
101
+ into one sysex_f0 followed by one or more sysex_f7s, then only the
102
+ last of those sysex_f7 events must end with the explicit \xF7 byte
103
+ (again, the raw data of individual sysex_f7 events must not start
104
+ with any \xF7 byte, since this gets added automatically).
105
+
106
+ Since version 6.4, text data is in bytes, not in a ISO-8859-1 string.
107
+
108
+
109
+ GOING THROUGH A SCORE WITHIN A PYTHON PROGRAM
110
+ channels = {2,3,5,8,13}
111
+ itrack = 1 # skip 1st element which is ticks
112
+ while itrack < len(score):
113
+ for event in score[itrack]:
114
+ if event[0] == 'note': # for example,
115
+ pass # do something to all notes
116
+ # or, to work on events in only particular channels...
117
+ channel_index = MIDI.Event2channelindex.get(event[0], False)
118
+ if channel_index and (event[channel_index] in channels):
119
+ pass # do something to channels 2,3,5,8 and 13
120
+ itrack += 1
121
+
122
+ '''
123
+
124
+ import sys, struct, copy
125
+ # sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb')
126
+ Version = '6.7'
127
+ VersionDate = '20201120'
128
+ # 20201120 6.7 call to bytest() removed, and protect _unshift_ber_int
129
+ # 20160702 6.6 to_millisecs() now handles set_tempo across multiple Tracks
130
+ # 20150921 6.5 segment restores controllers as well as patch and tempo
131
+ # 20150914 6.4 text data is bytes or bytearray, not ISO-8859-1 strings
132
+ # 20150628 6.3 absent any set_tempo, default is 120bpm (see MIDI file spec 1.1)
133
+ # 20150101 6.2 all text events can be 8-bit; let user get the right encoding
134
+ # 20141231 6.1 fix _some_text_event; sequencer_specific data can be 8-bit
135
+ # 20141230 6.0 synth_specific data can be 8-bit
136
+ # 20120504 5.9 add the contents of mid_opus_tracks()
137
+ # 20120208 5.8 fix num_notes_by_channel() ; should be a dict
138
+ # 20120129 5.7 _encode handles empty tracks; score2stats num_notes_by_channel
139
+ # 20111111 5.6 fix patch 45 and 46 in Number2patch, should be Harp
140
+ # 20110129 5.5 add mix_opus_tracks() and event2alsaseq()
141
+ # 20110126 5.4 "previous message repeated N times" to save space on stderr
142
+ # 20110125 5.2 opus2score terminates unended notes at the end of the track
143
+ # 20110124 5.1 the warnings in midi2opus display track_num
144
+ # 21110122 5.0 if garbage, midi2opus returns the opus so far
145
+ # 21110119 4.9 non-ascii chars stripped out of the text_events
146
+ # 21110110 4.8 note_on with velocity=0 treated as a note-off
147
+ # 21110108 4.6 unknown F-series event correctly eats just one byte
148
+ # 21011010 4.2 segment() uses start_time, end_time named params
149
+ # 21011005 4.1 timeshift() must not pad the set_tempo command
150
+ # 21011003 4.0 pitch2note_event must be chapitch2note_event
151
+ # 21010918 3.9 set_sequence_number supported, FWIW
152
+ # 20100913 3.7 many small bugfixes; passes all tests
153
+ # 20100910 3.6 concatenate_scores enforce ticks=1000, just like merge_scores
154
+ # 20100908 3.5 minor bugs fixed in score2stats
155
+ # 20091104 3.4 tune_request now supported
156
+ # 20091104 3.3 fixed bug in decoding song_position and song_select
157
+ # 20091104 3.2 unsupported: set_sequence_number tune_request raw_data
158
+ # 20091101 3.1 document how to traverse a score within Python
159
+ # 20091021 3.0 fixed bug in score2stats detecting GM-mode = 0
160
+ # 20091020 2.9 score2stats reports GM-mode and bank msb,lsb events
161
+ # 20091019 2.8 in merge_scores, channel 9 must remain channel 9 (in GM)
162
+ # 20091018 2.7 handles empty tracks gracefully
163
+ # 20091015 2.6 grep() selects channels
164
+ # 20091010 2.5 merge_scores reassigns channels to avoid conflicts
165
+ # 20091010 2.4 fixed bug in to_millisecs which now only does opusses
166
+ # 20091010 2.3 score2stats returns channels & patch_changes, by_track & total
167
+ # 20091010 2.2 score2stats() returns also pitches and percussion dicts
168
+ # 20091010 2.1 bugs: >= not > in segment, to notice patch_change at time 0
169
+ # 20091010 2.0 bugs: spurious pop(0) ( in _decode sysex
170
+ # 20091008 1.9 bugs: ISO decoding in sysex; str( not int( in note-off warning
171
+ # 20091008 1.8 add concatenate_scores()
172
+ # 20091006 1.7 score2stats() measures nticks and ticks_per_quarter
173
+ # 20091004 1.6 first mix_scores() and merge_scores()
174
+ # 20090424 1.5 timeshift() bugfix: earliest only sees events after from_time
175
+ # 20090330 1.4 timeshift() has also a from_time argument
176
+ # 20090322 1.3 timeshift() has also a start_time argument
177
+ # 20090319 1.2 add segment() and timeshift()
178
+ # 20090301 1.1 add to_millisecs()
179
+
180
+ _previous_warning = '' # 5.4
181
+ _previous_times = 0 # 5.4
182
+ #------------------------------- Encoding stuff --------------------------
183
+
184
+ def opus2midi(opus=[]):
185
+ r'''The argument is a list: the first item in the list is the "ticks"
186
+ parameter, the others are the tracks. Each track is a list
187
+ of midi-events, and each event is itself a list; see above.
188
+ opus2midi() returns a bytestring of the MIDI, which can then be
189
+ written either to a file opened in binary mode (mode='wb'),
190
+ or to stdout by means of: sys.stdout.buffer.write()
191
+
192
+ my_opus = [
193
+ 96,
194
+ [ # track 0:
195
+ ['patch_change', 0, 1, 8], # and these are the events...
196
+ ['note_on', 5, 1, 25, 96],
197
+ ['note_off', 96, 1, 25, 0],
198
+ ['note_on', 0, 1, 29, 96],
199
+ ['note_off', 96, 1, 29, 0],
200
+ ], # end of track 0
201
+ ]
202
+ my_midi = opus2midi(my_opus)
203
+ sys.stdout.buffer.write(my_midi)
204
+ '''
205
+ if len(opus) < 2:
206
+ opus=[1000, [],]
207
+ tracks = copy.deepcopy(opus)
208
+ ticks = int(tracks.pop(0))
209
+ ntracks = len(tracks)
210
+ if ntracks == 1:
211
+ format = 0
212
+ else:
213
+ format = 1
214
+
215
+ my_midi = b"MThd\x00\x00\x00\x06"+struct.pack('>HHH',format,ntracks,ticks)
216
+ for track in tracks:
217
+ events = _encode(track)
218
+ my_midi += b'MTrk' + struct.pack('>I',len(events)) + events
219
+ _clean_up_warnings()
220
+ return my_midi
221
+
222
+
223
+ def score2opus(score=None):
224
+ r'''
225
+ The argument is a list: the first item in the list is the "ticks"
226
+ parameter, the others are the tracks. Each track is a list
227
+ of score-events, and each event is itself a list. A score-event
228
+ is similar to an opus-event (see above), except that in a score:
229
+ 1) the times are expressed as an absolute number of ticks
230
+ from the track's start time
231
+ 2) the pairs of 'note_on' and 'note_off' events in an "opus"
232
+ are abstracted into a single 'note' event in a "score":
233
+ ['note', start_time, duration, channel, pitch, velocity]
234
+ score2opus() returns a list specifying the equivalent "opus".
235
+
236
+ my_score = [
237
+ 96,
238
+ [ # track 0:
239
+ ['patch_change', 0, 1, 8],
240
+ ['note', 5, 96, 1, 25, 96],
241
+ ['note', 101, 96, 1, 29, 96]
242
+ ], # end of track 0
243
+ ]
244
+ my_opus = score2opus(my_score)
245
+ '''
246
+ if len(score) < 2:
247
+ score=[1000, [],]
248
+ tracks = copy.deepcopy(score)
249
+ ticks = int(tracks.pop(0))
250
+ opus_tracks = []
251
+ for scoretrack in tracks:
252
+ time2events = dict([])
253
+ for scoreevent in scoretrack:
254
+ if scoreevent[0] == 'note':
255
+ note_on_event = ['note_on',scoreevent[1],
256
+ scoreevent[3],scoreevent[4],scoreevent[5]]
257
+ note_off_event = ['note_off',scoreevent[1]+scoreevent[2],
258
+ scoreevent[3],scoreevent[4],scoreevent[5]]
259
+ if time2events.get(note_on_event[1]):
260
+ time2events[note_on_event[1]].append(note_on_event)
261
+ else:
262
+ time2events[note_on_event[1]] = [note_on_event,]
263
+ if time2events.get(note_off_event[1]):
264
+ time2events[note_off_event[1]].append(note_off_event)
265
+ else:
266
+ time2events[note_off_event[1]] = [note_off_event,]
267
+ continue
268
+ if time2events.get(scoreevent[1]):
269
+ time2events[scoreevent[1]].append(scoreevent)
270
+ else:
271
+ time2events[scoreevent[1]] = [scoreevent,]
272
+
273
+ sorted_times = [] # list of keys
274
+ for k in time2events.keys():
275
+ sorted_times.append(k)
276
+ sorted_times.sort()
277
+
278
+ sorted_events = [] # once-flattened list of values sorted by key
279
+ for time in sorted_times:
280
+ sorted_events.extend(time2events[time])
281
+
282
+ abs_time = 0
283
+ for event in sorted_events: # convert abs times => delta times
284
+ delta_time = event[1] - abs_time
285
+ abs_time = event[1]
286
+ event[1] = delta_time
287
+ opus_tracks.append(sorted_events)
288
+ opus_tracks.insert(0,ticks)
289
+ _clean_up_warnings()
290
+ return opus_tracks
291
+
292
+ def score2midi(score=None):
293
+ r'''
294
+ Translates a "score" into MIDI, using score2opus() then opus2midi()
295
+ '''
296
+ return opus2midi(score2opus(score))
297
+
298
+ #--------------------------- Decoding stuff ------------------------
299
+
300
+ def midi2opus(midi=b''):
301
+ r'''Translates MIDI into a "opus". For a description of the
302
+ "opus" format, see opus2midi()
303
+ '''
304
+ my_midi=bytearray(midi)
305
+ if len(my_midi) < 4:
306
+ _clean_up_warnings()
307
+ return [1000,[],]
308
+ id = bytes(my_midi[0:4])
309
+ if id != b'MThd':
310
+ _warn("midi2opus: midi starts with "+str(id)+" instead of 'MThd'")
311
+ _clean_up_warnings()
312
+ return [1000,[],]
313
+ [length, format, tracks_expected, ticks] = struct.unpack(
314
+ '>IHHH', bytes(my_midi[4:14]))
315
+ if length != 6:
316
+ _warn("midi2opus: midi header length was "+str(length)+" instead of 6")
317
+ _clean_up_warnings()
318
+ return [1000,[],]
319
+ my_opus = [ticks,]
320
+ my_midi = my_midi[14:]
321
+ track_num = 1 # 5.1
322
+ while len(my_midi) >= 8:
323
+ track_type = bytes(my_midi[0:4])
324
+ if track_type != b'MTrk':
325
+ _warn('midi2opus: Warning: track #'+str(track_num)+' type is '+str(track_type)+" instead of b'MTrk'")
326
+ [track_length] = struct.unpack('>I', my_midi[4:8])
327
+ my_midi = my_midi[8:]
328
+ if track_length > len(my_midi):
329
+ _warn('midi2opus: track #'+str(track_num)+' length '+str(track_length)+' is too large')
330
+ _clean_up_warnings()
331
+ return my_opus # 5.0
332
+ my_midi_track = my_midi[0:track_length]
333
+ my_track = _decode(my_midi_track)
334
+ my_opus.append(my_track)
335
+ my_midi = my_midi[track_length:]
336
+ track_num += 1 # 5.1
337
+ _clean_up_warnings()
338
+ return my_opus
339
+
340
+ def opus2score(opus=[]):
341
+ r'''For a description of the "opus" and "score" formats,
342
+ see opus2midi() and score2opus().
343
+ '''
344
+ if len(opus) < 2:
345
+ _clean_up_warnings()
346
+ return [1000,[],]
347
+ tracks = copy.deepcopy(opus) # couple of slices probably quicker...
348
+ ticks = int(tracks.pop(0))
349
+ score = [ticks,]
350
+ for opus_track in tracks:
351
+ ticks_so_far = 0
352
+ score_track = []
353
+ chapitch2note_on_events = dict([]) # 4.0
354
+ for opus_event in opus_track:
355
+ ticks_so_far += opus_event[1]
356
+ if opus_event[0] == 'note_off' or (opus_event[0] == 'note_on' and opus_event[4] == 0): # 4.8
357
+ cha = opus_event[2]
358
+ pitch = opus_event[3]
359
+ key = cha*128 + pitch
360
+ if chapitch2note_on_events.get(key):
361
+ new_event = chapitch2note_on_events[key].pop(0)
362
+ new_event[2] = ticks_so_far - new_event[1]
363
+ score_track.append(new_event)
364
+ elif pitch > 127:
365
+ pass #_warn('opus2score: note_off with no note_on, bad pitch='+str(pitch))
366
+ else:
367
+ pass #_warn('opus2score: note_off with no note_on cha='+str(cha)+' pitch='+str(pitch))
368
+ elif opus_event[0] == 'note_on':
369
+ cha = opus_event[2]
370
+ pitch = opus_event[3]
371
+ key = cha*128 + pitch
372
+ new_event = ['note',ticks_so_far,0,cha,pitch, opus_event[4]]
373
+ if chapitch2note_on_events.get(key):
374
+ chapitch2note_on_events[key].append(new_event)
375
+ else:
376
+ chapitch2note_on_events[key] = [new_event,]
377
+ else:
378
+ opus_event[1] = ticks_so_far
379
+ score_track.append(opus_event)
380
+ # check for unterminated notes (Oisín) -- 5.2
381
+ for chapitch in chapitch2note_on_events:
382
+ note_on_events = chapitch2note_on_events[chapitch]
383
+ for new_e in note_on_events:
384
+ new_e[2] = ticks_so_far - new_e[1]
385
+ score_track.append(new_e)
386
+ pass #_warn("opus2score: note_on with no note_off cha="+str(new_e[3])+' pitch='+str(new_e[4])+'; adding note_off at end')
387
+ score.append(score_track)
388
+ _clean_up_warnings()
389
+ return score
390
+
391
+ def midi2score(midi=b''):
392
+ r'''
393
+ Translates MIDI into a "score", using midi2opus() then opus2score()
394
+ '''
395
+ return opus2score(midi2opus(midi))
396
+
397
+ def midi2ms_score(midi=b''):
398
+ r'''
399
+ Translates MIDI into a "score" with one beat per second and one
400
+ tick per millisecond, using midi2opus() then to_millisecs()
401
+ then opus2score()
402
+ '''
403
+ return opus2score(to_millisecs(midi2opus(midi)))
404
+
405
+ #------------------------ Other Transformations ---------------------
406
+
407
+ def to_millisecs(old_opus=None):
408
+ r'''Recallibrates all the times in an "opus" to use one beat
409
+ per second and one tick per millisecond. This makes it
410
+ hard to retrieve any information about beats or barlines,
411
+ but it does make it easy to mix different scores together.
412
+ '''
413
+ if old_opus == None:
414
+ return [1000,[],]
415
+ try:
416
+ old_tpq = int(old_opus[0])
417
+ except IndexError: # 5.0
418
+ _warn('to_millisecs: the opus '+str(type(old_opus))+' has no elements')
419
+ return [1000,[],]
420
+ new_opus = [1000,]
421
+ # 6.7 first go through building a table of set_tempos by absolute-tick
422
+ ticks2tempo = {}
423
+ itrack = 1
424
+ while itrack < len(old_opus):
425
+ ticks_so_far = 0
426
+ for old_event in old_opus[itrack]:
427
+ if old_event[0] == 'note':
428
+ raise TypeError('to_millisecs needs an opus, not a score')
429
+ ticks_so_far += old_event[1]
430
+ if old_event[0] == 'set_tempo':
431
+ ticks2tempo[ticks_so_far] = old_event[2]
432
+ itrack += 1
433
+ # then get the sorted-array of their keys
434
+ tempo_ticks = [] # list of keys
435
+ for k in ticks2tempo.keys():
436
+ tempo_ticks.append(k)
437
+ tempo_ticks.sort()
438
+ # then go through converting to millisec, testing if the next
439
+ # set_tempo lies before the next track-event, and using it if so.
440
+ itrack = 1
441
+ while itrack < len(old_opus):
442
+ ms_per_old_tick = 500.0 / old_tpq # float: will round later 6.3
443
+ i_tempo_ticks = 0
444
+ ticks_so_far = 0
445
+ ms_so_far = 0.0
446
+ previous_ms_so_far = 0.0
447
+ new_track = [['set_tempo',0,1000000],] # new "crochet" is 1 sec
448
+ for old_event in old_opus[itrack]:
449
+ # detect if ticks2tempo has something before this event
450
+ # 20160702 if ticks2tempo is at the same time, leave it
451
+ event_delta_ticks = old_event[1]
452
+ if (i_tempo_ticks < len(tempo_ticks) and
453
+ tempo_ticks[i_tempo_ticks] < (ticks_so_far + old_event[1])):
454
+ delta_ticks = tempo_ticks[i_tempo_ticks] - ticks_so_far
455
+ ms_so_far += (ms_per_old_tick * delta_ticks)
456
+ ticks_so_far = tempo_ticks[i_tempo_ticks]
457
+ ms_per_old_tick = ticks2tempo[ticks_so_far] / (1000.0*old_tpq)
458
+ i_tempo_ticks += 1
459
+ event_delta_ticks -= delta_ticks
460
+ new_event = copy.deepcopy(old_event) # now handle the new event
461
+ ms_so_far += (ms_per_old_tick * old_event[1])
462
+ new_event[1] = round(ms_so_far - previous_ms_so_far)
463
+ if old_event[0] != 'set_tempo':
464
+ previous_ms_so_far = ms_so_far
465
+ new_track.append(new_event)
466
+ ticks_so_far += event_delta_ticks
467
+ new_opus.append(new_track)
468
+ itrack += 1
469
+ _clean_up_warnings()
470
+ return new_opus
471
+
472
+ def event2alsaseq(event=None): # 5.5
473
+ r'''Converts an event into the format needed by the alsaseq module,
474
+ http://pp.com.mx/python/alsaseq
475
+ The type of track (opus or score) is autodetected.
476
+ '''
477
+ pass
478
+
479
+ def grep(score=None, channels=None):
480
+ r'''Returns a "score" containing only the channels specified
481
+ '''
482
+ if score == None:
483
+ return [1000,[],]
484
+ ticks = score[0]
485
+ new_score = [ticks,]
486
+ if channels == None:
487
+ return new_score
488
+ channels = set(channels)
489
+ global Event2channelindex
490
+ itrack = 1
491
+ while itrack < len(score):
492
+ new_score.append([])
493
+ for event in score[itrack]:
494
+ channel_index = Event2channelindex.get(event[0], False)
495
+ if channel_index:
496
+ if event[channel_index] in channels:
497
+ new_score[itrack].append(event)
498
+ else:
499
+ new_score[itrack].append(event)
500
+ itrack += 1
501
+ return new_score
502
+
503
+ def play_score(score=None):
504
+ r'''Converts the "score" to midi, and feeds it into 'aplaymidi -'
505
+ '''
506
+ if score == None:
507
+ return
508
+ import subprocess
509
+ pipe = subprocess.Popen(['aplaymidi','-'], stdin=subprocess.PIPE)
510
+ if score_type(score) == 'opus':
511
+ pipe.stdin.write(opus2midi(score))
512
+ else:
513
+ pipe.stdin.write(score2midi(score))
514
+ pipe.stdin.close()
515
+
516
+ def timeshift(score=None, shift=None, start_time=None, from_time=0, tracks={0,1,2,3,4,5,6,7,8,10,12,13,14,15}):
517
+ r'''Returns a "score" shifted in time by "shift" ticks, or shifted
518
+ so that the first event starts at "start_time" ticks.
519
+
520
+ If "from_time" is specified, only those events in the score
521
+ that begin after it are shifted. If "start_time" is less than
522
+ "from_time" (or "shift" is negative), then the intermediate
523
+ notes are deleted, though patch-change events are preserved.
524
+
525
+ If "tracks" are specified, then only those tracks get shifted.
526
+ "tracks" can be a list, tuple or set; it gets converted to set
527
+ internally.
528
+
529
+ It is deprecated to specify both "shift" and "start_time".
530
+ If this does happen, timeshift() will print a warning to
531
+ stderr and ignore the "shift" argument.
532
+
533
+ If "shift" is negative and sufficiently large that it would
534
+ leave some event with a negative tick-value, then the score
535
+ is shifted so that the first event occurs at time 0. This
536
+ also occurs if "start_time" is negative, and is also the
537
+ default if neither "shift" nor "start_time" are specified.
538
+ '''
539
+ #_warn('tracks='+str(tracks))
540
+ if score == None or len(score) < 2:
541
+ return [1000, [],]
542
+ new_score = [score[0],]
543
+ my_type = score_type(score)
544
+ if my_type == '':
545
+ return new_score
546
+ if my_type == 'opus':
547
+ _warn("timeshift: opus format is not supported\n")
548
+ # _clean_up_scores() 6.2; doesn't exist! what was it supposed to do?
549
+ return new_score
550
+ if not (shift == None) and not (start_time == None):
551
+ _warn("timeshift: shift and start_time specified: ignoring shift\n")
552
+ shift = None
553
+ if shift == None:
554
+ if (start_time == None) or (start_time < 0):
555
+ start_time = 0
556
+ # shift = start_time - from_time
557
+
558
+ i = 1 # ignore first element (ticks)
559
+ tracks = set(tracks) # defend against tuples and lists
560
+ earliest = 1000000000
561
+ if not (start_time == None) or shift < 0: # first find the earliest event
562
+ while i < len(score):
563
+ if len(tracks) and not ((i-1) in tracks):
564
+ i += 1
565
+ continue
566
+ for event in score[i]:
567
+ if event[1] < from_time:
568
+ continue # just inspect the to_be_shifted events
569
+ if event[1] < earliest:
570
+ earliest = event[1]
571
+ i += 1
572
+ if earliest > 999999999:
573
+ earliest = 0
574
+ if shift == None:
575
+ shift = start_time - earliest
576
+ elif (earliest + shift) < 0:
577
+ start_time = 0
578
+ shift = 0 - earliest
579
+
580
+ i = 1 # ignore first element (ticks)
581
+ while i < len(score):
582
+ if len(tracks) == 0 or not ((i-1) in tracks): # 3.8
583
+ new_score.append(score[i])
584
+ i += 1
585
+ continue
586
+ new_track = []
587
+ for event in score[i]:
588
+ new_event = list(event)
589
+ #if new_event[1] == 0 and shift > 0 and new_event[0] != 'note':
590
+ # pass
591
+ #elif new_event[1] >= from_time:
592
+ if new_event[1] >= from_time:
593
+ # 4.1 must not rightshift set_tempo
594
+ if new_event[0] != 'set_tempo' or shift<0:
595
+ new_event[1] += shift
596
+ elif (shift < 0) and (new_event[1] >= (from_time+shift)):
597
+ continue
598
+ new_track.append(new_event)
599
+ if len(new_track) > 0:
600
+ new_score.append(new_track)
601
+ i += 1
602
+ _clean_up_warnings()
603
+ return new_score
604
+
605
+ def segment(score=None, start_time=None, end_time=None, start=0, end=100000000,
606
+ tracks={0,1,2,3,4,5,6,7,8,10,11,12,13,14,15}):
607
+ r'''Returns a "score" which is a segment of the one supplied
608
+ as the argument, beginning at "start_time" ticks and ending
609
+ at "end_time" ticks (or at the end if "end_time" is not supplied).
610
+ If the set "tracks" is specified, only those tracks will
611
+ be returned.
612
+ '''
613
+ if score == None or len(score) < 2:
614
+ return [1000, [],]
615
+ if start_time == None: # as of 4.2 start_time is recommended
616
+ start_time = start # start is legacy usage
617
+ if end_time == None: # likewise
618
+ end_time = end
619
+ new_score = [score[0],]
620
+ my_type = score_type(score)
621
+ if my_type == '':
622
+ return new_score
623
+ if my_type == 'opus':
624
+ # more difficult (disconnecting note_on's from their note_off's)...
625
+ _warn("segment: opus format is not supported\n")
626
+ _clean_up_warnings()
627
+ return new_score
628
+ i = 1 # ignore first element (ticks); we count in ticks anyway
629
+ tracks = set(tracks) # defend against tuples and lists
630
+ while i < len(score):
631
+ if len(tracks) and not ((i-1) in tracks):
632
+ i += 1
633
+ continue
634
+ new_track = []
635
+ channel2cc_num = {} # most recent controller change before start
636
+ channel2cc_val = {}
637
+ channel2cc_time = {}
638
+ channel2patch_num = {} # keep most recent patch change before start
639
+ channel2patch_time = {}
640
+ set_tempo_num = 500000 # most recent tempo change before start 6.3
641
+ set_tempo_time = 0
642
+ earliest_note_time = end_time
643
+ for event in score[i]:
644
+ if event[0] == 'control_change': # 6.5
645
+ cc_time = channel2cc_time.get(event[2]) or 0
646
+ if (event[1] <= start_time) and (event[1] >= cc_time):
647
+ channel2cc_num[event[2]] = event[3]
648
+ channel2cc_val[event[2]] = event[4]
649
+ channel2cc_time[event[2]] = event[1]
650
+ elif event[0] == 'patch_change':
651
+ patch_time = channel2patch_time.get(event[2]) or 0
652
+ if (event[1]<=start_time) and (event[1] >= patch_time): # 2.0
653
+ channel2patch_num[event[2]] = event[3]
654
+ channel2patch_time[event[2]] = event[1]
655
+ elif event[0] == 'set_tempo':
656
+ if (event[1]<=start_time) and (event[1]>=set_tempo_time): #6.4
657
+ set_tempo_num = event[2]
658
+ set_tempo_time = event[1]
659
+ if (event[1] >= start_time) and (event[1] <= end_time):
660
+ new_track.append(event)
661
+ if (event[0] == 'note') and (event[1] < earliest_note_time):
662
+ earliest_note_time = event[1]
663
+ if len(new_track) > 0:
664
+ new_track.append(['set_tempo', start_time, set_tempo_num])
665
+ for c in channel2patch_num:
666
+ new_track.append(['patch_change',start_time,c,channel2patch_num[c]],)
667
+ for c in channel2cc_num: # 6.5
668
+ new_track.append(['control_change',start_time,c,channel2cc_num[c],channel2cc_val[c]])
669
+ new_score.append(new_track)
670
+ i += 1
671
+ _clean_up_warnings()
672
+ return new_score
673
+
674
+ def score_type(opus_or_score=None):
675
+ r'''Returns a string, either 'opus' or 'score' or ''
676
+ '''
677
+ if opus_or_score == None or str(type(opus_or_score)).find('list')<0 or len(opus_or_score) < 2:
678
+ return ''
679
+ i = 1 # ignore first element
680
+ while i < len(opus_or_score):
681
+ for event in opus_or_score[i]:
682
+ if event[0] == 'note':
683
+ return 'score'
684
+ elif event[0] == 'note_on':
685
+ return 'opus'
686
+ i += 1
687
+ return ''
688
+
689
+ def concatenate_scores(scores):
690
+ r'''Concatenates a list of scores into one score.
691
+ If the scores differ in their "ticks" parameter,
692
+ they will all get converted to millisecond-tick format.
693
+ '''
694
+ # the deepcopys are needed if the input_score's are refs to the same obj
695
+ # e.g. if invoked by midisox's repeat()
696
+ input_scores = _consistentise_ticks(scores) # 3.7
697
+ output_score = copy.deepcopy(input_scores[0])
698
+ for input_score in input_scores[1:]:
699
+ output_stats = score2stats(output_score)
700
+ delta_ticks = output_stats['nticks']
701
+ itrack = 1
702
+ while itrack < len(input_score):
703
+ if itrack >= len(output_score): # new output track if doesn't exist
704
+ output_score.append([])
705
+ for event in input_score[itrack]:
706
+ output_score[itrack].append(copy.deepcopy(event))
707
+ output_score[itrack][-1][1] += delta_ticks
708
+ itrack += 1
709
+ return output_score
710
+
711
+ def merge_scores(scores):
712
+ r'''Merges a list of scores into one score. A merged score comprises
713
+ all of the tracks from all of the input scores; un-merging is possible
714
+ by selecting just some of the tracks. If the scores differ in their
715
+ "ticks" parameter, they will all get converted to millisecond-tick
716
+ format. merge_scores attempts to resolve channel-conflicts,
717
+ but there are of course only 15 available channels...
718
+ '''
719
+ input_scores = _consistentise_ticks(scores) # 3.6
720
+ output_score = [1000]
721
+ channels_so_far = set()
722
+ all_channels = {0,1,2,3,4,5,6,7,8,10,11,12,13,14,15}
723
+ global Event2channelindex
724
+ for input_score in input_scores:
725
+ new_channels = set(score2stats(input_score).get('channels_total', []))
726
+ new_channels.discard(9) # 2.8 cha9 must remain cha9 (in GM)
727
+ for channel in channels_so_far & new_channels:
728
+ # consistently choose lowest avaiable, to ease testing
729
+ free_channels = list(all_channels - (channels_so_far|new_channels))
730
+ if len(free_channels) > 0:
731
+ free_channels.sort()
732
+ free_channel = free_channels[0]
733
+ else:
734
+ free_channel = None
735
+ break
736
+ itrack = 1
737
+ while itrack < len(input_score):
738
+ for input_event in input_score[itrack]:
739
+ channel_index=Event2channelindex.get(input_event[0],False)
740
+ if channel_index and input_event[channel_index]==channel:
741
+ input_event[channel_index] = free_channel
742
+ itrack += 1
743
+ channels_so_far.add(free_channel)
744
+
745
+ channels_so_far |= new_channels
746
+ output_score.extend(input_score[1:])
747
+ return output_score
748
+
749
+ def _ticks(event):
750
+ return event[1]
751
+ def mix_opus_tracks(input_tracks): # 5.5
752
+ r'''Mixes an array of tracks into one track. A mixed track
753
+ cannot be un-mixed. It is assumed that the tracks share the same
754
+ ticks parameter and the same tempo.
755
+ Mixing score-tracks is trivial (just insert all events into one array).
756
+ Mixing opus-tracks is only slightly harder, but it's common enough
757
+ that a dedicated function is useful.
758
+ '''
759
+ output_score = [1000, []]
760
+ for input_track in input_tracks: # 5.8
761
+ input_score = opus2score([1000, input_track])
762
+ for event in input_score[1]:
763
+ output_score[1].append(event)
764
+ output_score[1].sort(key=_ticks)
765
+ output_opus = score2opus(output_score)
766
+ return output_opus[1]
767
+
768
+ def mix_scores(scores):
769
+ r'''Mixes a list of scores into one one-track score.
770
+ A mixed score cannot be un-mixed. Hopefully the scores
771
+ have no undesirable channel-conflicts between them.
772
+ If the scores differ in their "ticks" parameter,
773
+ they will all get converted to millisecond-tick format.
774
+ '''
775
+ input_scores = _consistentise_ticks(scores) # 3.6
776
+ output_score = [1000, []]
777
+ for input_score in input_scores:
778
+ for input_track in input_score[1:]:
779
+ output_score[1].extend(input_track)
780
+ return output_score
781
+
782
+ def score2stats(opus_or_score=None):
783
+ r'''Returns a dict of some basic stats about the score, like
784
+ bank_select (list of tuples (msb,lsb)),
785
+ channels_by_track (list of lists), channels_total (set),
786
+ general_midi_mode (list),
787
+ ntracks, nticks, patch_changes_by_track (list of dicts),
788
+ num_notes_by_channel (list of numbers),
789
+ patch_changes_total (set),
790
+ percussion (dict histogram of channel 9 events),
791
+ pitches (dict histogram of pitches on channels other than 9),
792
+ pitch_range_by_track (list, by track, of two-member-tuples),
793
+ pitch_range_sum (sum over tracks of the pitch_ranges),
794
+ '''
795
+ bank_select_msb = -1
796
+ bank_select_lsb = -1
797
+ bank_select = []
798
+ channels_by_track = []
799
+ channels_total = set([])
800
+ general_midi_mode = []
801
+ num_notes_by_channel = dict([])
802
+ patches_used_by_track = []
803
+ patches_used_total = set([])
804
+ patch_changes_by_track = []
805
+ patch_changes_total = set([])
806
+ percussion = dict([]) # histogram of channel 9 "pitches"
807
+ pitches = dict([]) # histogram of pitch-occurrences channels 0-8,10-15
808
+ pitch_range_sum = 0 # u pitch-ranges of each track
809
+ pitch_range_by_track = []
810
+ is_a_score = True
811
+ if opus_or_score == None:
812
+ return {'bank_select':[], 'channels_by_track':[], 'channels_total':[],
813
+ 'general_midi_mode':[], 'ntracks':0, 'nticks':0,
814
+ 'num_notes_by_channel':dict([]),
815
+ 'patch_changes_by_track':[], 'patch_changes_total':[],
816
+ 'percussion':{}, 'pitches':{}, 'pitch_range_by_track':[],
817
+ 'ticks_per_quarter':0, 'pitch_range_sum':0}
818
+ ticks_per_quarter = opus_or_score[0]
819
+ i = 1 # ignore first element, which is ticks
820
+ nticks = 0
821
+ while i < len(opus_or_score):
822
+ highest_pitch = 0
823
+ lowest_pitch = 128
824
+ channels_this_track = set([])
825
+ patch_changes_this_track = dict({})
826
+ for event in opus_or_score[i]:
827
+ if event[0] == 'note':
828
+ num_notes_by_channel[event[3]] = num_notes_by_channel.get(event[3],0) + 1
829
+ if event[3] == 9:
830
+ percussion[event[4]] = percussion.get(event[4],0) + 1
831
+ else:
832
+ pitches[event[4]] = pitches.get(event[4],0) + 1
833
+ if event[4] > highest_pitch:
834
+ highest_pitch = event[4]
835
+ if event[4] < lowest_pitch:
836
+ lowest_pitch = event[4]
837
+ channels_this_track.add(event[3])
838
+ channels_total.add(event[3])
839
+ finish_time = event[1] + event[2]
840
+ if finish_time > nticks:
841
+ nticks = finish_time
842
+ elif event[0] == 'note_off' or (event[0] == 'note_on' and event[4] == 0): # 4.8
843
+ finish_time = event[1]
844
+ if finish_time > nticks:
845
+ nticks = finish_time
846
+ elif event[0] == 'note_on':
847
+ is_a_score = False
848
+ num_notes_by_channel[event[2]] = num_notes_by_channel.get(event[2],0) + 1
849
+ if event[2] == 9:
850
+ percussion[event[3]] = percussion.get(event[3],0) + 1
851
+ else:
852
+ pitches[event[3]] = pitches.get(event[3],0) + 1
853
+ if event[3] > highest_pitch:
854
+ highest_pitch = event[3]
855
+ if event[3] < lowest_pitch:
856
+ lowest_pitch = event[3]
857
+ channels_this_track.add(event[2])
858
+ channels_total.add(event[2])
859
+ elif event[0] == 'patch_change':
860
+ patch_changes_this_track[event[2]] = event[3]
861
+ patch_changes_total.add(event[3])
862
+ elif event[0] == 'control_change':
863
+ if event[3] == 0: # bank select MSB
864
+ bank_select_msb = event[4]
865
+ elif event[3] == 32: # bank select LSB
866
+ bank_select_lsb = event[4]
867
+ if bank_select_msb >= 0 and bank_select_lsb >= 0:
868
+ bank_select.append((bank_select_msb,bank_select_lsb))
869
+ bank_select_msb = -1
870
+ bank_select_lsb = -1
871
+ elif event[0] == 'sysex_f0':
872
+ if _sysex2midimode.get(event[2], -1) >= 0:
873
+ general_midi_mode.append(_sysex2midimode.get(event[2]))
874
+ if is_a_score:
875
+ if event[1] > nticks:
876
+ nticks = event[1]
877
+ else:
878
+ nticks += event[1]
879
+ if lowest_pitch == 128:
880
+ lowest_pitch = 0
881
+ channels_by_track.append(channels_this_track)
882
+ patch_changes_by_track.append(patch_changes_this_track)
883
+ pitch_range_by_track.append((lowest_pitch,highest_pitch))
884
+ pitch_range_sum += (highest_pitch-lowest_pitch)
885
+ i += 1
886
+
887
+ return {'bank_select':bank_select,
888
+ 'channels_by_track':channels_by_track,
889
+ 'channels_total':channels_total,
890
+ 'general_midi_mode':general_midi_mode,
891
+ 'ntracks':len(opus_or_score)-1,
892
+ 'nticks':nticks,
893
+ 'num_notes_by_channel':num_notes_by_channel,
894
+ 'patch_changes_by_track':patch_changes_by_track,
895
+ 'patch_changes_total':patch_changes_total,
896
+ 'percussion':percussion,
897
+ 'pitches':pitches,
898
+ 'pitch_range_by_track':pitch_range_by_track,
899
+ 'pitch_range_sum':pitch_range_sum,
900
+ 'ticks_per_quarter':ticks_per_quarter}
901
+
902
+ #----------------------------- Event stuff --------------------------
903
+
904
+ _sysex2midimode = {
905
+ "\x7E\x7F\x09\x01\xF7": 1,
906
+ "\x7E\x7F\x09\x02\xF7": 0,
907
+ "\x7E\x7F\x09\x03\xF7": 2,
908
+ }
909
+
910
+ # Some public-access tuples:
911
+ MIDI_events = tuple('''note_off note_on key_after_touch
912
+ control_change patch_change channel_after_touch
913
+ pitch_wheel_change'''.split())
914
+
915
+ Text_events = tuple('''text_event copyright_text_event
916
+ track_name instrument_name lyric marker cue_point text_event_08
917
+ text_event_09 text_event_0a text_event_0b text_event_0c
918
+ text_event_0d text_event_0e text_event_0f'''.split())
919
+
920
+ Nontext_meta_events = tuple('''end_track set_tempo
921
+ smpte_offset time_signature key_signature sequencer_specific
922
+ raw_meta_event sysex_f0 sysex_f7 song_position song_select
923
+ tune_request'''.split())
924
+ # unsupported: raw_data
925
+
926
+ # Actually, 'tune_request' is is F-series event, not strictly a meta-event...
927
+ Meta_events = Text_events + Nontext_meta_events
928
+ All_events = MIDI_events + Meta_events
929
+
930
+ # And three dictionaries:
931
+ Number2patch = { # General MIDI patch numbers:
932
+ 0:'Acoustic Grand',
933
+ 1:'Bright Acoustic',
934
+ 2:'Electric Grand',
935
+ 3:'Honky-Tonk',
936
+ 4:'Electric Piano 1',
937
+ 5:'Electric Piano 2',
938
+ 6:'Harpsichord',
939
+ 7:'Clav',
940
+ 8:'Celesta',
941
+ 9:'Glockenspiel',
942
+ 10:'Music Box',
943
+ 11:'Vibraphone',
944
+ 12:'Marimba',
945
+ 13:'Xylophone',
946
+ 14:'Tubular Bells',
947
+ 15:'Dulcimer',
948
+ 16:'Drawbar Organ',
949
+ 17:'Percussive Organ',
950
+ 18:'Rock Organ',
951
+ 19:'Church Organ',
952
+ 20:'Reed Organ',
953
+ 21:'Accordion',
954
+ 22:'Harmonica',
955
+ 23:'Tango Accordion',
956
+ 24:'Acoustic Guitar(nylon)',
957
+ 25:'Acoustic Guitar(steel)',
958
+ 26:'Electric Guitar(jazz)',
959
+ 27:'Electric Guitar(clean)',
960
+ 28:'Electric Guitar(muted)',
961
+ 29:'Overdriven Guitar',
962
+ 30:'Distortion Guitar',
963
+ 31:'Guitar Harmonics',
964
+ 32:'Acoustic Bass',
965
+ 33:'Electric Bass(finger)',
966
+ 34:'Electric Bass(pick)',
967
+ 35:'Fretless Bass',
968
+ 36:'Slap Bass 1',
969
+ 37:'Slap Bass 2',
970
+ 38:'Synth Bass 1',
971
+ 39:'Synth Bass 2',
972
+ 40:'Violin',
973
+ 41:'Viola',
974
+ 42:'Cello',
975
+ 43:'Contrabass',
976
+ 44:'Tremolo Strings',
977
+ 45:'Pizzicato Strings',
978
+ 46:'Orchestral Harp',
979
+ 47:'Timpani',
980
+ 48:'String Ensemble 1',
981
+ 49:'String Ensemble 2',
982
+ 50:'SynthStrings 1',
983
+ 51:'SynthStrings 2',
984
+ 52:'Choir Aahs',
985
+ 53:'Voice Oohs',
986
+ 54:'Synth Voice',
987
+ 55:'Orchestra Hit',
988
+ 56:'Trumpet',
989
+ 57:'Trombone',
990
+ 58:'Tuba',
991
+ 59:'Muted Trumpet',
992
+ 60:'French Horn',
993
+ 61:'Brass Section',
994
+ 62:'SynthBrass 1',
995
+ 63:'SynthBrass 2',
996
+ 64:'Soprano Sax',
997
+ 65:'Alto Sax',
998
+ 66:'Tenor Sax',
999
+ 67:'Baritone Sax',
1000
+ 68:'Oboe',
1001
+ 69:'English Horn',
1002
+ 70:'Bassoon',
1003
+ 71:'Clarinet',
1004
+ 72:'Piccolo',
1005
+ 73:'Flute',
1006
+ 74:'Recorder',
1007
+ 75:'Pan Flute',
1008
+ 76:'Blown Bottle',
1009
+ 77:'Skakuhachi',
1010
+ 78:'Whistle',
1011
+ 79:'Ocarina',
1012
+ 80:'Lead 1 (square)',
1013
+ 81:'Lead 2 (sawtooth)',
1014
+ 82:'Lead 3 (calliope)',
1015
+ 83:'Lead 4 (chiff)',
1016
+ 84:'Lead 5 (charang)',
1017
+ 85:'Lead 6 (voice)',
1018
+ 86:'Lead 7 (fifths)',
1019
+ 87:'Lead 8 (bass+lead)',
1020
+ 88:'Pad 1 (new age)',
1021
+ 89:'Pad 2 (warm)',
1022
+ 90:'Pad 3 (polysynth)',
1023
+ 91:'Pad 4 (choir)',
1024
+ 92:'Pad 5 (bowed)',
1025
+ 93:'Pad 6 (metallic)',
1026
+ 94:'Pad 7 (halo)',
1027
+ 95:'Pad 8 (sweep)',
1028
+ 96:'FX 1 (rain)',
1029
+ 97:'FX 2 (soundtrack)',
1030
+ 98:'FX 3 (crystal)',
1031
+ 99:'FX 4 (atmosphere)',
1032
+ 100:'FX 5 (brightness)',
1033
+ 101:'FX 6 (goblins)',
1034
+ 102:'FX 7 (echoes)',
1035
+ 103:'FX 8 (sci-fi)',
1036
+ 104:'Sitar',
1037
+ 105:'Banjo',
1038
+ 106:'Shamisen',
1039
+ 107:'Koto',
1040
+ 108:'Kalimba',
1041
+ 109:'Bagpipe',
1042
+ 110:'Fiddle',
1043
+ 111:'Shanai',
1044
+ 112:'Tinkle Bell',
1045
+ 113:'Agogo',
1046
+ 114:'Steel Drums',
1047
+ 115:'Woodblock',
1048
+ 116:'Taiko Drum',
1049
+ 117:'Melodic Tom',
1050
+ 118:'Synth Drum',
1051
+ 119:'Reverse Cymbal',
1052
+ 120:'Guitar Fret Noise',
1053
+ 121:'Breath Noise',
1054
+ 122:'Seashore',
1055
+ 123:'Bird Tweet',
1056
+ 124:'Telephone Ring',
1057
+ 125:'Helicopter',
1058
+ 126:'Applause',
1059
+ 127:'Gunshot',
1060
+ }
1061
+ Notenum2percussion = { # General MIDI Percussion (on Channel 9):
1062
+ 35:'Acoustic Bass Drum',
1063
+ 36:'Bass Drum 1',
1064
+ 37:'Side Stick',
1065
+ 38:'Acoustic Snare',
1066
+ 39:'Hand Clap',
1067
+ 40:'Electric Snare',
1068
+ 41:'Low Floor Tom',
1069
+ 42:'Closed Hi-Hat',
1070
+ 43:'High Floor Tom',
1071
+ 44:'Pedal Hi-Hat',
1072
+ 45:'Low Tom',
1073
+ 46:'Open Hi-Hat',
1074
+ 47:'Low-Mid Tom',
1075
+ 48:'Hi-Mid Tom',
1076
+ 49:'Crash Cymbal 1',
1077
+ 50:'High Tom',
1078
+ 51:'Ride Cymbal 1',
1079
+ 52:'Chinese Cymbal',
1080
+ 53:'Ride Bell',
1081
+ 54:'Tambourine',
1082
+ 55:'Splash Cymbal',
1083
+ 56:'Cowbell',
1084
+ 57:'Crash Cymbal 2',
1085
+ 58:'Vibraslap',
1086
+ 59:'Ride Cymbal 2',
1087
+ 60:'Hi Bongo',
1088
+ 61:'Low Bongo',
1089
+ 62:'Mute Hi Conga',
1090
+ 63:'Open Hi Conga',
1091
+ 64:'Low Conga',
1092
+ 65:'High Timbale',
1093
+ 66:'Low Timbale',
1094
+ 67:'High Agogo',
1095
+ 68:'Low Agogo',
1096
+ 69:'Cabasa',
1097
+ 70:'Maracas',
1098
+ 71:'Short Whistle',
1099
+ 72:'Long Whistle',
1100
+ 73:'Short Guiro',
1101
+ 74:'Long Guiro',
1102
+ 75:'Claves',
1103
+ 76:'Hi Wood Block',
1104
+ 77:'Low Wood Block',
1105
+ 78:'Mute Cuica',
1106
+ 79:'Open Cuica',
1107
+ 80:'Mute Triangle',
1108
+ 81:'Open Triangle',
1109
+ }
1110
+
1111
+ Event2channelindex = { 'note':3, 'note_off':2, 'note_on':2,
1112
+ 'key_after_touch':2, 'control_change':2, 'patch_change':2,
1113
+ 'channel_after_touch':2, 'pitch_wheel_change':2
1114
+ }
1115
+
1116
+ ################################################################
1117
+ # The code below this line is full of frightening things, all to
1118
+ # do with the actual encoding and decoding of binary MIDI data.
1119
+
1120
+ def _twobytes2int(byte_a):
1121
+ r'''decode a 16 bit quantity from two bytes,'''
1122
+ return (byte_a[1] | (byte_a[0] << 8))
1123
+
1124
+ def _int2twobytes(int_16bit):
1125
+ r'''encode a 16 bit quantity into two bytes,'''
1126
+ return bytes([(int_16bit>>8) & 0xFF, int_16bit & 0xFF])
1127
+
1128
+ def _read_14_bit(byte_a):
1129
+ r'''decode a 14 bit quantity from two bytes,'''
1130
+ return (byte_a[0] | (byte_a[1] << 7))
1131
+
1132
+ def _write_14_bit(int_14bit):
1133
+ r'''encode a 14 bit quantity into two bytes,'''
1134
+ return bytes([int_14bit & 0x7F, (int_14bit>>7) & 0x7F])
1135
+
1136
+ def _ber_compressed_int(integer):
1137
+ r'''BER compressed integer (not an ASN.1 BER, see perlpacktut for
1138
+ details). Its bytes represent an unsigned integer in base 128,
1139
+ most significant digit first, with as few digits as possible.
1140
+ Bit eight (the high bit) is set on each byte except the last.
1141
+ '''
1142
+ ber = bytearray(b'')
1143
+ seven_bits = 0x7F & integer
1144
+ ber.insert(0, seven_bits) # XXX surely should convert to a char ?
1145
+ integer >>= 7
1146
+ while integer > 0:
1147
+ seven_bits = 0x7F & integer
1148
+ ber.insert(0, 0x80|seven_bits) # XXX surely should convert to a char ?
1149
+ integer >>= 7
1150
+ return ber
1151
+
1152
+ def _unshift_ber_int(ba):
1153
+ r'''Given a bytearray, returns a tuple of (the ber-integer at the
1154
+ start, and the remainder of the bytearray).
1155
+ '''
1156
+ if not len(ba): # 6.7
1157
+ _warn('_unshift_ber_int: no integer found')
1158
+ return ((0, b""))
1159
+ byte = ba.pop(0)
1160
+ integer = 0
1161
+ while True:
1162
+ integer += (byte & 0x7F)
1163
+ if not (byte & 0x80):
1164
+ return ((integer, ba))
1165
+ if not len(ba):
1166
+ _warn('_unshift_ber_int: no end-of-integer found')
1167
+ return ((0, ba))
1168
+ byte = ba.pop(0)
1169
+ integer <<= 7
1170
+
1171
+ def _clean_up_warnings(): # 5.4
1172
+ # Call this before returning from any publicly callable function
1173
+ # whenever there's a possibility that a warning might have been printed
1174
+ # by the function, or by any private functions it might have called.
1175
+ global _previous_times
1176
+ global _previous_warning
1177
+ if _previous_times > 1:
1178
+ # E:1176, 0: invalid syntax (<string>, line 1176) (syntax-error) ???
1179
+ # print(' previous message repeated '+str(_previous_times)+' times', file=sys.stderr)
1180
+ # 6.7
1181
+ sys.stderr.write(' previous message repeated {0} times\n'.format(_previous_times))
1182
+ elif _previous_times > 0:
1183
+ sys.stderr.write(' previous message repeated\n')
1184
+ _previous_times = 0
1185
+ _previous_warning = ''
1186
+
1187
+ def _warn(s=''):
1188
+ global _previous_times
1189
+ global _previous_warning
1190
+ if s == _previous_warning: # 5.4
1191
+ _previous_times = _previous_times + 1
1192
+ else:
1193
+ _clean_up_warnings()
1194
+ sys.stderr.write(str(s)+"\n")
1195
+ _previous_warning = s
1196
+
1197
+ def _some_text_event(which_kind=0x01, text=b'some_text'):
1198
+ if str(type(text)).find("'str'") >= 0: # 6.4 test for back-compatibility
1199
+ data = bytes(text, encoding='ISO-8859-1')
1200
+ else:
1201
+ data = bytes(text)
1202
+ return b'\xFF'+bytes((which_kind,))+_ber_compressed_int(len(data))+data
1203
+
1204
+ def _consistentise_ticks(scores): # 3.6
1205
+ # used by mix_scores, merge_scores, concatenate_scores
1206
+ if len(scores) == 1:
1207
+ return copy.deepcopy(scores)
1208
+ are_consistent = True
1209
+ ticks = scores[0][0]
1210
+ iscore = 1
1211
+ while iscore < len(scores):
1212
+ if scores[iscore][0] != ticks:
1213
+ are_consistent = False
1214
+ break
1215
+ iscore += 1
1216
+ if are_consistent:
1217
+ return copy.deepcopy(scores)
1218
+ new_scores = []
1219
+ iscore = 0
1220
+ while iscore < len(scores):
1221
+ score = scores[iscore]
1222
+ new_scores.append(opus2score(to_millisecs(score2opus(score))))
1223
+ iscore += 1
1224
+ return new_scores
1225
+
1226
+
1227
+ ###########################################################################
1228
+
1229
+ def _decode(trackdata=b'', exclude=None, include=None,
1230
+ event_callback=None, exclusive_event_callback=None, no_eot_magic=False):
1231
+ r'''Decodes MIDI track data into an opus-style list of events.
1232
+ The options:
1233
+ 'exclude' is a list of event types which will be ignored SHOULD BE A SET
1234
+ 'include' (and no exclude), makes exclude a list
1235
+ of all possible events, /minus/ what include specifies
1236
+ 'event_callback' is a coderef
1237
+ 'exclusive_event_callback' is a coderef
1238
+ '''
1239
+ trackdata = bytearray(trackdata)
1240
+ if exclude == None:
1241
+ exclude = []
1242
+ if include == None:
1243
+ include = []
1244
+ if include and not exclude:
1245
+ exclude = All_events
1246
+ include = set(include)
1247
+ exclude = set(exclude)
1248
+
1249
+ # Pointer = 0; not used here; we eat through the bytearray instead.
1250
+ event_code = -1; # used for running status
1251
+ event_count = 0;
1252
+ events = []
1253
+
1254
+ while(len(trackdata)):
1255
+ # loop while there's anything to analyze ...
1256
+ eot = False # When True, the event registrar aborts this loop
1257
+ event_count += 1
1258
+
1259
+ E = []
1260
+ # E for events - we'll feed it to the event registrar at the end.
1261
+
1262
+ # Slice off the delta time code, and analyze it
1263
+ [time, remainder] = _unshift_ber_int(trackdata)
1264
+
1265
+ # Now let's see what we can make of the command
1266
+ first_byte = trackdata.pop(0) & 0xFF
1267
+
1268
+ if (first_byte < 0xF0): # It's a MIDI event
1269
+ if (first_byte & 0x80):
1270
+ event_code = first_byte
1271
+ else:
1272
+ # It wants running status; use last event_code value
1273
+ trackdata.insert(0, first_byte)
1274
+ if (event_code == -1):
1275
+ _warn("Running status not set; Aborting track.")
1276
+ return []
1277
+
1278
+ command = event_code & 0xF0
1279
+ channel = event_code & 0x0F
1280
+
1281
+ if (command == 0xF6): # 0-byte argument
1282
+ pass
1283
+ elif (command == 0xC0 or command == 0xD0): # 1-byte argument
1284
+ parameter = trackdata.pop(0) # could be B
1285
+ else: # 2-byte argument could be BB or 14-bit
1286
+ parameter = (trackdata.pop(0), trackdata.pop(0))
1287
+
1288
+ #################################################################
1289
+ # MIDI events
1290
+
1291
+ if (command == 0x80):
1292
+ if 'note_off' in exclude:
1293
+ continue
1294
+ E = ['note_off', time, channel, parameter[0], parameter[1]]
1295
+ elif (command == 0x90):
1296
+ if 'note_on' in exclude:
1297
+ continue
1298
+ E = ['note_on', time, channel, parameter[0], parameter[1]]
1299
+ elif (command == 0xA0):
1300
+ if 'key_after_touch' in exclude:
1301
+ continue
1302
+ E = ['key_after_touch',time,channel,parameter[0],parameter[1]]
1303
+ elif (command == 0xB0):
1304
+ if 'control_change' in exclude:
1305
+ continue
1306
+ E = ['control_change',time,channel,parameter[0],parameter[1]]
1307
+ elif (command == 0xC0):
1308
+ if 'patch_change' in exclude:
1309
+ continue
1310
+ E = ['patch_change', time, channel, parameter]
1311
+ elif (command == 0xD0):
1312
+ if 'channel_after_touch' in exclude:
1313
+ continue
1314
+ E = ['channel_after_touch', time, channel, parameter]
1315
+ elif (command == 0xE0):
1316
+ if 'pitch_wheel_change' in exclude:
1317
+ continue
1318
+ E = ['pitch_wheel_change', time, channel,
1319
+ _read_14_bit(parameter)-0x2000]
1320
+ else:
1321
+ _warn("Shouldn't get here; command="+hex(command))
1322
+
1323
+ elif (first_byte == 0xFF): # It's a Meta-Event! ##################
1324
+ #[command, length, remainder] =
1325
+ # unpack("xCwa*", substr(trackdata, $Pointer, 6));
1326
+ #Pointer += 6 - len(remainder);
1327
+ # # Move past JUST the length-encoded.
1328
+ command = trackdata.pop(0) & 0xFF
1329
+ [length, trackdata] = _unshift_ber_int(trackdata)
1330
+ if (command == 0x00):
1331
+ if (length == 2):
1332
+ E = ['set_sequence_number',time,_twobytes2int(trackdata)]
1333
+ else:
1334
+ _warn('set_sequence_number: length must be 2, not '+str(length))
1335
+ E = ['set_sequence_number', time, 0]
1336
+
1337
+ elif command >= 0x01 and command <= 0x0f: # Text events
1338
+ # 6.2 take it in bytes; let the user get the right encoding.
1339
+ # text_str = trackdata[0:length].decode('ascii','ignore')
1340
+ # text_str = trackdata[0:length].decode('ISO-8859-1')
1341
+ # 6.4 take it in bytes; let the user get the right encoding.
1342
+ text_data = bytes(trackdata[0:length]) # 6.4
1343
+ # Defined text events
1344
+ if (command == 0x01):
1345
+ E = ['text_event', time, text_data]
1346
+ elif (command == 0x02):
1347
+ E = ['copyright_text_event', time, text_data]
1348
+ elif (command == 0x03):
1349
+ E = ['track_name', time, text_data]
1350
+ elif (command == 0x04):
1351
+ E = ['instrument_name', time, text_data]
1352
+ elif (command == 0x05):
1353
+ E = ['lyric', time, text_data]
1354
+ elif (command == 0x06):
1355
+ E = ['marker', time, text_data]
1356
+ elif (command == 0x07):
1357
+ E = ['cue_point', time, text_data]
1358
+ # Reserved but apparently unassigned text events
1359
+ elif (command == 0x08):
1360
+ E = ['text_event_08', time, text_data]
1361
+ elif (command == 0x09):
1362
+ E = ['text_event_09', time, text_data]
1363
+ elif (command == 0x0a):
1364
+ E = ['text_event_0a', time, text_data]
1365
+ elif (command == 0x0b):
1366
+ E = ['text_event_0b', time, text_data]
1367
+ elif (command == 0x0c):
1368
+ E = ['text_event_0c', time, text_data]
1369
+ elif (command == 0x0d):
1370
+ E = ['text_event_0d', time, text_data]
1371
+ elif (command == 0x0e):
1372
+ E = ['text_event_0e', time, text_data]
1373
+ elif (command == 0x0f):
1374
+ E = ['text_event_0f', time, text_data]
1375
+
1376
+ # Now the sticky events -------------------------------------
1377
+ elif (command == 0x2F):
1378
+ E = ['end_track', time]
1379
+ # The code for handling this, oddly, comes LATER,
1380
+ # in the event registrar.
1381
+ elif (command == 0x51): # DTime, Microseconds/Crochet
1382
+ if length != 3:
1383
+ _warn('set_tempo event, but length='+str(length))
1384
+ E = ['set_tempo', time,
1385
+ struct.unpack(">I", b'\x00'+trackdata[0:3])[0]]
1386
+ elif (command == 0x54):
1387
+ if length != 5: # DTime, HR, MN, SE, FR, FF
1388
+ _warn('smpte_offset event, but length='+str(length))
1389
+ E = ['smpte_offset',time] + list(struct.unpack(">BBBBB",trackdata[0:5]))
1390
+ elif (command == 0x58):
1391
+ if length != 4: # DTime, NN, DD, CC, BB
1392
+ _warn('time_signature event, but length='+str(length))
1393
+ E = ['time_signature', time]+list(trackdata[0:4])
1394
+ elif (command == 0x59):
1395
+ if length != 2: # DTime, SF(signed), MI
1396
+ _warn('key_signature event, but length='+str(length))
1397
+ E = ['key_signature',time] + list(struct.unpack(">bB",trackdata[0:2]))
1398
+ elif (command == 0x7F): # 6.4
1399
+ E = ['sequencer_specific',time, bytes(trackdata[0:length])]
1400
+ else:
1401
+ E = ['raw_meta_event', time, command,
1402
+ bytes(trackdata[0:length])] # 6.0
1403
+ #"[uninterpretable meta-event command of length length]"
1404
+ # DTime, Command, Binary Data
1405
+ # It's uninterpretable; record it as raw_data.
1406
+
1407
+ # Pointer += length; # Now move Pointer
1408
+ trackdata = trackdata[length:]
1409
+
1410
+ ######################################################################
1411
+ elif (first_byte == 0xF0 or first_byte == 0xF7):
1412
+ # Note that sysexes in MIDI /files/ are different than sysexes
1413
+ # in MIDI transmissions!! The vast majority of system exclusive
1414
+ # messages will just use the F0 format. For instance, the
1415
+ # transmitted message F0 43 12 00 07 F7 would be stored in a
1416
+ # MIDI file as F0 05 43 12 00 07 F7. As mentioned above, it is
1417
+ # required to include the F7 at the end so that the reader of the
1418
+ # MIDI file knows that it has read the entire message. (But the F7
1419
+ # is omitted if this is a non-final block in a multiblock sysex;
1420
+ # but the F7 (if there) is counted in the message's declared
1421
+ # length, so we don't have to think about it anyway.)
1422
+ #command = trackdata.pop(0)
1423
+ [length, trackdata] = _unshift_ber_int(trackdata)
1424
+ if first_byte == 0xF0:
1425
+ # 20091008 added ISO-8859-1 to get an 8-bit str
1426
+ # 6.4 return bytes instead
1427
+ E = ['sysex_f0', time, bytes(trackdata[0:length])]
1428
+ else:
1429
+ E = ['sysex_f7', time, bytes(trackdata[0:length])]
1430
+ trackdata = trackdata[length:]
1431
+
1432
+ ######################################################################
1433
+ # Now, the MIDI file spec says:
1434
+ # <track data> = <MTrk event>+
1435
+ # <MTrk event> = <delta-time> <event>
1436
+ # <event> = <MIDI event> | <sysex event> | <meta-event>
1437
+ # I know that, on the wire, <MIDI event> can include note_on,
1438
+ # note_off, and all the other 8x to Ex events, AND Fx events
1439
+ # other than F0, F7, and FF -- namely, <song position msg>,
1440
+ # <song select msg>, and <tune request>.
1441
+ #
1442
+ # Whether these can occur in MIDI files is not clear specified
1443
+ # from the MIDI file spec. So, I'm going to assume that
1444
+ # they CAN, in practice, occur. I don't know whether it's
1445
+ # proper for you to actually emit these into a MIDI file.
1446
+
1447
+ elif (first_byte == 0xF2): # DTime, Beats
1448
+ # <song position msg> ::= F2 <data pair>
1449
+ E = ['song_position', time, _read_14_bit(trackdata[:2])]
1450
+ trackdata = trackdata[2:]
1451
+
1452
+ elif (first_byte == 0xF3): # <song select msg> ::= F3 <data singlet>
1453
+ # E = ['song_select', time, struct.unpack('>B',trackdata.pop(0))[0]]
1454
+ E = ['song_select', time, trackdata[0]]
1455
+ trackdata = trackdata[1:]
1456
+ # DTime, Thing (what?! song number? whatever ...)
1457
+
1458
+ elif (first_byte == 0xF6): # DTime
1459
+ E = ['tune_request', time]
1460
+ # What would a tune request be doing in a MIDI /file/?
1461
+
1462
+ #########################################################
1463
+ # ADD MORE META-EVENTS HERE. TODO:
1464
+ # f1 -- MTC Quarter Frame Message. One data byte follows
1465
+ # the Status; it's the time code value, from 0 to 127.
1466
+ # f8 -- MIDI clock. no data.
1467
+ # fa -- MIDI start. no data.
1468
+ # fb -- MIDI continue. no data.
1469
+ # fc -- MIDI stop. no data.
1470
+ # fe -- Active sense. no data.
1471
+ # f4 f5 f9 fd -- unallocated
1472
+
1473
+ r'''
1474
+ elif (first_byte > 0xF0) { # Some unknown kinda F-series event ####
1475
+ # Here we only produce a one-byte piece of raw data.
1476
+ # But the encoder for 'raw_data' accepts any length of it.
1477
+ E = [ 'raw_data',
1478
+ time, substr(trackdata,Pointer,1) ]
1479
+ # DTime and the Data (in this case, the one Event-byte)
1480
+ ++Pointer; # itself
1481
+
1482
+ '''
1483
+ elif first_byte > 0xF0: # Some unknown F-series event
1484
+ # Here we only produce a one-byte piece of raw data.
1485
+ # E = ['raw_data', time, bytest(trackdata[0])] # 6.4
1486
+ E = ['raw_data', time, trackdata[0]] # 6.4 6.7
1487
+ trackdata = trackdata[1:]
1488
+ else: # Fallthru.
1489
+ _warn("Aborting track. Command-byte first_byte="+hex(first_byte))
1490
+ break
1491
+ # End of the big if-group
1492
+
1493
+
1494
+ ######################################################################
1495
+ # THE EVENT REGISTRAR...
1496
+ if E and (E[0] == 'end_track'):
1497
+ # This is the code for exceptional handling of the EOT event.
1498
+ eot = True
1499
+ if not no_eot_magic:
1500
+ if E[1] > 0: # a null text-event to carry the delta-time
1501
+ E = ['text_event', E[1], '']
1502
+ else:
1503
+ E = [] # EOT with a delta-time of 0; ignore it.
1504
+
1505
+ if E and not (E[0] in exclude):
1506
+ #if ( $exclusive_event_callback ):
1507
+ # &{ $exclusive_event_callback }( @E );
1508
+ #else:
1509
+ # &{ $event_callback }( @E ) if $event_callback;
1510
+ events.append(E)
1511
+ if eot:
1512
+ break
1513
+
1514
+ # End of the big "Event" while-block
1515
+
1516
+ return events
1517
+
1518
+
1519
+ ###########################################################################
1520
+ def _encode(events_lol, unknown_callback=None, never_add_eot=False,
1521
+ no_eot_magic=False, no_running_status=False):
1522
+ # encode an event structure, presumably for writing to a file
1523
+ # Calling format:
1524
+ # $data_r = MIDI::Event::encode( \@event_lol, { options } );
1525
+ # Takes a REFERENCE to an event structure (a LoL)
1526
+ # Returns an (unblessed) REFERENCE to track data.
1527
+
1528
+ # If you want to use this to encode a /single/ event,
1529
+ # you still have to do it as a reference to an event structure (a LoL)
1530
+ # that just happens to have just one event. I.e.,
1531
+ # encode( [ $event ] ) or encode( [ [ 'note_on', 100, 5, 42, 64] ] )
1532
+ # If you're doing this, consider the never_add_eot track option, as in
1533
+ # print MIDI ${ encode( [ $event], { 'never_add_eot' => 1} ) };
1534
+
1535
+ data = [] # what I'll store the chunks of byte-data in
1536
+
1537
+ # This is so my end_track magic won't corrupt the original
1538
+ events = copy.deepcopy(events_lol)
1539
+
1540
+ if not never_add_eot:
1541
+ # One way or another, tack on an 'end_track'
1542
+ if events:
1543
+ last = events[-1]
1544
+ if not (last[0] == 'end_track'): # no end_track already
1545
+ if (last[0] == 'text_event' and len(last[2]) == 0):
1546
+ # 0-length text event at track-end.
1547
+ if no_eot_magic:
1548
+ # Exceptional case: don't mess with track-final
1549
+ # 0-length text_events; just peg on an end_track
1550
+ events.append(['end_track', 0])
1551
+ else:
1552
+ # NORMAL CASE: replace with an end_track, leaving DTime
1553
+ last[0] = 'end_track'
1554
+ else:
1555
+ # last event was neither 0-length text_event nor end_track
1556
+ events.append(['end_track', 0])
1557
+ else: # an eventless track!
1558
+ events = [['end_track', 0],]
1559
+
1560
+ # maybe_running_status = not no_running_status # unused? 4.7
1561
+ last_status = -1
1562
+
1563
+ for event_r in (events):
1564
+ E = copy.deepcopy(event_r)
1565
+ # otherwise the shifting'd corrupt the original
1566
+ if not E:
1567
+ continue
1568
+
1569
+ event = E.pop(0)
1570
+ if not len(event):
1571
+ continue
1572
+
1573
+ dtime = int(E.pop(0))
1574
+ # print('event='+str(event)+' dtime='+str(dtime))
1575
+
1576
+ event_data = ''
1577
+
1578
+ if ( # MIDI events -- eligible for running status
1579
+ event == 'note_on'
1580
+ or event == 'note_off'
1581
+ or event == 'control_change'
1582
+ or event == 'key_after_touch'
1583
+ or event == 'patch_change'
1584
+ or event == 'channel_after_touch'
1585
+ or event == 'pitch_wheel_change' ):
1586
+
1587
+ # This block is where we spend most of the time. Gotta be tight.
1588
+ if (event == 'note_off'):
1589
+ status = 0x80 | (int(E[0]) & 0x0F)
1590
+ parameters = struct.pack('>BB', int(E[1])&0x7F, int(E[2])&0x7F)
1591
+ elif (event == 'note_on'):
1592
+ status = 0x90 | (int(E[0]) & 0x0F)
1593
+ parameters = struct.pack('>BB', int(E[1])&0x7F, int(E[2])&0x7F)
1594
+ elif (event == 'key_after_touch'):
1595
+ status = 0xA0 | (int(E[0]) & 0x0F)
1596
+ parameters = struct.pack('>BB', int(E[1])&0x7F, int(E[2])&0x7F)
1597
+ elif (event == 'control_change'):
1598
+ status = 0xB0 | (int(E[0]) & 0x0F)
1599
+ parameters = struct.pack('>BB', int(E[1])&0xFF, int(E[2])&0xFF)
1600
+ elif (event == 'patch_change'):
1601
+ status = 0xC0 | (int(E[0]) & 0x0F)
1602
+ parameters = struct.pack('>B', int(E[1]) & 0xFF)
1603
+ elif (event == 'channel_after_touch'):
1604
+ status = 0xD0 | (int(E[0]) & 0x0F)
1605
+ parameters = struct.pack('>B', int(E[1]) & 0xFF)
1606
+ elif (event == 'pitch_wheel_change'):
1607
+ status = 0xE0 | (int(E[0]) & 0x0F)
1608
+ parameters = _write_14_bit(int(E[1]) + 0x2000)
1609
+ else:
1610
+ _warn("BADASS FREAKOUT ERROR 31415!")
1611
+
1612
+ # And now the encoding
1613
+ # w = BER compressed integer (not ASN.1 BER, see perlpacktut for
1614
+ # details). Its bytes represent an unsigned integer in base 128,
1615
+ # most significant digit first, with as few digits as possible.
1616
+ # Bit eight (the high bit) is set on each byte except the last.
1617
+
1618
+ data.append(_ber_compressed_int(dtime))
1619
+ if (status != last_status) or no_running_status:
1620
+ data.append(struct.pack('>B', status))
1621
+ data.append(parameters)
1622
+
1623
+ last_status = status
1624
+ continue
1625
+ else:
1626
+ # Not a MIDI event.
1627
+ # All the code in this block could be more efficient,
1628
+ # but this is not where the code needs to be tight.
1629
+ # print "zaz $event\n";
1630
+ last_status = -1
1631
+
1632
+ if event == 'raw_meta_event':
1633
+ event_data = _some_text_event(int(E[0]), E[1])
1634
+ elif (event == 'set_sequence_number'): # 3.9
1635
+ event_data = b'\xFF\x00\x02'+_int2twobytes(E[0])
1636
+
1637
+ # Text meta-events...
1638
+ # a case for a dict, I think (pjb) ...
1639
+ elif (event == 'text_event'):
1640
+ event_data = _some_text_event(0x01, E[0])
1641
+ elif (event == 'copyright_text_event'):
1642
+ event_data = _some_text_event(0x02, E[0])
1643
+ elif (event == 'track_name'):
1644
+ event_data = _some_text_event(0x03, E[0])
1645
+ elif (event == 'instrument_name'):
1646
+ event_data = _some_text_event(0x04, E[0])
1647
+ elif (event == 'lyric'):
1648
+ event_data = _some_text_event(0x05, E[0])
1649
+ elif (event == 'marker'):
1650
+ event_data = _some_text_event(0x06, E[0])
1651
+ elif (event == 'cue_point'):
1652
+ event_data = _some_text_event(0x07, E[0])
1653
+ elif (event == 'text_event_08'):
1654
+ event_data = _some_text_event(0x08, E[0])
1655
+ elif (event == 'text_event_09'):
1656
+ event_data = _some_text_event(0x09, E[0])
1657
+ elif (event == 'text_event_0a'):
1658
+ event_data = _some_text_event(0x0A, E[0])
1659
+ elif (event == 'text_event_0b'):
1660
+ event_data = _some_text_event(0x0B, E[0])
1661
+ elif (event == 'text_event_0c'):
1662
+ event_data = _some_text_event(0x0C, E[0])
1663
+ elif (event == 'text_event_0d'):
1664
+ event_data = _some_text_event(0x0D, E[0])
1665
+ elif (event == 'text_event_0e'):
1666
+ event_data = _some_text_event(0x0E, E[0])
1667
+ elif (event == 'text_event_0f'):
1668
+ event_data = _some_text_event(0x0F, E[0])
1669
+ # End of text meta-events
1670
+
1671
+ elif (event == 'end_track'):
1672
+ event_data = b"\xFF\x2F\x00"
1673
+
1674
+ elif (event == 'set_tempo'):
1675
+ #event_data = struct.pack(">BBwa*", 0xFF, 0x51, 3,
1676
+ # substr( struct.pack('>I', E[0]), 1, 3))
1677
+ event_data = b'\xFF\x51\x03'+struct.pack('>I',E[0])[1:]
1678
+ elif (event == 'smpte_offset'):
1679
+ # event_data = struct.pack(">BBwBBBBB", 0xFF, 0x54, 5, E[0:5] )
1680
+ event_data = struct.pack(">BBBbBBBB", 0xFF,0x54,0x05,E[0],E[1],E[2],E[3],E[4])
1681
+ elif (event == 'time_signature'):
1682
+ # event_data = struct.pack(">BBwBBBB", 0xFF, 0x58, 4, E[0:4] )
1683
+ event_data = struct.pack(">BBBbBBB", 0xFF, 0x58, 0x04, E[0],E[1],E[2],E[3])
1684
+ elif (event == 'key_signature'):
1685
+ event_data = struct.pack(">BBBbB", 0xFF, 0x59, 0x02, E[0],E[1])
1686
+ elif (event == 'sequencer_specific'):
1687
+ # event_data = struct.pack(">BBwa*", 0xFF,0x7F, len(E[0]), E[0])
1688
+ event_data = _some_text_event(0x7F, E[0])
1689
+ # End of Meta-events
1690
+
1691
+ # Other Things...
1692
+ elif (event == 'sysex_f0'):
1693
+ #event_data = struct.pack(">Bwa*", 0xF0, len(E[0]), E[0])
1694
+ #B=bitstring w=BER-compressed-integer a=null-padded-ascii-str
1695
+ event_data = bytearray(b'\xF0')+_ber_compressed_int(len(E[0]))+bytearray(E[0])
1696
+ elif (event == 'sysex_f7'):
1697
+ #event_data = struct.pack(">Bwa*", 0xF7, len(E[0]), E[0])
1698
+ event_data = bytearray(b'\xF7')+_ber_compressed_int(len(E[0]))+bytearray(E[0])
1699
+
1700
+ elif (event == 'song_position'):
1701
+ event_data = b"\xF2" + _write_14_bit( E[0] )
1702
+ elif (event == 'song_select'):
1703
+ event_data = struct.pack('>BB', 0xF3, E[0] )
1704
+ elif (event == 'tune_request'):
1705
+ event_data = b"\xF6"
1706
+ elif (event == 'raw_data'):
1707
+ _warn("_encode: raw_data event not supported")
1708
+ # event_data = E[0]
1709
+ continue
1710
+ # End of Other Stuff
1711
+
1712
+ else:
1713
+ # The Big Fallthru
1714
+ if unknown_callback:
1715
+ # push(@data, &{ $unknown_callback }( @$event_r ))
1716
+ pass
1717
+ else:
1718
+ _warn("Unknown event: "+str(event))
1719
+ # To surpress complaint here, just set
1720
+ # 'unknown_callback' => sub { return () }
1721
+ continue
1722
+
1723
+ #print "Event $event encoded part 2\n"
1724
+ if str(type(event_data)).find("'str'") >= 0:
1725
+ event_data = bytearray(event_data.encode('Latin1', 'ignore'))
1726
+ if len(event_data): # how could $event_data be empty
1727
+ # data.append(struct.pack('>wa*', dtime, event_data))
1728
+ # print(' event_data='+str(event_data))
1729
+ data.append(_ber_compressed_int(dtime)+event_data)
1730
+
1731
+ return b''.join(data)
1732
+