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