diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/midiInfo.m	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,395 @@
+function [Notes,endtime] = midiInfo(midi,outputFormat,tracklist)
+% [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<3
+  tracklist=[];
+  if nargin<2
+    outputFormat=1;
+  end
+end
+if (isempty(tracklist))
+  tracklist = 1:length(midi.track);
+end
+
+[tempos, tempos_time] = getTempoChanges(midi);
+
+current_tempo = 500000;  % default tempo
+
+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));
+    current_tempo = tempos(ind);
+
+    % 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)
+	error('ending non-open note?');
+      elseif (length(ind)>1)
+	%% ??? not sure about this...
+	%%disp('warning: found mulitple matches in endNote, taking first...');
+	ind = ind(1);
+      end
+      
+      % set info on ending:
+      Notes(ind,6) = seconds;
+      Notes(ind,8) = msgNum;
+      
+      % end of track:
+    elseif (midimeta==0 && type==47)
+      if (endtime == -1)
+	endtime = seconds;
+      else
+	disp('two "end of track" messages?');
+	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
+  
+  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