Mercurial > hg > smallbox
comparison util/matlab_midi/midiInfo.m @ 81:a30e8bd6d948
matlab_midi scripts
author | Ivan <ivan.damnjanovic@eecs.qmul.ac.uk> |
---|---|
date | Mon, 28 Mar 2011 17:35:01 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
80:16df822019f1 | 81:a30e8bd6d948 |
---|---|
1 function [Notes,endtime] = midiInfo(midi,outputFormat,tracklist) | |
2 % [Notes,endtime] = midiInfo(midi,outputFormat,tracklist) | |
3 % | |
4 % Takes a midi structre and generates info on notes and messages | |
5 % Can return a matrix of note parameters and/or output/display | |
6 % formatted table of messages | |
7 % | |
8 % Inputs: | |
9 % midi - Matlab structure (created by readmidi.m) | |
10 % tracklist - which tracks to show ([] for all) | |
11 % outputFormat | |
12 % - if it's a string write the formated output to the file | |
13 % - if 0, don't display or write formatted output | |
14 % - if 1, just display (default) | |
15 % | |
16 % outputs: | |
17 % Notes - a matrix containing a list of notes, ordered by start time | |
18 % column values are: | |
19 % 1 2 3 4 5 6 7 8 | |
20 % [track chan nn vel t1 t2 msgNum1 msgNum2] | |
21 % endtime - time of end of track message | |
22 % | |
23 | |
24 % Copyright (c) 2009 Ken Schutte | |
25 % more info at: http://www.kenschutte.com/midi | |
26 | |
27 if nargin<3 | |
28 tracklist=[]; | |
29 if nargin<2 | |
30 outputFormat=1; | |
31 end | |
32 end | |
33 if (isempty(tracklist)) | |
34 tracklist = 1:length(midi.track); | |
35 end | |
36 | |
37 [tempos, tempos_time] = getTempoChanges(midi); | |
38 | |
39 current_tempo = 500000; % default tempo | |
40 | |
41 fid = -1; | |
42 if (ischar(outputFormat)) | |
43 fid = fopen(outputFormat,'w'); | |
44 end | |
45 | |
46 endtime = -1; | |
47 | |
48 % each row: | |
49 % 1 2 3 4 5 6 7 8 | |
50 % [track chan nn vel t1 t2 msgNum1 msgNum2] | |
51 Notes = zeros(0,8); | |
52 | |
53 for i=1:length(tracklist) | |
54 tracknum = tracklist(i); | |
55 | |
56 cumtime=0; | |
57 seconds=0; | |
58 | |
59 Msg = cell(0); | |
60 Msg{1,1} = 'chan'; | |
61 Msg{1,2} = 'deltatime'; | |
62 Msg{1,3} = 'time'; | |
63 Msg{1,4} = 'name'; | |
64 Msg{1,5} = 'data'; | |
65 | |
66 for msgNum=1:length(midi.track(tracknum).messages) | |
67 | |
68 currMsg = midi.track(tracknum).messages(msgNum); | |
69 | |
70 midimeta = currMsg.midimeta; | |
71 deltatime = currMsg.deltatime; | |
72 data = currMsg.data; | |
73 type = currMsg.type; | |
74 chan = currMsg.chan; | |
75 | |
76 cumtime = cumtime + deltatime; | |
77 seconds = seconds + deltatime*1e-6*current_tempo/midi.ticks_per_quarter_note; | |
78 | |
79 [mx ind] = max(find(cumtime >= tempos_time)); | |
80 current_tempo = tempos(ind); | |
81 | |
82 % find start/stop of notes: | |
83 % if (strcmp(name,'Note on') && (data(2)>0)) | |
84 % note on with vel>0: | |
85 if (midimeta==1 && type==144 && data(2)>0) | |
86 % note on: | |
87 Notes(end+1,:) = [tracknum chan data(1) data(2) seconds 0 msgNum -1]; | |
88 % elseif ((strcmp(name,'Note on') && (data(2)==0)) || strcmp(name,'Note off')) | |
89 % note on with vel==0 or note off: | |
90 elseif (midimeta==1 && ( (type==144 && data(2)==0) || type==128 )) | |
91 | |
92 % note off: | |
93 % % find index, wther tr,chan,and nn match, and not complete | |
94 | |
95 ind = find((... | |
96 (Notes(:,1)==tracknum) + ... | |
97 (Notes(:,2)==chan) + ... | |
98 (Notes(:,3)==data(1)) + ... | |
99 (Notes(:,8)==-1)... | |
100 )==4); | |
101 | |
102 if (length(ind)==0) | |
103 error('ending non-open note?'); | |
104 elseif (length(ind)>1) | |
105 %% ??? not sure about this... | |
106 %%disp('warning: found mulitple matches in endNote, taking first...'); | |
107 ind = ind(1); | |
108 end | |
109 | |
110 % set info on ending: | |
111 Notes(ind,6) = seconds; | |
112 Notes(ind,8) = msgNum; | |
113 | |
114 % end of track: | |
115 elseif (midimeta==0 && type==47) | |
116 if (endtime == -1) | |
117 endtime = seconds; | |
118 else | |
119 disp('two "end of track" messages?'); | |
120 endtime(end+1) = seconds; | |
121 end | |
122 | |
123 | |
124 end | |
125 | |
126 % we could check to make sure it ends with | |
127 % 'end of track' | |
128 | |
129 | |
130 if (outputFormat ~= 0) | |
131 % get some specific descriptions: | |
132 name = num2str(type); | |
133 dataStr = num2str(data); | |
134 | |
135 if (isempty(chan)) | |
136 Msg{msgNum,1} = '-'; | |
137 else | |
138 Msg{msgNum,1} = num2str(chan); | |
139 end | |
140 | |
141 Msg{msgNum,2} = num2str(deltatime); | |
142 Msg{msgNum,3} = formatTime(seconds); | |
143 | |
144 if (midimeta==0) | |
145 Msg{msgNum,4} = 'meta'; | |
146 else | |
147 Msg{msgNum,4} = ''; | |
148 end | |
149 | |
150 [name,dataStr] = getMsgInfo(midimeta, type, data); | |
151 Msg{msgNum,5} = name; | |
152 Msg{msgNum,6} = dataStr; | |
153 end | |
154 | |
155 | |
156 end | |
157 | |
158 if (outputFormat ~= 0) | |
159 printTrackInfo(Msg,tracknum,fid); | |
160 end | |
161 | |
162 end | |
163 | |
164 % make this an option!!! | |
165 % - I'm not sure why it's needed... | |
166 % remove start silence: | |
167 first_t = min(Notes(:,5)); | |
168 Notes(:,5) = Notes(:,5) - first_t; | |
169 Notes(:,6) = Notes(:,6) - first_t; | |
170 | |
171 % sort Notes by start time: | |
172 [junk,ord] = sort(Notes(:,5)); | |
173 Notes = Notes(ord,:); | |
174 | |
175 | |
176 if (fid ~= -1) | |
177 fclose(fid); | |
178 end | |
179 | |
180 | |
181 | |
182 | |
183 | |
184 | |
185 | |
186 | |
187 | |
188 | |
189 | |
190 function printTrackInfo(Msg,tracknum,fid) | |
191 | |
192 | |
193 % make cols same length instead of just using \t | |
194 for i=1:size(Msg,2) | |
195 maxLen(i)=0; | |
196 for j=1:size(Msg,1) | |
197 if (length(Msg{j,i})>maxLen(i)) | |
198 maxLen(i) = length(Msg{j,i}); | |
199 end | |
200 end | |
201 end | |
202 | |
203 | |
204 s=''; | |
205 s=[s sprintf('--------------------------------------------------\n')]; | |
206 s=[s sprintf('Track %d\n',tracknum)]; | |
207 s=[s sprintf('--------------------------------------------------\n')]; | |
208 | |
209 if (fid == -1) | |
210 disp(s) | |
211 else | |
212 fprintf(fid,'%s',s); | |
213 end | |
214 | |
215 | |
216 for i=1:size(Msg,1) | |
217 line=''; | |
218 for j=1:size(Msg,2) | |
219 sp = repmat(' ',1,5+maxLen(j)-length(Msg{i,j})); | |
220 m = Msg{i,j}; | |
221 m = m(:)'; % ensure column vector | |
222 % line = [line Msg{i,j} sp]; | |
223 line = [line m sp]; | |
224 end | |
225 | |
226 if (fid == -1) | |
227 disp(line) | |
228 else | |
229 fprintf(fid,'%s\n',line); | |
230 end | |
231 | |
232 end | |
233 | |
234 | |
235 | |
236 function s=formatTime(seconds) | |
237 | |
238 minutes = floor(seconds/60); | |
239 secs = seconds - 60*minutes; | |
240 | |
241 s = sprintf('%d:%2.3f',minutes,secs); | |
242 | |
243 | |
244 | |
245 function [name,dataStr]=getMsgInfo(midimeta, type, data); | |
246 | |
247 % meta events: | |
248 if (midimeta==0) | |
249 if (type==0); name = 'Sequence Number'; len=2; dataStr = num2str(data); | |
250 elseif (type==1); name = 'Text Events'; len=-1; dataStr = char(data); | |
251 elseif (type==2); name = 'Copyright Notice'; len=-1; dataStr = char(data); | |
252 elseif (type==3); name = 'Sequence/Track Name'; len=-1; dataStr = char(data); | |
253 elseif (type==4); name = 'Instrument Name'; len=-1; dataStr = char(data); | |
254 elseif (type==5); name = 'Lyric'; len=-1; dataStr = char(data); | |
255 elseif (type==6); name = 'Marker'; len=-1; dataStr = char(data); | |
256 elseif (type==7); name = 'Cue Point'; len=-1; dataStr = char(data); | |
257 elseif (type==32); name = 'MIDI Channel Prefix'; len=1; dataStr = num2str(data); | |
258 elseif (type==47); name = 'End of Track'; len=0; dataStr = ''; | |
259 elseif (type==81); name = 'Set Tempo'; len=3; | |
260 val = data(1)*16^4+data(2)*16^2+data(3); dataStr = ['microsec per quarter note: ' num2str(val)]; | |
261 elseif (type==84); name = 'SMPTE Offset'; len=5; | |
262 dataStr = ['[hh mm ss fr ff]=' num2str(data)]; | |
263 elseif (type==88); name = 'Time Signature'; len=4; | |
264 dataStr = [num2str(data(1)) '/' num2str(data(2)) ', clock ticks and notated 32nd notes=' num2str(data(3)) '/' num2str(data(4))]; | |
265 elseif (type==89); name = 'Key Signature'; len=2; | |
266 % num sharps/flats (flats negative) | |
267 if (data(1)>=0) | |
268 % 1 2 3 4 5 6 7 | |
269 ss={'C','G','D', 'A', 'E','B', 'F#', 'C#'}; | |
270 dataStr = ss{data(1)+1}; | |
271 else | |
272 % 1 2 3 4 5 6 7 | |
273 ss={'F','Bb','Eb','Ab','Db','Gb','Cb'}; | |
274 dataStr = ss{abs(data(1))}; | |
275 end | |
276 if (data(2)==0) | |
277 dataStr = [dataStr ' Major']; | |
278 else | |
279 dataStr = [dataStr ' Minor']; | |
280 end | |
281 | |
282 elseif (type==89); name = 'Sequencer-Specific Meta-event'; len=-1; | |
283 dataStr = char(data); | |
284 % !! last two conflict... | |
285 | |
286 else | |
287 name = ['UNKNOWN META EVENT: ' num2str(type)]; dataStr = num2str(data); | |
288 end | |
289 | |
290 % meta 0x21 = MIDI port number, length 1 (? perhaps) | |
291 else | |
292 | |
293 % channel voice messages: | |
294 % (from event byte with chan removed, eg 0x8n -> 0x80 = 128 for | |
295 % note off) | |
296 if (type==128); name = 'Note off'; len=2; dataStr = ['nn=' num2str(data(1)) ' vel=' num2str(data(2))]; | |
297 elseif (type==144); name = 'Note on'; len=2; dataStr = ['nn=' num2str(data(1)) ' vel=' num2str(data(2))]; | |
298 elseif (type==160); name = 'Polyphonic Key Pressure'; len=2; dataStr = ['nn=' num2str(data(1)) ' vel=' num2str(data(2))]; | |
299 elseif (type==176); name = 'Controller Change'; len=2; dataStr = ['ctrl=' controllers(data(1)) ' value=' num2str(data(2))]; | |
300 elseif (type==192); name = 'Program Change'; len=1; dataStr = ['instr=' num2str(data)]; | |
301 elseif (type==208); name = 'Channel Key Pressure'; len=1; dataStr = ['vel=' num2str(data)]; | |
302 elseif (type==224); name = 'Pitch Bend'; len=2; | |
303 val = data(1)+data(2)*256; | |
304 val = base2dec('2000',16) - val; | |
305 dataStr = ['change=' num2str(val) '?']; | |
306 | |
307 % channel mode messages: | |
308 % ... unsure about data for these... (do some have a data byte and | |
309 % others not?) | |
310 % | |
311 % 0xC1 .. 0xC8 | |
312 elseif (type==193); name = 'All Sounds Off'; dataStr = num2str(data); | |
313 elseif (type==194); name = 'Reset All Controllers'; dataStr = num2str(data); | |
314 elseif (type==195); name = 'Local Control'; dataStr = num2str(data); | |
315 elseif (type==196); name = 'All Notes Off'; dataStr = num2str(data); | |
316 elseif (type==197); name = 'Omni Mode Off'; dataStr = num2str(data); | |
317 elseif (type==198); name = 'Omni Mode On'; dataStr = num2str(data); | |
318 elseif (type==199); name = 'Mono Mode On'; dataStr = num2str(data); | |
319 elseif (type==200); name = 'Poly Mode On'; dataStr = num2str(data); | |
320 | |
321 % sysex, F0->F7 | |
322 elseif (type==240); name = 'Sysex 0xF0'; dataStr = num2str(data); | |
323 elseif (type==241); name = 'Sysex 0xF1'; dataStr = num2str(data); | |
324 elseif (type==242); name = 'Sysex 0xF2'; dataStr = num2str(data); | |
325 elseif (type==243); name = 'Sysex 0xF3'; dataStr = num2str(data); | |
326 elseif (type==244); name = 'Sysex 0xF4'; dataStr = num2str(data); | |
327 elseif (type==245); name = 'Sysex 0xF5'; dataStr = num2str(data); | |
328 elseif (type==246); name = 'Sysex 0xF6'; dataStr = num2str(data); | |
329 elseif (type==247); name = 'Sysex 0xF7'; dataStr = num2str(data); | |
330 | |
331 % realtime | |
332 % (i think have no data..?) | |
333 elseif (type==248); name = 'Real-time 0xF8 - Timing clock'; dataStr = num2str(data); | |
334 elseif (type==249); name = 'Real-time 0xF9'; dataStr = num2str(data); | |
335 elseif (type==250); name = 'Real-time 0xFA - Start a sequence'; dataStr = num2str(data); | |
336 elseif (type==251); name = 'Real-time 0xFB - Continue a sequence'; dataStr = num2str(data); | |
337 elseif (type==252); name = 'Real-time 0xFC - Stop a sequence'; dataStr = num2str(data); | |
338 elseif (type==253); name = 'Real-time 0xFD'; dataStr = num2str(data); | |
339 elseif (type==254); name = 'Real-time 0xFE'; dataStr = num2str(data); | |
340 elseif (type==255); name = 'Real-time 0xFF'; dataStr = num2str(data); | |
341 | |
342 | |
343 else | |
344 name = ['UNKNOWN MIDI EVENT: ' num2str(type)]; dataStr = num2str(data); | |
345 end | |
346 | |
347 | |
348 end | |
349 | |
350 function s=controllers(n) | |
351 if (n==1); s='Mod Wheel'; | |
352 elseif (n==2); s='Breath Controllery'; | |
353 elseif (n==4); s='Foot Controller'; | |
354 elseif (n==5); s='Portamento Time'; | |
355 elseif (n==6); s='Data Entry MSB'; | |
356 elseif (n==7); s='Volume'; | |
357 elseif (n==8); s='Balance'; | |
358 elseif (n==10); s='Pan'; | |
359 elseif (n==11); s='Expression Controller'; | |
360 elseif (n==16); s='General Purpose 1'; | |
361 elseif (n==17); s='General Purpose 2'; | |
362 elseif (n==18); s='General Purpose 3'; | |
363 elseif (n==19); s='General Purpose 4'; | |
364 elseif (n==64); s='Sustain'; | |
365 elseif (n==65); s='Portamento'; | |
366 elseif (n==66); s='Sustenuto'; | |
367 elseif (n==67); s='Soft Pedal'; | |
368 elseif (n==69); s='Hold 2'; | |
369 elseif (n==80); s='General Purpose 5'; | |
370 elseif (n==81); s='Temp Change (General Purpose 6)'; | |
371 elseif (n==82); s='General Purpose 7'; | |
372 elseif (n==83); s='General Purpose 8'; | |
373 elseif (n==91); s='Ext Effects Depth'; | |
374 elseif (n==92); s='Tremelo Depthy'; | |
375 elseif (n==93); s='Chorus Depth'; | |
376 elseif (n==94); s='Detune Depth (Celeste Depth)'; | |
377 elseif (n==95); s='Phaser Depth'; | |
378 elseif (n==96); s='Data Increment (Data Entry +1)'; | |
379 elseif (n==97); s='Data Decrement (Data Entry -1)'; | |
380 elseif (n==98); s='Non-Registered Param LSB'; | |
381 elseif (n==99); s='Non-Registered Param MSB'; | |
382 elseif (n==100); s='Registered Param LSB'; | |
383 elseif (n==101); s='Registered Param MSB'; | |
384 else | |
385 s='UNKNOWN CONTROLLER'; | |
386 end | |
387 | |
388 %Channel mode message values | |
389 %Reset All Controllers 79 121 Val ?? | |
390 %Local Control 7A 122 Val 0 = off, 7F (127) = on | |
391 %All Notes Off 7B 123 Val must be 0 | |
392 %Omni Mode Off 7C 124 Val must be 0 | |
393 %Omni Mode On 7D 125 Val must be 0 | |
394 %Mono Mode On 7E 126 Val = # of channels, or 0 if # channels equals # voices in receiver | |
395 %Poly Mode On 7F 127 Val must be 0 |