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