diff util/matlab_midi/readmidi.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/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