function midinote(FileName)
%MIDINOTE Extract MIDI notelist from text-format MIDI file
%	MIDINOTE('fname') extracts MIDI note-on and note-off events
% from the text-format MIDI file specified by the file name
% 'fname'. NOTE: Only supply the base filename; MIDINOTE assumes
% that the file has the standard text file extension '.txt'.
%
% The events are stored in a notelist matrix called 'Notes'; an 
% additional 16x1 vector 'NumNotes' indicates the number of note events
% contained in each channel of the notelist. The 'Notes' matrix is
% organized as a 3-D matrix. Each row of the matrix consists of
% note-on time, duration (both in seconds), note number, and velocity.
% The remaining dimension indicates MIDI channel number.
% 'Notes' is an Nx4x16 matrix, where N indicates the maximum number of
% note events found in any of the 16 MIDI channels.
%
% MIDINOTE saves the 'Notes' and 'NumNotes' matrices to 'fname.mat'.
% These can be retrieved later using 'load'. It is only necessary
% to run MIDINOTE one time per MIDI file.

%  Created 2000 Jan 10
%  Ed.Doering@Rose-Hulman.Edu
%  Department of Electrical and Computer Engineering
%  Rose-Hulman Institute of Technology
%  Terre Haute, IN 47803-3999
%
%  Copyright (c) 2000 by Ed Doering.

%
%%% User inputs:
%


%
%%% Read and parse the text-format MIDI file to obtain note-on and note-off events:
%

% Name of MIDI text file with extension added
fname = [FileName '.txt'];

% Name of this file
mname = 'MIDINOTE: ';

% Default Tempo
Tempo = 500000;

% Initialize the note-on and note-off arrays
NoteOn = [];
NoteOff = [];

% Open the .TXT file in text read mode
[fid emessage] = fopen(fname,'rt');
if fid == -1
    disp([mname, emessage])
    return
end

% Read the header chunk ID; verify that first four characters
% are as expected
s=fscanf(fid,'%s',1);
if (s ~= 'MFile')
   disp([mname, '"' fname '" is not a text-format MIDI file']);
   disp([mname, '   (expected first five characters to be "MFile"']);
   return
end

% Read the file type, number of tracks, and division
FileType = fscanf(fid,'%d',1);
NumTracks = fscanf(fid,'%d',1);
Division = fscanf(fid,'%d',1);

% Read all the tracks
for k=1:NumTracks
   
   % Read the track chunk ID; verify that characters are correct
   s=fscanf(fid,'%s',1);
	 if (s ~= 'MTrk')
  		 disp([mname, '"' fname '" is garbled (1)']);
   	 return
    end
    
    % Set flag to indicate when finished reading track
    TrackDone = 0;
    
    while (TrackDone ~= 1)
    % Read delta time string; test if it represents end of track
    s=fscanf(fid,'%s',1);
    if (isletter(s(1)))
       % Appears to be a track end label
       if (s == 'TrkEnd')
          disp([mname, 'Finished reading Track ',num2str(k),' of ',num2str(NumTracks)])
          TrackDone = 1;
       else 
          disp([mname, '"' fname '" is garbled (2)']);
       end
    else
       % Convert string to delta time
       DeltaTime = str2num(s);
       
       % Read the MIDI event name
       MidiEventName = fscanf(fid,'%s',1);
       
       % Read the MIDI event data
	     s = fgetl(fid);

       % Parse the MIDI event
       switch (MidiEventName)
       case 'TimeSig'
          %disp('TimeSig event') 
       case 'SeqSpec'
          %disp('SeqSpec event')
       case 'Tempo'
          %disp('Tempo event')
          Tempo = sscanf(s,'%d',1);

       case 'Meta'
          %disp('Meta event')
       case 'On'
          %disp('Note on event')
          %disp(s)
          % Read channel
          loc=findstr(s,'ch=');
          MidiChannel = sscanf(s(loc+3:length(s)),'%d');
          
          % Read note number
          loc=findstr(s,'n=');
          NoteNumber = sscanf(s(loc+2:length(s)),'%d');
          
          % Read velocity
          loc=findstr(s,'v=');
          Velocity = sscanf(s(loc+2:length(s)),'%d');
          
          % Append to note-on array if velocity is nonzero, otherwise
          % append to note-off array
          if (Velocity ~= 0)
             NoteOn = [NoteOn; [DeltaTime MidiChannel NoteNumber Velocity]];
          else
             NoteOff = [NoteOff; [DeltaTime MidiChannel NoteNumber Velocity]];
          end
          
       case 'Off'
          %disp('Note off event')
          %disp(s)
          % Read channel
          loc=findstr(s,'ch=');
          MidiChannel = sscanf(s(loc+3:length(s)),'%d');
          
          % Read note number
          loc=findstr(s,'n=');
          NoteNumber = sscanf(s(loc+2:length(s)),'%d');
          
          % Read velocity
          loc=findstr(s,'v=');
          Velocity = sscanf(s(loc+2:length(s)),'%d');
          
          % Append to note-on array
          NoteOff = [NoteOff; [DeltaTime MidiChannel NoteNumber Velocity]];

       end % of switch
       
    end % of if
    
    end % of while
    
end

% Close the file
fclose(fid);

% Determine the number of events
NumOnEvents = size(NoteOn,1);
NumOffEvents = size(NoteOff,1);

% Generate a warning if the number of 'on' and 'off' events are not the same
if (NumOnEvents ~= NumOffEvents)
   disp([mname, 'Number of "ON" events is not the same as the number of "OFF" events']);
   NumOnEvents
   NumOffEvents
end


%
%%% Create a note list:
%

% Allocate space for note list:
%   one row for each time/duration/channel/notenum/velocity
NoteList = zeros(NumOnEvents,5);
NoteListPointer = 1;

% Allocate space for note events counter
NumNotes = zeros(16,1);

% Step through each channel
for k=1:16
   disp([mname, 'Processing MIDI channel ' num2str(k) ' of 16'])
   
   % Initialize note event counter
   NumNoteEvents = 0;
   
   % Collect note-on and note-off events on MIDI channel k
   NoteOnK = NoteOn(find(NoteOn(:,2)==k),:);
   NoteOffK = NoteOff(find(NoteOff(:,2)==k),:);
   
   % Sort the events according to note number
   [tmp,SortIndex]=sort(NoteOnK(:,3));
   NoteOnK = NoteOnK(SortIndex,:);
   [tmp,SortIndex]=sort(NoteOffK(:,3));
   NoteOffK = NoteOffK(SortIndex,:);
   
   % Insert results into note list
   for j=1:size(NoteOnK,1)
      NoteList(NoteListPointer,:)=...
      [NoteOnK(j,1) NoteOffK(j,1)-NoteOnK(j,1) k NoteOnK(j,3) NoteOnK(j,4)];
      NoteListPointer = NoteListPointer + 1;
      NumNoteEvents = NumNoteEvents + 1;
   end
   
   % Store number of note events found
   NumNotes(k) = NumNoteEvents;
   
   % Display number of note events found
   disp([mname, '   (found ', num2str(NumNoteEvents), ' note events)'])
end

% Sort the notelist in ascending order of time
[tmp,SortIndex]=sort(NoteList(:,1));
NoteList = NoteList(SortIndex,:);

% Calibrate the time columns in seconds
% Note: Time scaling works like this:
% time [s] = deltatime [tics] X (1/div) [qnote/tic] X tempo [us/qnote] X 10^-6 [s/us]
NoteList(:,1) = NoteList(:,1)*(1/Division)*Tempo*10^-6;
NoteList(:,2) = NoteList(:,2)*(1/Division)*Tempo*10^-6;

% Allocate space for notelist separated out according to MIDI channel
Notes = zeros(max(NumNotes),4,16);

% Extract notelist for each MIDI channel; also omit channel number from each
% individual note list
for k = 1:16
   Notes(1:NumNotes(k),:,k) = NoteList(find(NoteList(:,3)==k),[1 2 4 5]);
end

% Save notelist and number of notes array to a .MAT file
% (use 'load filename' to retrieve)
save(FileName,'Notes','NumNotes');

% Notify user about creation of .mat file
disp([mname, FileName,'.mat has been created.'])

return
