changeset 81:a30e8bd6d948

matlab_midi scripts
author Ivan <ivan.damnjanovic@eecs.qmul.ac.uk>
date Mon, 28 Mar 2011 17:35:01 +0100
parents 16df822019f1
children 138f7f0fdcdf
files util/matlab_midi/README util/matlab_midi/getTempoChanges.m util/matlab_midi/jesu.mid util/matlab_midi/matrix2midi.m util/matlab_midi/midi2audio.m util/matlab_midi/midi2freq.m util/matlab_midi/midiInfo.m util/matlab_midi/piano_roll.m util/matlab_midi/readmidi.m util/matlab_midi/synth.m util/matlab_midi/writemidi.m
diffstat 11 files changed, 1342 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/README	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,10 @@
+
+MIDI file tools for MATLAB
+
+Information, examples, and updates are available at:
+http://www.kenschutte.com/midi/
+
+This code is released under the GPL.
+
+Ken Schutte
+kschutte@csail.mit.edu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/getTempoChanges.m	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,31 @@
+function [tempos,tempos_time]=getTempoChanges(midi)
+% [tempos,tempos_time]=getTempoChanges(midi)
+%
+% input: a midi struct from readmidi.m
+% output:
+%  tempos = tempo values indexed by tempos_time
+%    tempos_time is in units of ticks
+%
+% should tempo changes effect across tracks? across channels?
+%
+
+% Copyright (c) 2009 Ken Schutte
+% more info at: http://www.kenschutte.com/midi
+
+tempos = [];
+tempos_time = [];
+for i=1:length(midi.track)
+  cumtime=0;
+  for j=1:length(midi.track(i).messages)
+    cumtime = cumtime+midi.track(i).messages(j).deltatime;
+%    if (strcmp(midi.track(i).messages(j).name,'Set Tempo'))
+    if (midi.track(i).messages(j).midimeta==0 && midi.track(i).messages(j).type==81)
+      tempos_time(end+1) = cumtime;
+      d = midi.track(i).messages(j).data;
+      tempos(end+1) =  d(1)*16^4 + d(2)*16^2 + d(3);
+    end
+  end
+end
+
+
+
Binary file util/matlab_midi/jesu.mid has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/matrix2midi.m	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,137 @@
+function midi=matrix2midi(M,ticks_per_quarter_note,timesig)
+% midi=matrix2midi(M,ticks_per_quarter_note)
+%
+% generates a midi matlab structure from a matrix
+%  specifying a list of notes.  The structure output
+%  can then be used by writemidi.m
+%
+% M: input matrix:
+%   1     2    3  4   5  6  
+%  [track chan nn vel t1 t2] (any more cols ignored...)
+%
+% optional arguments:
+% - ticks_per_quarter_note: integer (default 300)
+% - timesig: a vector of len 4 (default [4,2,24,8])
+%
+
+% Copyright (c) 2009 Ken Schutte
+% more info at: http://www.kenschutte.com/midi
+
+% TODO options:
+%  - note-off vs vel=0
+%  - tempo, ticks, etc
+
+if nargin < 2
+  ticks_per_quarter_note = 300;
+end
+
+if nargin < 3
+  timesig = [4,2,24,8];
+end
+
+tracks = unique(M(:,1));
+Ntracks = length(tracks);
+
+% start building 'midi' struct
+
+if (Ntracks==1)
+  midi.format = 0;
+else
+  midi.format = 1;
+end
+
+midi.ticks_per_quarter_note = ticks_per_quarter_note;
+
+tempo = 500000;   % could be set by user, etc...
+% (microsec per quarter note)
+
+for i=1:Ntracks
+  
+  trM = M(i==M(:,1),:);
+  
+  note_events_onoff = [];
+  note_events_n = [];
+  note_events_ticktime = [];
+ 
+  % gather all the notes:
+  for j=1:size(trM,1)
+    % note on event:
+    note_events_onoff(end+1)    = 1;
+    note_events_n(end+1)        = j;
+    note_events_ticktime(end+1) = 1e6 * trM(j,5) * ticks_per_quarter_note / tempo;
+    
+    % note off event:
+    note_events_onoff(end+1)    = 0;
+    note_events_n(end+1)        = j;
+    note_events_ticktime(end+1) = 1e6 * trM(j,6) * ticks_per_quarter_note / tempo;
+  end
+
+  
+  msgCtr = 1;
+  
+  % set tempo...
+  midi.track(i).messages(msgCtr).deltatime = 0;
+  midi.track(i).messages(msgCtr).type = 81;
+  midi.track(i).messages(msgCtr).midimeta = 0;
+  midi.track(i).messages(msgCtr).data = encode_int(tempo,3);
+  midi.track(i).messages(msgCtr).chan = [];
+  msgCtr = msgCtr + 1;
+  
+  % set time sig...
+  midi.track(i).messages(msgCtr).deltatime = 0;
+  midi.track(i).messages(msgCtr).type = 88;
+  midi.track(i).messages(msgCtr).midimeta = 0;
+  midi.track(i).messages(msgCtr).data = timesig(:);
+  midi.track(i).messages(msgCtr).chan = [];
+  msgCtr = msgCtr + 1;
+  
+  [junk,ord] = sort(note_events_ticktime);
+
+  prevtick = 0;
+  for j=1:length(ord)
+    
+    n = note_events_n(ord(j));
+    cumticks = note_events_ticktime(ord(j));
+    
+    midi.track(i).messages(msgCtr).deltatime = cumticks - prevtick;
+    midi.track(i).messages(msgCtr).midimeta = 1; 
+    midi.track(i).messages(msgCtr).chan = trM(n,2);
+    midi.track(i).messages(msgCtr).used_running_mode = 0;
+
+    if (note_events_onoff(ord(j))==1)
+      % note on:
+      midi.track(i).messages(msgCtr).type = 144;
+      midi.track(i).messages(msgCtr).data = [trM(n,3); trM(n,4)];
+    else
+      %-- note off msg:
+      %midi.track(i).messages(msgCtr).type = 128;
+      %midi.track(i).messages(msgCtr).data = [trM(n,3); trM(n,4)];
+      %-- note on vel=0:
+      midi.track(i).messages(msgCtr).type = 144;
+      midi.track(i).messages(msgCtr).data = [trM(n,3); 0];
+    end
+    msgCtr = msgCtr + 1;
+    
+    prevtick = cumticks;
+  end
+
+  % end of track:
+  midi.track(i).messages(msgCtr).deltatime = 0;
+  midi.track(i).messages(msgCtr).type = 47;
+  midi.track(i).messages(msgCtr).midimeta = 0;
+  midi.track(i).messages(msgCtr).data = [];
+  midi.track(i).messages(msgCtr).chan = [];
+  msgCtr = msgCtr + 1;
+  
+end
+
+
+% return a _column_ vector
+% (copied from writemidi.m)
+function A=encode_int(val,Nbytes)
+
+A = zeros(Nbytes,1);  %ensure col vector (diff from writemidi.m...)
+for i=1:Nbytes
+  A(i) = bitand(bitshift(val, -8*(Nbytes-i)), 255);
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/midi2audio.m	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,66 @@
+function [y,Fs]=midi2audio(input,Fs,synthtype)
+% y = midi2audio(input, Fs, synthtype)
+% y = midi2audio(input, Fs)
+% y = midi2audio(input)
+%
+% Convert midi structure to a digital waveform
+%
+% Inputs:
+%  input - can be one of:
+%    a structure: matlab midi structure (created by readmidi.m)
+%    a string: a midi filename
+%    other: a 'Notes' matrix (as ouput by midiInfo.m)
+%
+%  synthtype - string to choose synthesis method
+%      passed to synth function in synth.m
+%      current choices are: 'fm', 'sine' or 'saw'
+%      default='fm'
+%
+%  Fs - sampling frequency in Hz (beware of aliasing!)
+%       default =  44.1e3
+
+% Copyright (c) 2009 Ken Schutte
+% more info at: http://www.kenschutte.com/midi
+
+if (nargin<2)
+  Fs=44.1e3;
+end
+if (nargin<3)
+  synthtype='fm';
+end
+
+endtime = -1;
+if (isstruct(input))
+  [Notes,endtime] = midiInfo(input,0);
+elseif (ischar(input))
+  [Notes,endtime] = midiInfo(readmidi(input), 0);
+else
+  Notes = input;
+end
+
+% t2 = 6th col
+if (endtime == -1)
+  endtime = max(Notes(:,6));
+end
+if (length(endtime)>1)
+  endtime = max(endtime);
+end
+
+
+y = zeros(1,ceil(endtime*Fs));
+
+for i=1:size(Notes,1)
+
+  f = midi2freq(Notes(i,3));
+  dur = Notes(i,6) - Notes(i,5);
+  amp = Notes(i,4)/127;
+
+  yt = synth(f, dur, amp, Fs, synthtype);
+
+  n1 = floor(Notes(i,5)*Fs)+1;
+  N = length(yt);  
+
+  % ensure yt is [1,N]:
+  y(n1:n1+N-1) = y(n1:n1+N-1) + reshape(yt,1,[]);
+
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/midi2freq.m	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,12 @@
+function f = midi2freq(m)
+% f = midi2freq(m)
+%     
+% Convert MIDI note number (m=0-127) to 
+% frequency, f,  in Hz
+% (m can also be a vector or matrix)
+%
+
+% Copyright (c) 2009 Ken Schutte
+% more info at: http://www.kenschutte.com/midi
+
+f = (440/32)*2.^((m-9)/12);
--- /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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/piano_roll.m	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,46 @@
+function [PR,t,nn] = piano_roll(Notes,vel,ts)
+%
+% Inputs:
+%  Notes: A 'notes' matrix as returned from midiInfo.m
+%  vel:   (optional) if vel==1, set value to note velocity instead of 1. (default 0)
+%  ts:    (optional) time step of one 'pixel' in seconds (default 0.01)
+%
+% Outputs:
+%  PR:    PR(ni,ti): value at note index ni, time index ti
+%  t:     t(ti):  time value in seconds at time index ti
+%  nn:    nn(ni): note number at note index ti
+%
+%   (i.e. t and nn provide 'real-world units' for PR)
+%
+
+% Copyright (c) 2009 Ken Schutte
+% more info at: http://www.kenschutte.com/midi
+
+if nargin < 2
+  vel = 0;
+end
+if nargin < 3
+  ts = 0.01;
+end
+
+Nnotes = size(Notes,1);
+
+n1 = round(Notes(:,5)/ts)+1;
+n2 = round(Notes(:,6)/ts)+1;
+
+if vel == 0
+  vals = ones(Nnotes,1);
+else
+  vals = Notes(:,4); % velocity
+end
+
+for i=1:Nnotes
+  PR(Notes(i,3), n1(i):n2(i)) = vals(i);
+end
+
+% create quantized time axis:
+t = linspace(0,max(Notes(:,6)),size(PR,2));
+% note axis:
+nn = min(Notes(:,3)):max(Notes(:,3));  
+% truncate to notes used:
+PR = PR(nn,:);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/readmidi.m	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,438 @@
+function midi = readmidi(filename, rawbytes)
+% midi = readmidi(filename, rawbytes)
+% midi = readmidi(filename)
+%
+% Read MIDI file and store in a Matlab structure
+% (use midiInfo.m to see structure detail)
+%
+% Inputs:
+%  filename - input MIDI file
+%  rawbytes - 0 or 1: Include raw bytes in structure
+%             This info is redundant, but can be
+%             useful for debugging. default=0
+%
+
+% Copyright (c) 2009 Ken Schutte
+% more info at: http://www.kenschutte.com/midi
+
+
+if (nargin<2)
+  rawbytes=0;
+end
+
+fid = fopen(filename);
+[A count] = fread(fid,'uint8');
+fclose(fid);
+
+if (rawbytes) midi.rawbytes_all = A; end
+
+% realtime events: status: [F8, FF].  no data bytes
+%clock, undefined, start, continue, stop, undefined, active
+%sensing, systerm reset
+
+% file consists of "header chunk" and "track chunks"
+%   4B  'MThd' (header) or 'MTrk' (track)
+%   4B  32-bit unsigned int = number of bytes in chunk, not
+%       counting these first 8
+
+
+% HEADER CHUNK --------------------------------------------------------
+% 4B 'Mthd'
+% 4B length
+% 2B file format
+%    0=single track, 1=multitrack synchronous, 2=multitrack asynchronous
+%    Synchronous formats start all tracks at the same time, while asynchronous formats can start and end any track at any time during the score.
+% 2B track cout (must be 1 for format 0)
+% 2B num delta-time ticks per quarter note
+%
+
+if ~isequal(A(1:4)',[77 84 104 100])  % double('MThd')
+    error('File does not begin with header ID (MThd)');
+end
+
+header_len = decode_int(A(5:8));
+if (header_len == 6)
+else
+    error('Header length != 6 bytes.');
+end
+
+format = decode_int(A(9:10));
+if (format==0 || format==1 || format==2)
+     midi.format = format;
+else    
+    error('Format does not equal 0,1,or 2');
+end
+
+num_tracks = decode_int(A(11:12));
+if (format==0 && num_tracks~=1)
+    error('File is format 0, but num_tracks != 1');
+end
+
+time_unit = decode_int(A(13:14));
+if (bitand(time_unit,2^15)==0)
+  midi.ticks_per_quarter_note = time_unit;
+else
+  error('Header: SMPTE time format found - not currently supported');
+end
+
+if (rawbytes)
+  midi.rawbytes_header = A(1:14);
+end
+
+% end header parse ----------------------------------------------------
+
+
+
+
+
+
+% BREAK INTO SEPARATE TRACKS ------------------------------------------
+% midi.track(1).data = [byte byte byte ...];
+% midi.track(2).date = ...
+% ...
+%
+% Track Chunks---------
+% 4B 'MTrk'
+% 4B length (after first 8B)
+%
+ctr = 15;
+for i=1:num_tracks
+  
+  if ~isequal(A(ctr:ctr+3)',[77 84 114 107])  % double('MTrk')
+    error(['Track ' num2str(i) ' does not begin with track ID=MTrk']);
+  end
+  ctr = ctr+4;
+  
+  track_len = decode_int(A(ctr:ctr+3));
+  ctr = ctr+4;
+  
+  % have track.rawbytes hold initial 8B also...
+  track_rawbytes{i} = A((ctr-8):(ctr+track_len-1));
+  
+  if (rawbytes)
+    midi.track(i).rawbytes_header = A(ctr-8:ctr-1);
+  end
+  
+  ctr = ctr+track_len;
+end
+% ----------------------------------------------------------------------
+
+
+
+
+
+
+% Events:
+%  - meta events: start with 'FF'
+%  - MIDI events: all others
+
+% MIDI events:
+%  optional command byte + 0,1,or 2 bytes of parameters
+%  "running mode": command byte omitted.
+%
+% all midi command bytes have MSB=1
+% all data for inside midi command have value <= 127 (ie MSB=0)
+% -> so can determine running mode
+% 
+% meta events' data may have any values (meta events have to set
+% len)
+%
+
+
+
+% 'Fn' MIDI commands:
+%  no chan. control the entire system
+%F8 Timing Clock
+%FA start a sequence
+%FB continue a sequence
+%FC stop a sequence
+
+% Meta events:
+%  1B 0xFF
+%  1B event type
+%  1B length of additional data
+%  ?? additional data
+%
+
+
+% "channel mode messages"
+% have same code as "control change": 0xBn
+%  but uses reserved controller numbers 120-127
+%
+
+
+%Midi events consist of an optional command byte 
+% followed by zero, one or two bytes of parameters.
+% In running mode, the command can be omitted, in 
+% which case the last MIDI command specified is 
+% assumed.  The first bit of a command byte is 1, 
+% while the first bit of a parameter is always 0. 
+%   In addition, the last 4 bits of a command 
+%   indicate the channel to which the event should 
+%   be sent; therefore, there are 6 possible 
+%   commands (really 7, but we will discuss the x'Fn' 
+%   commands later) that can be specified.  They are:
+
+
+% parse tracks -----------------------------------------
+for i=1:num_tracks
+  
+  track = track_rawbytes{i};
+
+  if (rawbytes); midi.track(i).rawbytes = track; end
+  
+  msgCtr = 1;
+  ctr=9;  % first 8B were MTrk and length
+  while (ctr < length(track_rawbytes{i}))
+
+    clear currMsg;
+    currMsg.used_running_mode = 0;
+    % note:
+    %  .used_running_mode is necessary only to 
+    %  be able to reconstruct a file _exactly_ from 
+    %  the 'midi' structure.  this is helpful for 
+    %  debugging since write(read(filename)) can be 
+    %  tested for exact replication...
+    %
+    
+    ctr_start_msg = ctr;
+    
+    [deltatime,ctr] = decode_var_length(track, ctr);
+    
+    % ?
+    %if (rawbytes)
+    %  currMsg.rawbytes_deltatime = track(ctr_start_msg:ctr-1);
+    %end
+    
+    % deltaime must be 1-4 bytes long.
+    % could check here...
+    
+    
+    % CHECK FOR META EVENTS ------------------------
+    % 'FF'
+    if track(ctr)==255
+
+      type = track(ctr+1);
+      
+      ctr = ctr+2;
+
+      % get variable length 'length' field
+      [len,ctr] = decode_var_length(track, ctr);
+
+      % note: some meta events have pre-determined lengths...
+      %  we could try verifiying they are correct here.
+
+      thedata = track(ctr:ctr+len-1);
+      chan = [];
+      
+      ctr = ctr + len;      
+
+      midimeta = 0;
+    
+    else 
+      midimeta = 1;
+      % MIDI EVENT ---------------------------
+      
+
+
+      
+      % check for running mode:
+      if (track(ctr)<128)
+
+	% make it re-do last command:
+	%ctr = ctr - 1;
+	%track(ctr) = last_byte;
+	currMsg.used_running_mode = 1;
+	
+	B = last_byte;
+	nB = track(ctr); % ?
+	
+      else
+      
+	B  = track(ctr);
+	nB = track(ctr+1);
+
+	ctr = ctr + 1;
+
+      end
+      
+      % nibbles:
+      %B  = track(ctr);
+      %nB = track(ctr+1);
+
+      
+      Hn = bitshift(B,-4);
+      Ln = bitand(B,15);
+
+      chan = [];
+      
+      msg_type = midi_msg_type(B,nB);
+
+      % DEBUG:
+      if (i==2)
+	  if (msgCtr==1)
+	    disp(msg_type);
+	  end
+      end
+      
+      
+      switch msg_type
+      
+       case 'channel_mode'
+	
+	% UNSURE: if all channel mode messages have 2 data byes (?)
+	type = bitshift(Hn,4) + (nB-120+1);
+	thedata = track(ctr:ctr+1);
+	chan = Ln;
+	
+	ctr = ctr + 2;
+	
+	% ---- channel voice messages:
+       case 'channel_voice'
+	
+	type = bitshift(Hn,4);
+	len = channel_voice_msg_len(type); % var length data:
+	thedata = track(ctr:ctr+len-1);
+	chan = Ln;
+
+	% DEBUG:
+	if (i==2)
+	  if (msgCtr==1)
+	    disp([999  Hn type])
+	  end
+	end
+	
+	ctr = ctr + len;
+	
+       case 'sysex'
+	
+	% UNSURE: do sysex events (F0-F7) have 
+	%  variable length 'length' field?
+	
+	[len,ctr] = decode_var_length(track, ctr);
+	
+	type = B;
+	thedata = track(ctr:ctr+len-1);
+	chan = [];
+	
+	ctr = ctr + len;
+	
+       case 'sys_realtime'
+	
+	% UNSURE: I think these are all just one byte
+	type = B;
+	thedata = [];
+	chan = [];
+	
+      end
+      
+      last_byte = Ln + bitshift(Hn,4);
+      
+    end % end midi event 'if'
+
+    
+    currMsg.deltatime = deltatime;
+    currMsg.midimeta = midimeta;
+    currMsg.type = type;
+    currMsg.data = thedata;
+    currMsg.chan = chan;
+    
+    if (rawbytes)
+      currMsg.rawbytes = track(ctr_start_msg:ctr-1);
+    end
+    
+    midi.track(i).messages(msgCtr) = currMsg;
+    msgCtr = msgCtr + 1;
+
+    
+  end % end loop over rawbytes
+end % end loop over tracks
+
+
+function val=decode_int(A)
+
+val = 0;
+for i=1:length(A)
+  val = val + bitshift(A(length(A)-i+1), 8*(i-1));
+end
+
+
+function len=channel_voice_msg_len(type)
+
+if     (type==128); len=2;
+elseif (type==144); len=2;
+elseif (type==160); len=2;
+elseif (type==176); len=2;
+elseif (type==192); len=1;
+elseif (type==208); len=1;
+elseif (type==224); len=2;
+else
+  disp(type); error('bad channel voice message type');
+end
+
+
+%
+% decode variable length field (often deltatime)
+%
+%  return value and new position of pointer into 'bytes'
+%
+function [val,ptr] = decode_var_length(bytes, ptr)
+
+keepgoing=1;
+binarystring = '';
+while (keepgoing)
+  % check MSB:
+  %  if MSB=1, then delta-time continues into next byte...
+  if(~bitand(bytes(ptr),128))
+    keepgoing=0;
+  end
+  % keep appending last 7 bits from each byte in the deltatime:
+  binbyte = ['00000000' dec2base(bytes(ptr),2)];
+  binarystring = [binarystring binbyte(end-6:end)];
+  ptr=ptr+1;
+end
+val = base2dec(binarystring,2);
+
+
+
+
+%
+% Read first 2 bytes of msg and 
+%  determine the type
+%  (most require only 1st byte)
+%
+% str is one of:
+%  'channel_mode'
+%  'channel_voice'
+%  'sysex'
+%  'sys_realtime'
+%
+function str=midi_msg_type(B,nB)
+
+Hn = bitshift(B,-4);
+Ln = bitand(B,7);
+
+% ---- channel mode messages:
+%if (Hn==11 && nB>=120 && nB<=127)
+if (Hn==11 && nB>=122 && nB<=127)
+  str = 'channel_mode';
+
+  % ---- channel voice messages:
+elseif (Hn>=8 && Hn<=14)
+  str = 'channel_voice';
+  
+  %  ---- sysex events:
+elseif (Hn==15 && Ln>=0 && Ln<=7)
+  str = 'sysex';
+
+  % system real-time messages
+elseif (Hn==15 && Ln>=8 && Ln<=15)
+  % UNSURE: how can you tell between 0xFF system real-time
+  %   message and 0xFF meta event?
+  %   (now, it will always be processed by meta)
+  str = 'sys_realtime';
+  
+else
+  % don't think it can get here...
+  error('bad midi message');
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/synth.m	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,64 @@
+function y=synth(freq,dur,amp,Fs,type)
+% y=synth(freq,dur,amp,Fs,type)
+%
+% Synthesize a single note
+%
+% Inputs:
+%  freq - frequency in Hz
+%  dur - duration in seconds
+%  amp - Amplitude in range [0,1]
+%  Fs -  sampling frequency in Hz
+%  type - string to select synthesis type
+%         current options: 'fm', 'sine', or 'saw'
+
+% Copyright (c) 2009 Ken Schutte
+% more info at: http://www.kenschutte.com/midi
+
+if nargin<5
+  error('Five arguments required for synth()');
+end
+
+N = floor(dur*Fs);
+
+if N == 0
+  warning('Note with zero duration.');
+  y = [];
+  return;
+
+elseif N < 0
+  warning('Note with negative duration. Skipping.');
+  y = [];
+  return;
+end
+
+n=0:N-1;
+if (strcmp(type,'sine'))
+  y = amp.*sin(2*pi*n*freq/Fs);
+
+elseif (strcmp(type,'saw'))
+
+  T = (1/freq)*Fs;     % period in fractional samples
+  ramp = (0:(N-1))/T;
+  y = ramp-fix(ramp);
+  y = amp.*y;
+  y = y - mean(y);
+
+elseif (strcmp(type,'fm'))
+
+  t = 0:(1/Fs):dur;
+  envel = interp1([0 dur/6 dur/3 dur/5 dur], [0 1 .75 .6 0], 0:(1/Fs):dur);
+  I_env = 5.*envel;
+  y = envel.*sin(2.*pi.*freq.*t + I_env.*sin(2.*pi.*freq.*t));
+  
+else
+  error('Unknown synthesis type');
+end
+
+% smooth edges w/ 10ms ramp
+if (dur > .02)
+  L = 2*fix(.01*Fs)+1;  % L odd
+  ramp = bartlett(L)';  % odd length
+  L = ceil(L/2);
+  y(1:L) = y(1:L) .* ramp(1:L);
+  y(end-L+1:end) = y(end-L+1:end) .* ramp(end-L+1:end);
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/matlab_midi/writemidi.m	Mon Mar 28 17:35:01 2011 +0100
@@ -0,0 +1,143 @@
+function rawbytes=writemidi(midi,filename,do_run_mode)
+% rawbytes=writemidi(midi,filename,do_run_mode)
+%
+% writes to a midi file
+%
+% midi is a structure like that created by readmidi.m
+%
+% do_run_mode: flag - use running mode when possible.
+%    if given, will override the msg.used_running_mode
+%    default==0.  (1 may not work...)
+%
+% TODO: use note-on for note-off... (for other function...)
+%
+
+% Copyright (c) 2009 Ken Schutte
+% more info at: http://www.kenschutte.com/midi
+
+
+%if (nargin<3)
+do_run_mode = 0;
+%end
+
+
+% do each track:
+Ntracks = length(midi.track);
+
+for i=1:Ntracks
+
+  databytes_track{i} = [];
+  
+  for j=1:length(midi.track(i).messages)
+
+    msg = midi.track(i).messages(j);
+
+    msg_bytes = encode_var_length(msg.deltatime);
+
+    if (msg.midimeta==1)
+
+      % check for doing running mode
+      run_mode = 0;
+      run_mode = msg.used_running_mode;
+      
+      % should check that prev msg has same type to allow run
+      % mode...
+      
+      
+      %      if (j>1 && do_run_mode && msg.type == midi.track(i).messages(j-1).type)
+%	run_mode = 1;
+%      end
+
+
+msg_bytes = [msg_bytes; encode_midi_msg(msg, run_mode)];
+    
+    
+    else
+      
+      msg_bytes = [msg_bytes; encode_meta_msg(msg)];
+      
+    end
+
+%    disp(msg_bytes')
+
+%if (msg_bytes ~= msg.rawbytes)
+%  error('rawbytes mismatch');
+%end
+
+    databytes_track{i} = [databytes_track{i}; msg_bytes];
+    
+  end
+end 
+
+
+% HEADER
+% double('MThd') = [77 84 104 100]
+rawbytes = [77 84 104 100 ...
+	    0 0 0 6 ...
+	    encode_int(midi.format,2) ...
+	    encode_int(Ntracks,2) ...
+	    encode_int(midi.ticks_per_quarter_note,2) ...
+	   ]';
+
+% TRACK_CHUCKS
+for i=1:Ntracks
+  a = length(databytes_track{i});
+  % double('MTrk') = [77 84 114 107]
+  tmp = [77 84 114 107 ...
+	 encode_int(length(databytes_track{i}),4) ...
+	 databytes_track{i}']';
+  rawbytes(end+1:end+length(tmp)) = tmp;
+end
+
+
+% write to file
+fid = fopen(filename,'w');
+%fwrite(fid,rawbytes,'char');
+fwrite(fid,rawbytes,'uint8');
+fclose(fid);
+
+% return a _column_ vector
+function A=encode_int(val,Nbytes)
+
+for i=1:Nbytes
+  A(i) = bitand(bitshift(val, -8*(Nbytes-i)), 255);
+end
+
+
+function bytes=encode_var_length(val)
+
+binStr = dec2base(round(val),2);
+Nbytes = ceil(length(binStr)/7);
+
+binStr = ['00000000' binStr];
+bytes = [];
+for i=1:Nbytes
+  if (i==1)
+    lastbit = '0';
+  else
+    lastbit = '1';
+  end
+  B = bin2dec([lastbit binStr(end-i*7+1:end-(i-1)*7)]);
+  bytes = [B; bytes];
+end
+
+
+function bytes=encode_midi_msg(msg, run_mode)
+
+bytes = [];
+
+if (run_mode ~= 1)
+  bytes = msg.type;
+  % channel:
+  bytes = bytes + msg.chan;  % lower nibble should be chan
+end
+
+bytes = [bytes; msg.data];
+
+function bytes=encode_meta_msg(msg)
+
+bytes = 255;
+bytes = [bytes; msg.type];
+bytes = [bytes; encode_var_length(length(msg.data))];
+bytes = [bytes; msg.data];
+