-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmidiInfo.m
429 lines (344 loc) · 13 KB
/
midiInfo.m
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
function [Notes,endtime] = midiInfo(midi,outputFormat,tracklist,verbose)
% [Notes,endtime] = midiInfo(midi,outputFormat,tracklist)
%
% Takes a midi structre and generates info on notes and messages
% Can return a matrix of note parameters and/or output/display
% formatted table of messages
%
% Inputs:
% midi - Matlab structure (created by readmidi.m)
% tracklist - which tracks to show ([] for all)
% outputFormat
% - if it's a string write the formated output to the file
% - if 0, don't display or write formatted output
% - if 1, just display (default)
%
% outputs:
% Notes - a matrix containing a list of notes, ordered by start time
% column values are:
% 1 2 3 4 5 6 7 8
% [track chan nn vel t1 t2 msgNum1 msgNum2]
% endtime - time of end of track message
%
% Copyright (c) 2009 Ken Schutte
% more info at: http://www.kenschutte.com/midi
if nargin<4
verbose = 0;
end
if nargin<3
tracklist=[];
if nargin<2
outputFormat=0;
end
end
if (isempty(tracklist))
tracklist = 1:length(midi.track);
end
current_tempo = 500000; % default tempo
[tempos, tempos_time] = getTempoChanges(midi);
% What to do if no tempos are given?
% This seems at leat get things to work (see eire01.mid)
if length(tempos) == 0
tempos = [current_tempo];
tempos_time = [0];
end
fid = -1;
if (ischar(outputFormat))
fid = fopen(outputFormat,'w');
end
endtime = -1;
% each row:
% 1 2 3 4 5 6 7 8
% [track chan nn vel t1 t2 msgNum1 msgNum2]
Notes = zeros(0,8);
for i=1:length(tracklist)
tracknum = tracklist(i);
cumtime=0;
seconds=0;
Msg = cell(0);
Msg{1,1} = 'chan';
Msg{1,2} = 'deltatime';
Msg{1,3} = 'time';
Msg{1,4} = 'name';
Msg{1,5} = 'data';
for msgNum=1:length(midi.track(tracknum).messages)
currMsg = midi.track(tracknum).messages(msgNum);
midimeta = currMsg.midimeta;
deltatime = currMsg.deltatime;
data = currMsg.data;
type = currMsg.type;
chan = currMsg.chan;
cumtime = cumtime + deltatime;
seconds = seconds + deltatime*1e-6*current_tempo/midi.ticks_per_quarter_note;
[mx ind] = max(find(cumtime >= tempos_time));
if numel(ind)>0 % if only we found smth
current_tempo = tempos(ind);
else
if verbose
disp('No tempos_time found?');
end
end
% find start/stop of notes:
% if (strcmp(name,'Note on') && (data(2)>0))
% note on with vel>0:
if (midimeta==1 && type==144 && data(2)>0)
% note on:
Notes(end+1,:) = [tracknum chan data(1) data(2) seconds 0 msgNum -1];
% elseif ((strcmp(name,'Note on') && (data(2)==0)) || strcmp(name,'Note off'))
% note on with vel==0 or note off:
elseif (midimeta==1 && ( (type==144 && data(2)==0) || type==128 ))
% note off:
% % find index, wther tr,chan,and nn match, and not complete
ind = find((...
(Notes(:,1)==tracknum) + ...
(Notes(:,2)==chan) + ...
(Notes(:,3)==data(1)) + ...
(Notes(:,8)==-1)...
)==4);
if (length(ind)==0)
%% was an error before; change to warning and ignore the message.
if verbose
warning('ending non-open note?');
end
else
if (length(ind)>1)
%% ??? not sure about this...
%disp('warning: found mulitple matches in endNote, taking first...');
%% should we take first or last? should we give a warning?
ind = ind(1);
end
% set info on ending:
Notes(ind,6) = seconds;
Notes(ind,8) = msgNum;
end
% end of track:
elseif (midimeta==0 && type==47)
if (endtime == -1)
endtime = seconds;
else
if verbose
disp('two "end of track" messages?');
end
endtime(end+1) = seconds;
end
end
% we could check to make sure it ends with
% 'end of track'
if (outputFormat ~= 0)
% get some specific descriptions:
name = num2str(type);
dataStr = num2str(data);
if (isempty(chan))
Msg{msgNum,1} = '-';
else
Msg{msgNum,1} = num2str(chan);
end
Msg{msgNum,2} = num2str(deltatime);
Msg{msgNum,3} = formatTime(seconds);
if (midimeta==0)
Msg{msgNum,4} = 'meta';
else
Msg{msgNum,4} = '';
end
[name,dataStr] = getMsgInfo(midimeta, type, data);
Msg{msgNum,5} = name;
Msg{msgNum,6} = dataStr;
end
end %% end track.
%% any note-on that are not turned off?
nleft = sum(Notes(:,8)==-1);
if (nleft > 0)
%warning(sprintf('%d notes needed to be turned off at end of track.', nleft));
Notes(Notes(:,8) == -1, 6) = seconds;
end
if (outputFormat ~= 0)
printTrackInfo(Msg,tracknum,fid);
end
end
% make this an option!!!
% - I'm not sure why it's needed...
% remove start silence:
%first_t = min(Notes(:,5));
Notes(:,5) = Notes(:,5);% - first_t;
Notes(:,6) = Notes(:,6);% - first_t;
% sort Notes by start time:
[junk,ord] = sort(Notes(:,5));
Notes = Notes(ord,:);
if (fid ~= -1)
fclose(fid);
end
function printTrackInfo(Msg,tracknum,fid)
% make cols same length instead of just using \t
for i=1:size(Msg,2)
maxLen(i)=0;
for j=1:size(Msg,1)
if (length(Msg{j,i})>maxLen(i))
maxLen(i) = length(Msg{j,i});
end
end
end
s='';
s=[s sprintf('--------------------------------------------------\n')];
s=[s sprintf('Track %d\n',tracknum)];
s=[s sprintf('--------------------------------------------------\n')];
if (fid == -1)
disp(s)
else
fprintf(fid,'%s',s);
end
for i=1:size(Msg,1)
line='';
for j=1:size(Msg,2)
sp = repmat(' ',1,5+maxLen(j)-length(Msg{i,j}));
m = Msg{i,j};
m = m(:)'; % ensure column vector
% line = [line Msg{i,j} sp];
line = [line m sp];
end
if (fid == -1)
disp(line)
else
fprintf(fid,'%s\n',line);
end
end
function s=formatTime(seconds)
minutes = floor(seconds/60);
secs = seconds - 60*minutes;
s = sprintf('%d:%2.3f',minutes,secs);
function [name,dataStr]=getMsgInfo(midimeta, type, data);
% meta events:
if (midimeta==0)
if (type==0); name = 'Sequence Number'; len=2; dataStr = num2str(data);
elseif (type==1); name = 'Text Events'; len=-1; dataStr = char(data);
elseif (type==2); name = 'Copyright Notice'; len=-1; dataStr = char(data);
elseif (type==3); name = 'Sequence/Track Name'; len=-1; dataStr = char(data);
elseif (type==4); name = 'Instrument Name'; len=-1; dataStr = char(data);
elseif (type==5); name = 'Lyric'; len=-1; dataStr = char(data);
elseif (type==6); name = 'Marker'; len=-1; dataStr = char(data);
elseif (type==7); name = 'Cue Point'; len=-1; dataStr = char(data);
elseif (type==32); name = 'MIDI Channel Prefix'; len=1; dataStr = num2str(data);
elseif (type==47); name = 'End of Track'; len=0; dataStr = '';
elseif (type==81); name = 'Set Tempo'; len=3;
val = data(1)*16^4+data(2)*16^2+data(3); dataStr = ['microsec per quarter note: ' num2str(val)];
elseif (type==84); name = 'SMPTE Offset'; len=5;
dataStr = ['[hh mm ss fr ff]=' num2str(data)];
elseif (type==88); name = 'Time Signature'; len=4;
dataStr = [num2str(data(1)) '/' num2str(data(2)) ', clock ticks and notated 32nd notes=' num2str(data(3)) '/' num2str(data(4))];
elseif (type==89); name = 'Key Signature'; len=2;
% num sharps/flats (flats negative)
if (data(1)>=0)
% 1 2 3 4 5 6 7
ss={'C','G','D', 'A', 'E','B', 'F#', 'C#'};
dataStr = ss{data(1)+1};
else
% 1 2 3 4 5 6 7
ss={'F','Bb','Eb','Ab','Db','Gb','Cb'};
dataStr = ss{abs(data(1))};
end
if (data(2)==0)
dataStr = [dataStr ' Major'];
else
dataStr = [dataStr ' Minor'];
end
elseif (type==89); name = 'Sequencer-Specific Meta-event'; len=-1;
dataStr = char(data);
% !! last two conflict...
else
name = ['UNKNOWN META EVENT: ' num2str(type)]; dataStr = num2str(data);
end
% meta 0x21 = MIDI port number, length 1 (? perhaps)
else
% channel voice messages:
% (from event byte with chan removed, eg 0x8n -> 0x80 = 128 for
% note off)
if (type==128); name = 'Note off'; len=2; dataStr = ['nn=' num2str(data(1)) ' vel=' num2str(data(2))];
elseif (type==144); name = 'Note on'; len=2; dataStr = ['nn=' num2str(data(1)) ' vel=' num2str(data(2))];
elseif (type==160); name = 'Polyphonic Key Pressure'; len=2; dataStr = ['nn=' num2str(data(1)) ' vel=' num2str(data(2))];
elseif (type==176); name = 'Controller Change'; len=2; dataStr = ['ctrl=' controllers(data(1)) ' value=' num2str(data(2))];
elseif (type==192); name = 'Program Change'; len=1; dataStr = ['instr=' num2str(data)];
elseif (type==208); name = 'Channel Key Pressure'; len=1; dataStr = ['vel=' num2str(data)];
elseif (type==224); name = 'Pitch Bend'; len=2;
val = data(1)+data(2)*256;
val = base2dec('2000',16) - val;
dataStr = ['change=' num2str(val) '?'];
% channel mode messages:
% ... unsure about data for these... (do some have a data byte and
% others not?)
%
% 0xC1 .. 0xC8
elseif (type==193); name = 'All Sounds Off'; dataStr = num2str(data);
elseif (type==194); name = 'Reset All Controllers'; dataStr = num2str(data);
elseif (type==195); name = 'Local Control'; dataStr = num2str(data);
elseif (type==196); name = 'All Notes Off'; dataStr = num2str(data);
elseif (type==197); name = 'Omni Mode Off'; dataStr = num2str(data);
elseif (type==198); name = 'Omni Mode On'; dataStr = num2str(data);
elseif (type==199); name = 'Mono Mode On'; dataStr = num2str(data);
elseif (type==200); name = 'Poly Mode On'; dataStr = num2str(data);
% sysex, F0->F7
elseif (type==240); name = 'Sysex 0xF0'; dataStr = num2str(data);
elseif (type==241); name = 'Sysex 0xF1'; dataStr = num2str(data);
elseif (type==242); name = 'Sysex 0xF2'; dataStr = num2str(data);
elseif (type==243); name = 'Sysex 0xF3'; dataStr = num2str(data);
elseif (type==244); name = 'Sysex 0xF4'; dataStr = num2str(data);
elseif (type==245); name = 'Sysex 0xF5'; dataStr = num2str(data);
elseif (type==246); name = 'Sysex 0xF6'; dataStr = num2str(data);
elseif (type==247); name = 'Sysex 0xF7'; dataStr = num2str(data);
% realtime
% (i think have no data..?)
elseif (type==248); name = 'Real-time 0xF8 - Timing clock'; dataStr = num2str(data);
elseif (type==249); name = 'Real-time 0xF9'; dataStr = num2str(data);
elseif (type==250); name = 'Real-time 0xFA - Start a sequence'; dataStr = num2str(data);
elseif (type==251); name = 'Real-time 0xFB - Continue a sequence'; dataStr = num2str(data);
elseif (type==252); name = 'Real-time 0xFC - Stop a sequence'; dataStr = num2str(data);
elseif (type==253); name = 'Real-time 0xFD'; dataStr = num2str(data);
elseif (type==254); name = 'Real-time 0xFE'; dataStr = num2str(data);
elseif (type==255); name = 'Real-time 0xFF'; dataStr = num2str(data);
else
name = ['UNKNOWN MIDI EVENT: ' num2str(type)]; dataStr = num2str(data);
end
end
function s=controllers(n)
if (n==1); s='Mod Wheel';
elseif (n==2); s='Breath Controllery';
elseif (n==4); s='Foot Controller';
elseif (n==5); s='Portamento Time';
elseif (n==6); s='Data Entry MSB';
elseif (n==7); s='Volume';
elseif (n==8); s='Balance';
elseif (n==10); s='Pan';
elseif (n==11); s='Expression Controller';
elseif (n==16); s='General Purpose 1';
elseif (n==17); s='General Purpose 2';
elseif (n==18); s='General Purpose 3';
elseif (n==19); s='General Purpose 4';
elseif (n==64); s='Sustain';
elseif (n==65); s='Portamento';
elseif (n==66); s='Sustenuto';
elseif (n==67); s='Soft Pedal';
elseif (n==69); s='Hold 2';
elseif (n==80); s='General Purpose 5';
elseif (n==81); s='Temp Change (General Purpose 6)';
elseif (n==82); s='General Purpose 7';
elseif (n==83); s='General Purpose 8';
elseif (n==91); s='Ext Effects Depth';
elseif (n==92); s='Tremelo Depthy';
elseif (n==93); s='Chorus Depth';
elseif (n==94); s='Detune Depth (Celeste Depth)';
elseif (n==95); s='Phaser Depth';
elseif (n==96); s='Data Increment (Data Entry +1)';
elseif (n==97); s='Data Decrement (Data Entry -1)';
elseif (n==98); s='Non-Registered Param LSB';
elseif (n==99); s='Non-Registered Param MSB';
elseif (n==100); s='Registered Param LSB';
elseif (n==101); s='Registered Param MSB';
else
s='UNKNOWN CONTROLLER';
end
%Channel mode message values
%Reset All Controllers 79 121 Val ??
%Local Control 7A 122 Val 0 = off, 7F (127) = on
%All Notes Off 7B 123 Val must be 0
%Omni Mode Off 7C 124 Val must be 0
%Omni Mode On 7D 125 Val must be 0
%Mono Mode On 7E 126 Val = # of channels, or 0 if # channels equals # voices in receiver
%Poly Mode On 7F 127 Val must be 0