├── README ├── src ├── getTempoChanges.m ├── matrix2midi.m ├── midi2audio.m ├── midi2freq.m ├── midiInfo.m ├── pianoRoll2matrix.m ├── piano_roll.m ├── readmidi.m ├── synth.m └── writemidi.m └── tests ├── example.m └── midi └── jesu.mid /README: -------------------------------------------------------------------------------- 1 | 2 | MIDI file tools for MATLAB 3 | 4 | 5 | USAGE 6 | 7 | Add the directory 'matlab-midi/src' to your Matlab path. 8 | Basic read: 9 | 10 | Basic write: 11 | 12 | For full documentation, see http://kenschutte.com/midi 13 | 14 | LICENSE 15 | 16 | This code is released under the GPL. 17 | 18 | AUTHOR 19 | 20 | Ken Schutte 21 | kenschutte@gmail.com 22 | -------------------------------------------------------------------------------- /src/getTempoChanges.m: -------------------------------------------------------------------------------- 1 | function [tempos,tempos_time]=getTempoChanges(midi) 2 | % [tempos,tempos_time]=getTempoChanges(midi) 3 | % 4 | % input: a midi struct from readmidi.m 5 | % output: 6 | % tempos = tempo values indexed by tempos_time 7 | % tempos_time is in units of ticks 8 | % 9 | % should tempo changes effect across tracks? across channels? 10 | % 11 | 12 | % Copyright (c) 2009 Ken Schutte 13 | % more info at: http://www.kenschutte.com/midi 14 | 15 | tempos = []; 16 | tempos_time = []; 17 | for i=1:length(midi.track) 18 | cumtime=0; 19 | for j=1:length(midi.track(i).messages) 20 | cumtime = cumtime+midi.track(i).messages(j).deltatime; 21 | % if (strcmp(midi.track(i).messages(j).name,'Set Tempo')) 22 | if (midi.track(i).messages(j).midimeta==0 && midi.track(i).messages(j).type==81) 23 | tempos_time(end+1) = cumtime; 24 | d = midi.track(i).messages(j).data; 25 | tempos(end+1) = d(1)*16^4 + d(2)*16^2 + d(3); 26 | end 27 | end 28 | end 29 | 30 | if numel(tempos)==0 31 | tempos = 500000; % default value for midi 32 | tempos_time = 0; 33 | end 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/matrix2midi.m: -------------------------------------------------------------------------------- 1 | function midi=matrix2midi(M,ticks_per_quarter_note,timesig) 2 | % midi=matrix2midi(M,ticks_per_quarter_note) 3 | % 4 | % generates a midi matlab structure from a matrix 5 | % specifying a list of notes. The structure output 6 | % can then be used by writemidi.m 7 | % 8 | % M: input matrix: 9 | % 1 2 3 4 5 6 10 | % [track chan nn vel t1 t2] (any more cols ignored...) 11 | % 12 | % optional arguments: 13 | % - ticks_per_quarter_note: integer (default 300) 14 | % - timesig: a vector of len 4 (default [4,2,24,8]) 15 | % 16 | 17 | % Copyright (c) 2009 Ken Schutte 18 | % more info at: http://www.kenschutte.com/midi 19 | 20 | % TODO options: 21 | % - note-off vs vel=0 22 | % - tempo, ticks, etc 23 | 24 | if nargin < 2 25 | ticks_per_quarter_note = 300; 26 | end 27 | 28 | if nargin < 3 29 | timesig = [4,2,24,8]; 30 | end 31 | 32 | tracks = unique(M(:,1)); 33 | Ntracks = length(tracks); 34 | 35 | % start building 'midi' struct 36 | 37 | if (Ntracks==1) 38 | midi.format = 0; 39 | else 40 | midi.format = 1; 41 | end 42 | 43 | midi.ticks_per_quarter_note = ticks_per_quarter_note; 44 | 45 | tempo = 500000; % could be set by user, etc... 46 | % (microsec per quarter note) 47 | 48 | for i=1:Ntracks 49 | 50 | trM = M(tracks(i)==M(:,1),:); 51 | 52 | note_events_onoff = []; 53 | note_events_n = []; 54 | note_events_ticktime = []; 55 | 56 | % gather all the notes: 57 | for j=1:size(trM,1) 58 | % note on event: 59 | note_events_onoff(end+1) = 1; 60 | note_events_n(end+1) = j; 61 | note_events_ticktime(end+1) = 1e6 * trM(j,5) * ticks_per_quarter_note / tempo; 62 | 63 | % note off event: 64 | note_events_onoff(end+1) = 0; 65 | note_events_n(end+1) = j; 66 | note_events_ticktime(end+1) = 1e6 * trM(j,6) * ticks_per_quarter_note / tempo; 67 | end 68 | 69 | 70 | msgCtr = 1; 71 | 72 | % set tempo... 73 | midi.track(i).messages(msgCtr).deltatime = 0; 74 | midi.track(i).messages(msgCtr).type = 81; 75 | midi.track(i).messages(msgCtr).midimeta = 0; 76 | midi.track(i).messages(msgCtr).data = encode_int(tempo,3); 77 | midi.track(i).messages(msgCtr).chan = []; 78 | msgCtr = msgCtr + 1; 79 | 80 | % set time sig... 81 | midi.track(i).messages(msgCtr).deltatime = 0; 82 | midi.track(i).messages(msgCtr).type = 88; 83 | midi.track(i).messages(msgCtr).midimeta = 0; 84 | midi.track(i).messages(msgCtr).data = timesig(:); 85 | midi.track(i).messages(msgCtr).chan = []; 86 | msgCtr = msgCtr + 1; 87 | 88 | [junk,ord] = sort(note_events_ticktime); 89 | 90 | prevtick = 0; 91 | for j=1:length(ord) 92 | 93 | n = note_events_n(ord(j)); 94 | cumticks = note_events_ticktime(ord(j)); 95 | 96 | midi.track(i).messages(msgCtr).deltatime = cumticks - prevtick; 97 | midi.track(i).messages(msgCtr).midimeta = 1; 98 | midi.track(i).messages(msgCtr).chan = trM(n,2); 99 | midi.track(i).messages(msgCtr).used_running_mode = 0; 100 | 101 | if (note_events_onoff(ord(j))==1) 102 | % note on: 103 | midi.track(i).messages(msgCtr).type = 144; 104 | midi.track(i).messages(msgCtr).data = [trM(n,3); trM(n,4)]; 105 | else 106 | %-- note off msg: 107 | %midi.track(i).messages(msgCtr).type = 128; 108 | %midi.track(i).messages(msgCtr).data = [trM(n,3); trM(n,4)]; 109 | %-- note on vel=0: 110 | midi.track(i).messages(msgCtr).type = 144; 111 | midi.track(i).messages(msgCtr).data = [trM(n,3); 0]; 112 | end 113 | msgCtr = msgCtr + 1; 114 | 115 | prevtick = cumticks; 116 | end 117 | 118 | % end of track: 119 | midi.track(i).messages(msgCtr).deltatime = 0; 120 | midi.track(i).messages(msgCtr).type = 47; 121 | midi.track(i).messages(msgCtr).midimeta = 0; 122 | midi.track(i).messages(msgCtr).data = []; 123 | midi.track(i).messages(msgCtr).chan = []; 124 | msgCtr = msgCtr + 1; 125 | 126 | end 127 | 128 | 129 | % return a _column_ vector 130 | % (copied from writemidi.m) 131 | function A=encode_int(val,Nbytes) 132 | 133 | A = zeros(Nbytes,1); %ensure col vector (diff from writemidi.m...) 134 | for i=1:Nbytes 135 | A(i) = bitand(bitshift(val, -8*(Nbytes-i)), 255); 136 | end 137 | 138 | -------------------------------------------------------------------------------- /src/midi2audio.m: -------------------------------------------------------------------------------- 1 | function [y,Fs]=midi2audio(input,Fs,synthtype) 2 | % y = midi2audio(input, Fs, synthtype) 3 | % y = midi2audio(input, Fs) 4 | % y = midi2audio(input) 5 | % 6 | % Convert midi structure to a digital waveform 7 | % 8 | % Inputs: 9 | % input - can be one of: 10 | % a structure: matlab midi structure (created by readmidi.m) 11 | % a string: a midi filename 12 | % other: a 'Notes' matrix (as ouput by midiInfo.m) 13 | % 14 | % synthtype - string to choose synthesis method 15 | % passed to synth function in synth.m 16 | % current choices are: 'fm', 'sine' or 'saw' 17 | % default='fm' 18 | % 19 | % Fs - sampling frequency in Hz (beware of aliasing!) 20 | % default = 44.1e3 21 | 22 | % Copyright (c) 2009 Ken Schutte 23 | % more info at: http://www.kenschutte.com/midi 24 | 25 | if (nargin<2) 26 | Fs=44.1e3; 27 | end 28 | if (nargin<3) 29 | synthtype='fm'; 30 | end 31 | 32 | endtime = -1; 33 | if (isstruct(input)) 34 | [Notes,endtime] = midiInfo(input,0); 35 | elseif (ischar(input)) 36 | [Notes,endtime] = midiInfo(readmidi(input), 0); 37 | else 38 | Notes = input; 39 | end 40 | 41 | % t2 = 6th col 42 | if (endtime == -1) 43 | endtime = max(Notes(:,6)); 44 | end 45 | if (length(endtime)>1) 46 | endtime = max(endtime); 47 | end 48 | 49 | 50 | y = zeros(1,ceil(endtime*Fs)); 51 | 52 | for i=1:size(Notes,1) 53 | 54 | f = midi2freq(Notes(i,3)); 55 | dur = Notes(i,6) - Notes(i,5); 56 | amp = Notes(i,4)/127; 57 | 58 | yt = synth(f, dur, amp, Fs, synthtype); 59 | 60 | n1 = floor(Notes(i,5)*Fs)+1; 61 | N = length(yt); 62 | 63 | n2 = n1 + N - 1; 64 | 65 | % hack: for some examples (6246525.midi), one yt 66 | % extended past endtime (just by one sample in this case) 67 | % todo: check why that was the case. For now, just truncate, 68 | if (n2 > length(y)) 69 | ndiff = n2 - length(y); 70 | % 71 | yt = yt(1:(end-ndiff)); 72 | n2 = n2 - ndiff; 73 | end 74 | 75 | % ensure yt is [1,N]: 76 | y(n1:n2) = y(n1:n2) + reshape(yt,1,[]); 77 | 78 | end 79 | -------------------------------------------------------------------------------- /src/midi2freq.m: -------------------------------------------------------------------------------- 1 | function f = midi2freq(m) 2 | % f = midi2freq(m) 3 | % 4 | % Convert MIDI note number (m=0-127) to 5 | % frequency, f, in Hz 6 | % (m can also be a vector or matrix) 7 | % 8 | 9 | % Copyright (c) 2009 Ken Schutte 10 | % more info at: http://www.kenschutte.com/midi 11 | 12 | f = (440/32)*2.^((m-9)/12); 13 | -------------------------------------------------------------------------------- /src/midiInfo.m: -------------------------------------------------------------------------------- 1 | function [Notes,endtime] = midiInfo(midi,outputFormat,tracklist,verbose) 2 | % [Notes,endtime] = midiInfo(midi,outputFormat,tracklist) 3 | % 4 | % Takes a midi structre and generates info on notes and messages 5 | % Can return a matrix of note parameters and/or output/display 6 | % formatted table of messages 7 | % 8 | % Inputs: 9 | % midi - Matlab structure (created by readmidi.m) 10 | % tracklist - which tracks to show ([] for all) 11 | % outputFormat 12 | % - if it's a string write the formated output to the file 13 | % - if 0, don't display or write formatted output 14 | % - if 1, just display (default) 15 | % 16 | % outputs: 17 | % Notes - a matrix containing a list of notes, ordered by start time 18 | % column values are: 19 | % 1 2 3 4 5 6 7 8 20 | % [track chan nn vel t1 t2 msgNum1 msgNum2] 21 | % endtime - time of end of track message 22 | % 23 | 24 | % Copyright (c) 2009 Ken Schutte 25 | % more info at: http://www.kenschutte.com/midi 26 | if nargin<4 27 | verbose = 0; 28 | end 29 | if nargin<3 30 | tracklist=[]; 31 | if nargin<2 32 | outputFormat=1; 33 | end 34 | end 35 | if (isempty(tracklist)) 36 | tracklist = 1:length(midi.track); 37 | end 38 | 39 | current_tempo = 500000; % default tempo 40 | 41 | 42 | [tempos, tempos_time] = getTempoChanges(midi); 43 | 44 | % What to do if no tempos are given? 45 | % This seems at leat get things to work (see eire01.mid) 46 | if length(tempos) == 0 47 | tempos = [current_tempo]; 48 | tempos_time = [0]; 49 | end 50 | 51 | 52 | fid = -1; 53 | if (ischar(outputFormat)) 54 | fid = fopen(outputFormat,'w'); 55 | end 56 | 57 | endtime = -1; 58 | 59 | % each row: 60 | % 1 2 3 4 5 6 7 8 61 | % [track chan nn vel t1 t2 msgNum1 msgNum2] 62 | Notes = zeros(0,8); 63 | 64 | for i=1:length(tracklist) 65 | tracknum = tracklist(i); 66 | 67 | cumtime=0; 68 | seconds=0; 69 | 70 | Msg = cell(0); 71 | Msg{1,1} = 'chan'; 72 | Msg{1,2} = 'deltatime'; 73 | Msg{1,3} = 'time'; 74 | Msg{1,4} = 'name'; 75 | Msg{1,5} = 'data'; 76 | 77 | for msgNum=1:length(midi.track(tracknum).messages) 78 | 79 | currMsg = midi.track(tracknum).messages(msgNum); 80 | 81 | midimeta = currMsg.midimeta; 82 | deltatime = currMsg.deltatime; 83 | data = currMsg.data; 84 | type = currMsg.type; 85 | chan = currMsg.chan; 86 | 87 | cumtime = cumtime + deltatime; 88 | seconds = seconds + deltatime*1e-6*current_tempo/midi.ticks_per_quarter_note; 89 | 90 | [mx ind] = max(find(cumtime >= tempos_time)); 91 | if numel(ind)>0 % if only we found smth 92 | current_tempo = tempos(ind); 93 | else 94 | if verbose 95 | disp('No tempos_time found?'); 96 | end 97 | end 98 | 99 | % find start/stop of notes: 100 | % if (strcmp(name,'Note on') && (data(2)>0)) 101 | % note on with vel>0: 102 | if (midimeta==1 && type==144 && data(2)>0) 103 | % note on: 104 | Notes(end+1,:) = [tracknum chan data(1) data(2) seconds 0 msgNum -1]; 105 | % elseif ((strcmp(name,'Note on') && (data(2)==0)) || strcmp(name,'Note off')) 106 | % note on with vel==0 or note off: 107 | elseif (midimeta==1 && ( (type==144 && data(2)==0) || type==128 )) 108 | 109 | % note off: 110 | % % find index, wther tr,chan,and nn match, and not complete 111 | 112 | ind = find((... 113 | (Notes(:,1)==tracknum) + ... 114 | (Notes(:,2)==chan) + ... 115 | (Notes(:,3)==data(1)) + ... 116 | (Notes(:,8)==-1)... 117 | )==4); 118 | 119 | if (length(ind)==0) 120 | %% was an error before; change to warning and ignore the message. 121 | if verbose 122 | warning('ending non-open note?'); 123 | end 124 | 125 | else 126 | if (length(ind)>1) 127 | %% ??? not sure about this... 128 | %disp('warning: found mulitple matches in endNote, taking first...'); 129 | %% should we take first or last? should we give a warning? 130 | ind = ind(1); 131 | end 132 | 133 | % set info on ending: 134 | Notes(ind,6) = seconds; 135 | Notes(ind,8) = msgNum; 136 | 137 | end 138 | 139 | 140 | % end of track: 141 | elseif (midimeta==0 && type==47) 142 | if (endtime == -1) 143 | endtime = seconds; 144 | else 145 | if verbose 146 | disp('two "end of track" messages?'); 147 | end 148 | endtime(end+1) = seconds; 149 | end 150 | 151 | 152 | end 153 | 154 | % we could check to make sure it ends with 155 | % 'end of track' 156 | 157 | 158 | if (outputFormat ~= 0) 159 | % get some specific descriptions: 160 | name = num2str(type); 161 | dataStr = num2str(data); 162 | 163 | if (isempty(chan)) 164 | Msg{msgNum,1} = '-'; 165 | else 166 | Msg{msgNum,1} = num2str(chan); 167 | end 168 | 169 | Msg{msgNum,2} = num2str(deltatime); 170 | Msg{msgNum,3} = formatTime(seconds); 171 | 172 | if (midimeta==0) 173 | Msg{msgNum,4} = 'meta'; 174 | else 175 | Msg{msgNum,4} = ''; 176 | end 177 | 178 | [name,dataStr] = getMsgInfo(midimeta, type, data); 179 | Msg{msgNum,5} = name; 180 | Msg{msgNum,6} = dataStr; 181 | end 182 | 183 | end %% end track. 184 | 185 | %% any note-on that are not turned off? 186 | nleft = sum(Notes(:,8)==-1); 187 | if (nleft > 0) 188 | %warning(sprintf('%d notes needed to be turned off at end of track.', nleft)); 189 | Notes(Notes(:,8) == -1, 6) = seconds; 190 | end 191 | 192 | if (outputFormat ~= 0) 193 | printTrackInfo(Msg,tracknum,fid); 194 | end 195 | 196 | end 197 | 198 | % make this an option!!! 199 | % - I'm not sure why it's needed... 200 | % remove start silence: 201 | first_t = min(Notes(:,5)); 202 | Notes(:,5) = Notes(:,5) - first_t; 203 | Notes(:,6) = Notes(:,6) - first_t; 204 | 205 | % sort Notes by start time: 206 | [junk,ord] = sort(Notes(:,5)); 207 | Notes = Notes(ord,:); 208 | 209 | 210 | if (fid ~= -1) 211 | fclose(fid); 212 | end 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | function printTrackInfo(Msg,tracknum,fid) 225 | 226 | 227 | % make cols same length instead of just using \t 228 | for i=1:size(Msg,2) 229 | maxLen(i)=0; 230 | for j=1:size(Msg,1) 231 | if (length(Msg{j,i})>maxLen(i)) 232 | maxLen(i) = length(Msg{j,i}); 233 | end 234 | end 235 | end 236 | 237 | 238 | s=''; 239 | s=[s sprintf('--------------------------------------------------\n')]; 240 | s=[s sprintf('Track %d\n',tracknum)]; 241 | s=[s sprintf('--------------------------------------------------\n')]; 242 | 243 | if (fid == -1) 244 | disp(s) 245 | else 246 | fprintf(fid,'%s',s); 247 | end 248 | 249 | 250 | for i=1:size(Msg,1) 251 | line=''; 252 | for j=1:size(Msg,2) 253 | sp = repmat(' ',1,5+maxLen(j)-length(Msg{i,j})); 254 | m = Msg{i,j}; 255 | m = m(:)'; % ensure column vector 256 | % line = [line Msg{i,j} sp]; 257 | line = [line m sp]; 258 | end 259 | 260 | if (fid == -1) 261 | disp(line) 262 | else 263 | fprintf(fid,'%s\n',line); 264 | end 265 | 266 | end 267 | 268 | 269 | 270 | function s=formatTime(seconds) 271 | 272 | minutes = floor(seconds/60); 273 | secs = seconds - 60*minutes; 274 | 275 | s = sprintf('%d:%2.3f',minutes,secs); 276 | 277 | 278 | 279 | function [name,dataStr]=getMsgInfo(midimeta, type, data); 280 | 281 | % meta events: 282 | if (midimeta==0) 283 | if (type==0); name = 'Sequence Number'; len=2; dataStr = num2str(data); 284 | elseif (type==1); name = 'Text Events'; len=-1; dataStr = char(data); 285 | elseif (type==2); name = 'Copyright Notice'; len=-1; dataStr = char(data); 286 | elseif (type==3); name = 'Sequence/Track Name'; len=-1; dataStr = char(data); 287 | elseif (type==4); name = 'Instrument Name'; len=-1; dataStr = char(data); 288 | elseif (type==5); name = 'Lyric'; len=-1; dataStr = char(data); 289 | elseif (type==6); name = 'Marker'; len=-1; dataStr = char(data); 290 | elseif (type==7); name = 'Cue Point'; len=-1; dataStr = char(data); 291 | elseif (type==32); name = 'MIDI Channel Prefix'; len=1; dataStr = num2str(data); 292 | elseif (type==47); name = 'End of Track'; len=0; dataStr = ''; 293 | elseif (type==81); name = 'Set Tempo'; len=3; 294 | val = data(1)*16^4+data(2)*16^2+data(3); dataStr = ['microsec per quarter note: ' num2str(val)]; 295 | elseif (type==84); name = 'SMPTE Offset'; len=5; 296 | dataStr = ['[hh;mm;ss;fr;ff]=' mat2str(data)]; 297 | elseif (type==88); name = 'Time Signature'; len=4; 298 | dataStr = [num2str(data(1)) '/' num2str(data(2)) ', clock ticks and notated 32nd notes=' num2str(data(3)) '/' num2str(data(4))]; 299 | elseif (type==89); name = 'Key Signature'; len=2; 300 | % num sharps/flats (flats negative) 301 | % but data(1) is unsigned 8-bit 302 | if (data(1)<=7) 303 | % 0 1 2 3 4 5 6 7 304 | ss={'C','G','D', 'A', 'E','B', 'F#', 'C#'}; 305 | dataStr = ss{data(1)+1}; 306 | elseif (data(1)>=249) 307 | % 1 2 3 4 5 6 7 308 | % 255 ... 249 309 | ss={'F','Bb','Eb','Ab','Db','Gb','Cb'}; 310 | dataStr = ss{255-data(1)+1}; 311 | else 312 | dataStr = '?'; 313 | end 314 | if (data(2)==0) 315 | dataStr = [dataStr ' Major']; 316 | else 317 | dataStr = [dataStr ' Minor']; 318 | end 319 | 320 | elseif (type==89); name = 'Sequencer-Specific Meta-event'; len=-1; 321 | dataStr = char(data); 322 | % !! last two conflict... 323 | 324 | else 325 | name = ['UNKNOWN META EVENT: ' num2str(type)]; dataStr = num2str(data); 326 | end 327 | 328 | % meta 0x21 = MIDI port number, length 1 (? perhaps) 329 | else 330 | 331 | % channel voice messages: 332 | % (from event byte with chan removed, eg 0x8n -> 0x80 = 128 for 333 | % note off) 334 | if (type==128); name = 'Note off'; len=2; dataStr = ['nn=' num2str(data(1)) ' vel=' num2str(data(2))]; 335 | elseif (type==144); name = 'Note on'; len=2; dataStr = ['nn=' num2str(data(1)) ' vel=' num2str(data(2))]; 336 | elseif (type==160); name = 'Polyphonic Key Pressure'; len=2; dataStr = ['nn=' num2str(data(1)) ' vel=' num2str(data(2))]; 337 | elseif (type==176); name = 'Controller Change'; len=2; dataStr = ['ctrl=' controllers(data(1)) ' value=' num2str(data(2))]; 338 | elseif (type==192); name = 'Program Change'; len=1; dataStr = ['instr=' num2str(data)]; 339 | elseif (type==208); name = 'Channel Key Pressure'; len=1; dataStr = ['vel=' num2str(data)]; 340 | elseif (type==224); name = 'Pitch Bend'; len=2; 341 | val = data(1)+data(2)*256; 342 | val = base2dec('2000',16) - val; 343 | dataStr = ['change=' num2str(val) '?']; 344 | 345 | % channel mode messages: 346 | % ... unsure about data for these... (do some have a data byte and 347 | % others not?) 348 | % 349 | % 0xC1 .. 0xC8 350 | elseif (type==193); name = 'All Sounds Off'; dataStr = num2str(data); 351 | elseif (type==194); name = 'Reset All Controllers'; dataStr = num2str(data); 352 | elseif (type==195); name = 'Local Control'; dataStr = num2str(data); 353 | elseif (type==196); name = 'All Notes Off'; dataStr = num2str(data); 354 | elseif (type==197); name = 'Omni Mode Off'; dataStr = num2str(data); 355 | elseif (type==198); name = 'Omni Mode On'; dataStr = num2str(data); 356 | elseif (type==199); name = 'Mono Mode On'; dataStr = num2str(data); 357 | elseif (type==200); name = 'Poly Mode On'; dataStr = num2str(data); 358 | 359 | % sysex, F0->F7 360 | elseif (type==240); name = 'Sysex 0xF0'; dataStr = num2str(data); 361 | elseif (type==241); name = 'Sysex 0xF1'; dataStr = num2str(data); 362 | elseif (type==242); name = 'Sysex 0xF2'; dataStr = num2str(data); 363 | elseif (type==243); name = 'Sysex 0xF3'; dataStr = num2str(data); 364 | elseif (type==244); name = 'Sysex 0xF4'; dataStr = num2str(data); 365 | elseif (type==245); name = 'Sysex 0xF5'; dataStr = num2str(data); 366 | elseif (type==246); name = 'Sysex 0xF6'; dataStr = num2str(data); 367 | elseif (type==247); name = 'Sysex 0xF7'; dataStr = num2str(data); 368 | 369 | % realtime 370 | % (i think have no data..?) 371 | elseif (type==248); name = 'Real-time 0xF8 - Timing clock'; dataStr = num2str(data); 372 | elseif (type==249); name = 'Real-time 0xF9'; dataStr = num2str(data); 373 | elseif (type==250); name = 'Real-time 0xFA - Start a sequence'; dataStr = num2str(data); 374 | elseif (type==251); name = 'Real-time 0xFB - Continue a sequence'; dataStr = num2str(data); 375 | elseif (type==252); name = 'Real-time 0xFC - Stop a sequence'; dataStr = num2str(data); 376 | elseif (type==253); name = 'Real-time 0xFD'; dataStr = num2str(data); 377 | elseif (type==254); name = 'Real-time 0xFE'; dataStr = num2str(data); 378 | elseif (type==255); name = 'Real-time 0xFF'; dataStr = num2str(data); 379 | 380 | 381 | else 382 | name = ['UNKNOWN MIDI EVENT: ' num2str(type)]; dataStr = num2str(data); 383 | end 384 | 385 | 386 | end 387 | 388 | function s=controllers(n) 389 | if (n==1); s='Mod Wheel'; 390 | elseif (n==2); s='Breath Controllery'; 391 | elseif (n==4); s='Foot Controller'; 392 | elseif (n==5); s='Portamento Time'; 393 | elseif (n==6); s='Data Entry MSB'; 394 | elseif (n==7); s='Volume'; 395 | elseif (n==8); s='Balance'; 396 | elseif (n==10); s='Pan'; 397 | elseif (n==11); s='Expression Controller'; 398 | elseif (n==16); s='General Purpose 1'; 399 | elseif (n==17); s='General Purpose 2'; 400 | elseif (n==18); s='General Purpose 3'; 401 | elseif (n==19); s='General Purpose 4'; 402 | elseif (n==64); s='Sustain'; 403 | elseif (n==65); s='Portamento'; 404 | elseif (n==66); s='Sustenuto'; 405 | elseif (n==67); s='Soft Pedal'; 406 | elseif (n==69); s='Hold 2'; 407 | elseif (n==80); s='General Purpose 5'; 408 | elseif (n==81); s='Temp Change (General Purpose 6)'; 409 | elseif (n==82); s='General Purpose 7'; 410 | elseif (n==83); s='General Purpose 8'; 411 | elseif (n==91); s='Ext Effects Depth'; 412 | elseif (n==92); s='Tremelo Depthy'; 413 | elseif (n==93); s='Chorus Depth'; 414 | elseif (n==94); s='Detune Depth (Celeste Depth)'; 415 | elseif (n==95); s='Phaser Depth'; 416 | elseif (n==96); s='Data Increment (Data Entry +1)'; 417 | elseif (n==97); s='Data Decrement (Data Entry -1)'; 418 | elseif (n==98); s='Non-Registered Param LSB'; 419 | elseif (n==99); s='Non-Registered Param MSB'; 420 | elseif (n==100); s='Registered Param LSB'; 421 | elseif (n==101); s='Registered Param MSB'; 422 | else 423 | s='UNKNOWN CONTROLLER'; 424 | end 425 | 426 | %Channel mode message values 427 | %Reset All Controllers 79 121 Val ?? 428 | %Local Control 7A 122 Val 0 = off, 7F (127) = on 429 | %All Notes Off 7B 123 Val must be 0 430 | %Omni Mode Off 7C 124 Val must be 0 431 | %Omni Mode On 7D 125 Val must be 0 432 | %Mono Mode On 7E 126 Val = # of channels, or 0 if # channels equals # voices in receiver 433 | %Poly Mode On 7F 127 Val must be 0 434 | -------------------------------------------------------------------------------- /src/pianoRoll2matrix.m: -------------------------------------------------------------------------------- 1 | function notes = pianoRoll2matrix(roll, dt, nn) 2 | % notes = pianoRoll2matrix(roll, dt, nn) 3 | % Converts piano roll into a matrix of notes. 4 | % 5 | % Inputs: 6 | % roll: Piano roll. e.g. could be generated with piano_roll.m 7 | % dt: delta time - duration of ine step in seconds 8 | % nn: note number at note index ti (map of pitches) 9 | % 10 | % Outputs: 11 | % notes: matrix of notes, that you could pass into matrix2midi() 12 | % 1 2 3 4 5 6 13 | % [track chan pitch vel t1 t2] 14 | 15 | % Commited by: mikhail-matrosov 16 | 17 | N = size(roll, 2); 18 | notes = zeros(0, 6); 19 | 20 | dn = min(nn)-1; 21 | nN = numel(nn); 22 | 23 | roll = [roll,zeros(nN,1)]; 24 | N=N+1; 25 | noteCnt = 1; 26 | 27 | for nn=1:nN 28 | vel = roll(nn, 1); 29 | dur = 1; 30 | for i=1:N 31 | velNew = roll(nn, i); 32 | if velNew == vel 33 | dur = dur+1; 34 | else 35 | if vel ~= 0 36 | notes(noteCnt,:) = [1, 0, nn+dn, vel, dt*(i-dur), dt*i]; 37 | noteCnt = noteCnt+1; 38 | end 39 | dur = 1; 40 | vel = velNew; 41 | end 42 | end 43 | end 44 | 45 | notes = sortrows(notes, 5); % sort by start time 46 | 47 | end 48 | 49 | -------------------------------------------------------------------------------- /src/piano_roll.m: -------------------------------------------------------------------------------- 1 | function [PR,t,nn] = piano_roll(Notes,vel,ts) 2 | % 3 | % Inputs: 4 | % Notes: A 'notes' matrix as returned from midiInfo.m 5 | % vel: (optional) if vel==1, set value to note velocity instead of 1. (default 0) 6 | % ts: (optional) time step of one 'pixel' in seconds (default 0.01) 7 | % 8 | % Outputs: 9 | % PR: PR(ni,ti): value at note index ni, time index ti 10 | % t: t(ti): time value in seconds at time index ti 11 | % nn: nn(ni): note number at note index ti 12 | % 13 | % (i.e. t and nn provide 'real-world units' for PR) 14 | % 15 | 16 | % Copyright (c) 2009 Ken Schutte 17 | % more info at: http://www.kenschutte.com/midi 18 | 19 | if nargin < 2 20 | vel = 0; 21 | end 22 | if nargin < 3 23 | ts = 0.01; 24 | end 25 | 26 | Nnotes = size(Notes,1); 27 | 28 | n1 = round(Notes(:,5)/ts)+1; % start tics 29 | n2 = round(Notes(:,6)/ts)+1; % end tics 30 | 31 | if vel == 0 32 | vals = ones(Nnotes,1); 33 | else 34 | vals = Notes(:,4); % velocity 35 | end 36 | 37 | Notes(:,3) = Notes(:,3) + (Notes(:,3)==0); % correct zeros in the tone 38 | PR = zeros(max(Notes(:,3)), max(n2)); 39 | 40 | for i=1:Nnotes 41 | PR(Notes(i,3), n1(i):n2(i)) = vals(i); 42 | end 43 | 44 | % create quantized time axis: 45 | t = linspace(0,max(Notes(:,6)),size(PR,2)); 46 | % note axis: 47 | nn = min(Notes(:,3)):max(Notes(:,3)); 48 | % truncate to notes used: 49 | PR = PR(nn,:); 50 | -------------------------------------------------------------------------------- /src/readmidi.m: -------------------------------------------------------------------------------- 1 | function midi = readmidi(filename, rawbytes) 2 | % midi = readmidi(filename, rawbytes) 3 | % midi = readmidi(filename) 4 | % 5 | % Read MIDI file and store in a Matlab structure 6 | % (use midiInfo.m to see structure detail) 7 | % 8 | % Inputs: 9 | % filename - input MIDI file 10 | % rawbytes - 0 or 1: Include raw bytes in structure 11 | % This info is redundant, but can be 12 | % useful for debugging. default=0 13 | % 14 | 15 | % Copyright (c) 2009 Ken Schutte 16 | % more info at: http://www.kenschutte.com/midi 17 | 18 | 19 | if (nargin<2) 20 | rawbytes=0; 21 | end 22 | 23 | fid = fopen(filename); 24 | [A count] = fread(fid,'uint8'); 25 | fclose(fid); 26 | 27 | if (rawbytes) midi.rawbytes_all = A; end 28 | 29 | % realtime events: status: [F8, FF]. no data bytes 30 | %clock, undefined, start, continue, stop, undefined, active 31 | %sensing, systerm reset 32 | 33 | % file consists of "header chunk" and "track chunks" 34 | % 4B 'MThd' (header) or 'MTrk' (track) 35 | % 4B 32-bit unsigned int = number of bytes in chunk, not 36 | % counting these first 8 37 | 38 | 39 | % HEADER CHUNK -------------------------------------------------------- 40 | % 4B 'Mthd' 41 | % 4B length 42 | % 2B file format 43 | % 0=single track, 1=multitrack synchronous, 2=multitrack asynchronous 44 | % Synchronous formats start all tracks at the same time, while asynchronous formats can start and end any track at any time during the score. 45 | % 2B track cout (must be 1 for format 0) 46 | % 2B num delta-time ticks per quarter note 47 | % 48 | 49 | if ~isequal(A(1:4)',[77 84 104 100]) % double('MThd') 50 | error('File does not begin with header ID (MThd)'); 51 | end 52 | 53 | header_len = decode_int(A(5:8)); 54 | if (header_len == 6) 55 | else 56 | error('Header length != 6 bytes.'); 57 | end 58 | 59 | format = decode_int(A(9:10)); 60 | if (format==0 || format==1 || format==2) 61 | midi.format = format; 62 | else 63 | error('Format does not equal 0,1,or 2'); 64 | end 65 | 66 | num_tracks = decode_int(A(11:12)); 67 | if (format==0 && num_tracks~=1) 68 | error('File is format 0, but num_tracks != 1'); 69 | end 70 | 71 | time_unit = decode_int(A(13:14)); 72 | if (bitand(time_unit,2^15)==0) 73 | midi.ticks_per_quarter_note = time_unit; 74 | else 75 | error('Header: SMPTE time format found - not currently supported'); 76 | end 77 | 78 | if (rawbytes) 79 | midi.rawbytes_header = A(1:14); 80 | end 81 | 82 | % end header parse ---------------------------------------------------- 83 | 84 | 85 | 86 | 87 | 88 | 89 | % BREAK INTO SEPARATE TRACKS ------------------------------------------ 90 | % midi.track(1).data = [byte byte byte ...]; 91 | % midi.track(2).date = ... 92 | % ... 93 | % 94 | % Track Chunks--------- 95 | % 4B 'MTrk' 96 | % 4B length (after first 8B) 97 | % 98 | ctr = 15; 99 | for i=1:num_tracks 100 | 101 | if ~isequal(A(ctr:ctr+3)',[77 84 114 107]) % double('MTrk') 102 | error(['Track ' num2str(i) ' does not begin with track ID=MTrk']); 103 | end 104 | ctr = ctr+4; 105 | 106 | track_len = decode_int(A(ctr:ctr+3)); 107 | ctr = ctr+4; 108 | 109 | % have track.rawbytes hold initial 8B also... 110 | track_rawbytes{i} = A((ctr-8):(ctr+track_len-1)); 111 | 112 | if (rawbytes) 113 | midi.track(i).rawbytes_header = A(ctr-8:ctr-1); 114 | end 115 | 116 | ctr = ctr+track_len; 117 | end 118 | % ---------------------------------------------------------------------- 119 | 120 | 121 | 122 | 123 | 124 | 125 | % Events: 126 | % - meta events: start with 'FF' 127 | % - MIDI events: all others 128 | 129 | % MIDI events: 130 | % optional command byte + 0,1,or 2 bytes of parameters 131 | % "running mode": command byte omitted. 132 | % 133 | % all midi command bytes have MSB=1 134 | % all data for inside midi command have value <= 127 (ie MSB=0) 135 | % -> so can determine running mode 136 | % 137 | % meta events' data may have any values (meta events have to set 138 | % len) 139 | % 140 | 141 | 142 | 143 | % 'Fn' MIDI commands: 144 | % no chan. control the entire system 145 | %F8 Timing Clock 146 | %FA start a sequence 147 | %FB continue a sequence 148 | %FC stop a sequence 149 | 150 | % Meta events: 151 | % 1B 0xFF 152 | % 1B event type 153 | % 1B length of additional data 154 | % ?? additional data 155 | % 156 | 157 | 158 | % "channel mode messages" 159 | % have same code as "control change": 0xBn 160 | % but uses reserved controller numbers 120-127 161 | % 162 | 163 | 164 | %Midi events consist of an optional command byte 165 | % followed by zero, one or two bytes of parameters. 166 | % In running mode, the command can be omitted, in 167 | % which case the last MIDI command specified is 168 | % assumed. The first bit of a command byte is 1, 169 | % while the first bit of a parameter is always 0. 170 | % In addition, the last 4 bits of a command 171 | % indicate the channel to which the event should 172 | % be sent; therefore, there are 6 possible 173 | % commands (really 7, but we will discuss the x'Fn' 174 | % commands later) that can be specified. They are: 175 | 176 | 177 | % parse tracks ----------------------------------------- 178 | for i=1:num_tracks 179 | 180 | track = track_rawbytes{i}; 181 | 182 | if (rawbytes); midi.track(i).rawbytes = track; end 183 | 184 | msgCtr = 1; 185 | ctr=9; % first 8B were MTrk and length 186 | while (ctr < length(track_rawbytes{i})) 187 | 188 | clear currMsg; 189 | currMsg.used_running_mode = 0; 190 | % note: 191 | % .used_running_mode is necessary only to 192 | % be able to reconstruct a file _exactly_ from 193 | % the 'midi' structure. this is helpful for 194 | % debugging since write(read(filename)) can be 195 | % tested for exact replication... 196 | % 197 | 198 | ctr_start_msg = ctr; 199 | 200 | [deltatime,ctr] = decode_var_length(track, ctr); 201 | 202 | % ? 203 | %if (rawbytes) 204 | % currMsg.rawbytes_deltatime = track(ctr_start_msg:ctr-1); 205 | %end 206 | 207 | % deltaime must be 1-4 bytes long. 208 | % could check here... 209 | 210 | 211 | % CHECK FOR META EVENTS ------------------------ 212 | % 'FF' 213 | if track(ctr)==255 214 | 215 | type = track(ctr+1); 216 | 217 | ctr = ctr+2; 218 | 219 | % get variable length 'length' field 220 | [len,ctr] = decode_var_length(track, ctr); 221 | 222 | % note: some meta events have pre-determined lengths... 223 | % we could try verifiying they are correct here. 224 | 225 | thedata = track(ctr:ctr+len-1); 226 | chan = []; 227 | 228 | ctr = ctr + len; 229 | 230 | midimeta = 0; 231 | 232 | else 233 | midimeta = 1; 234 | % MIDI EVENT --------------------------- 235 | 236 | 237 | 238 | 239 | % check for running mode: 240 | if (track(ctr)<128) 241 | 242 | % make it re-do last command: 243 | %ctr = ctr - 1; 244 | %track(ctr) = last_byte; 245 | currMsg.used_running_mode = 1; 246 | 247 | B = last_byte; 248 | nB = track(ctr); % ? 249 | 250 | else 251 | 252 | B = track(ctr); 253 | nB = track(ctr+1); 254 | 255 | ctr = ctr + 1; 256 | 257 | end 258 | 259 | % nibbles: 260 | %B = track(ctr); 261 | %nB = track(ctr+1); 262 | 263 | 264 | Hn = bitshift(B,-4); 265 | Ln = bitand(B,15); 266 | 267 | chan = []; 268 | 269 | msg_type = midi_msg_type(B,nB); 270 | 271 | % DEBUG: 272 | if (i==2) 273 | if (msgCtr==1) 274 | disp(msg_type); 275 | end 276 | end 277 | 278 | 279 | switch msg_type 280 | 281 | case 'channel_mode' 282 | 283 | % UNSURE: if all channel mode messages have 2 data byes (?) 284 | type = bitshift(Hn,4) + (nB-120+1); 285 | thedata = track(ctr:ctr+1); 286 | chan = Ln; 287 | 288 | ctr = ctr + 2; 289 | 290 | % ---- channel voice messages: 291 | case 'channel_voice' 292 | 293 | type = bitshift(Hn,4); 294 | len = channel_voice_msg_len(type); % var length data: 295 | thedata = track(ctr:ctr+len-1); 296 | chan = Ln; 297 | 298 | % DEBUG: 299 | if (i==2) 300 | if (msgCtr==1) 301 | disp([999 Hn type]) 302 | end 303 | end 304 | 305 | ctr = ctr + len; 306 | 307 | case 'sysex' 308 | 309 | % UNSURE: do sysex events (F0-F7) have 310 | % variable length 'length' field? 311 | 312 | [len,ctr] = decode_var_length(track, ctr); 313 | 314 | type = B; 315 | thedata = track(ctr:ctr+len-1); 316 | chan = []; 317 | 318 | ctr = ctr + len; 319 | 320 | case 'sys_realtime' 321 | 322 | % UNSURE: I think these are all just one byte 323 | type = B; 324 | thedata = []; 325 | chan = []; 326 | 327 | end 328 | 329 | last_byte = Ln + bitshift(Hn,4); 330 | 331 | end % end midi event 'if' 332 | 333 | 334 | currMsg.deltatime = deltatime; 335 | currMsg.midimeta = midimeta; 336 | currMsg.type = type; 337 | currMsg.data = thedata; 338 | currMsg.chan = chan; 339 | 340 | if (rawbytes) 341 | currMsg.rawbytes = track(ctr_start_msg:ctr-1); 342 | end 343 | 344 | midi.track(i).messages(msgCtr) = currMsg; 345 | msgCtr = msgCtr + 1; 346 | 347 | 348 | end % end loop over rawbytes 349 | end % end loop over tracks 350 | 351 | function val=decode_int(A) 352 | 353 | val = 0; 354 | for i=1:length(A) 355 | val = val + bitshift(A(length(A)-i+1), 8*(i-1)); 356 | end 357 | 358 | 359 | function len=channel_voice_msg_len(type) 360 | 361 | if (type==128); len=2; 362 | elseif (type==144); len=2; 363 | elseif (type==160); len=2; 364 | elseif (type==176); len=2; 365 | elseif (type==192); len=1; 366 | elseif (type==208); len=1; 367 | elseif (type==224); len=2; 368 | else 369 | disp(type); error('bad channel voice message type'); 370 | end 371 | 372 | 373 | % 374 | % decode variable length field (often deltatime) 375 | % 376 | % return value and new position of pointer into 'bytes' 377 | % 378 | function [val,ptr] = decode_var_length(bytes, ptr) 379 | 380 | keepgoing=1; 381 | val = 0; 382 | while (keepgoing) 383 | % check MSB: 384 | % if MSB=1, then delta-time continues into next byte... 385 | if(~bitand(bytes(ptr),128)) 386 | keepgoing=0; 387 | end 388 | % keep appending last 7 bits from each byte in the deltatime: 389 | val = val*128 + rem(bytes(ptr), 128); 390 | ptr=ptr+1; 391 | end 392 | 393 | 394 | 395 | 396 | % 397 | % Read first 2 bytes of msg and 398 | % determine the type 399 | % (most require only 1st byte) 400 | % 401 | % str is one of: 402 | % 'channel_mode' 403 | % 'channel_voice' 404 | % 'sysex' 405 | % 'sys_realtime' 406 | % 407 | function str=midi_msg_type(B,nB) 408 | 409 | Hn = bitshift(B,-4); 410 | Ln = bitand(B,7); 411 | 412 | % ---- channel mode messages: 413 | %if (Hn==11 && nB>=120 && nB<=127) 414 | if (Hn==11 && nB>=122 && nB<=127) 415 | str = 'channel_mode'; 416 | 417 | % ---- channel voice messages: 418 | elseif (Hn>=8 && Hn<=14) 419 | str = 'channel_voice'; 420 | 421 | % ---- sysex events: 422 | elseif (Hn==15 && Ln>=0 && Ln<=7) 423 | str = 'sysex'; 424 | 425 | % system real-time messages 426 | elseif (Hn==15 && Ln>=8 && Ln<=15) 427 | % UNSURE: how can you tell between 0xFF system real-time 428 | % message and 0xFF meta event? 429 | % (now, it will always be processed by meta) 430 | str = 'sys_realtime'; 431 | 432 | else 433 | % don't think it can get here... 434 | error('bad midi message'); 435 | end 436 | -------------------------------------------------------------------------------- /src/synth.m: -------------------------------------------------------------------------------- 1 | function y=synth(freq,dur,amp,Fs,type) 2 | % y=synth(freq,dur,amp,Fs,type) 3 | % 4 | % Synthesize a single note 5 | % 6 | % Inputs: 7 | % freq - frequency in Hz 8 | % dur - duration in seconds 9 | % amp - Amplitude in range [0,1] 10 | % Fs - sampling frequency in Hz 11 | % type - string to select synthesis type 12 | % current options: 'fm', 'sine', or 'saw' 13 | 14 | % Copyright (c) 2009 Ken Schutte 15 | % more info at: http://www.kenschutte.com/midi 16 | 17 | if nargin<5 18 | error('Five arguments required for synth()'); 19 | end 20 | 21 | N = floor(dur*Fs); 22 | 23 | if N == 0 24 | warning('Note with zero duration.'); 25 | y = []; 26 | return; 27 | 28 | elseif N < 0 29 | warning('Note with negative duration. Skipping.'); 30 | y = []; 31 | return; 32 | end 33 | 34 | n=0:N-1; 35 | if (strcmp(type,'sine')) 36 | y = amp.*sin(2*pi*n*freq/Fs); 37 | 38 | elseif (strcmp(type,'saw')) 39 | 40 | T = (1/freq)*Fs; % period in fractional samples 41 | ramp = (0:(N-1))/T; 42 | y = ramp-fix(ramp); 43 | y = amp.*y; 44 | y = y - mean(y); 45 | 46 | elseif (strcmp(type,'fm')) 47 | 48 | t = 0:(1/Fs):dur; 49 | envel = interp1([0 dur/6 dur/3 dur/5 dur], [0 1 .75 .6 0], 0:(1/Fs):dur); 50 | I_env = 5.*envel; 51 | y = envel.*sin(2.*pi.*freq.*t + I_env.*sin(2.*pi.*freq.*t)); 52 | 53 | else 54 | error('Unknown synthesis type'); 55 | end 56 | 57 | % smooth edges w/ 10ms ramp 58 | if (dur > .02) 59 | L = 2*fix(.01*Fs)+1; % L odd 60 | ramp = bartlett(L)'; % odd length 61 | L = ceil(L/2); 62 | y(1:L) = y(1:L) .* ramp(1:L); 63 | y(end-L+1:end) = y(end-L+1:end) .* ramp(end-L+1:end); 64 | end 65 | -------------------------------------------------------------------------------- /src/writemidi.m: -------------------------------------------------------------------------------- 1 | function rawbytes=writemidi(midi,filename,do_run_mode) 2 | % rawbytes=writemidi(midi,filename,do_run_mode) 3 | % 4 | % writes to a midi file 5 | % 6 | % midi is a structure like that created by readmidi.m 7 | % 8 | % do_run_mode: flag - use running mode when possible. 9 | % if given, will override the msg.used_running_mode 10 | % default==0. (1 may not work...) 11 | % 12 | % TODO: use note-on for note-off... (for other function...) 13 | % 14 | 15 | % Copyright (c) 2009 Ken Schutte 16 | % more info at: http://www.kenschutte.com/midi 17 | 18 | 19 | %if (nargin<3) 20 | do_run_mode = 0; 21 | %end 22 | 23 | 24 | % do each track: 25 | Ntracks = length(midi.track); 26 | 27 | for i=1:Ntracks 28 | 29 | databytes_track{i} = []; 30 | 31 | for j=1:length(midi.track(i).messages) 32 | 33 | msg = midi.track(i).messages(j); 34 | 35 | msg_bytes = encode_var_length(msg.deltatime); 36 | 37 | if (msg.midimeta==1) 38 | 39 | % check for doing running mode 40 | run_mode = 0; 41 | run_mode = msg.used_running_mode; 42 | 43 | % should check that prev msg has same type to allow run 44 | % mode... 45 | 46 | 47 | % if (j>1 && do_run_mode && msg.type == midi.track(i).messages(j-1).type) 48 | % run_mode = 1; 49 | % end 50 | 51 | 52 | msg_bytes = [msg_bytes; encode_midi_msg(msg, run_mode)]; 53 | 54 | 55 | else 56 | 57 | msg_bytes = [msg_bytes; encode_meta_msg(msg)]; 58 | 59 | end 60 | 61 | % disp(msg_bytes') 62 | 63 | %if (msg_bytes ~= msg.rawbytes) 64 | % error('rawbytes mismatch'); 65 | %end 66 | 67 | databytes_track{i} = [databytes_track{i}; msg_bytes]; 68 | 69 | end 70 | end 71 | 72 | 73 | % HEADER 74 | % double('MThd') = [77 84 104 100] 75 | rawbytes = [77 84 104 100 ... 76 | 0 0 0 6 ... 77 | encode_int(midi.format,2) ... 78 | encode_int(Ntracks,2) ... 79 | encode_int(midi.ticks_per_quarter_note,2) ... 80 | ]'; 81 | 82 | % TRACK_CHUCKS 83 | for i=1:Ntracks 84 | a = length(databytes_track{i}); 85 | % double('MTrk') = [77 84 114 107] 86 | tmp = [77 84 114 107 ... 87 | encode_int(length(databytes_track{i}),4) ... 88 | databytes_track{i}']'; 89 | rawbytes(end+1:end+length(tmp)) = tmp; 90 | end 91 | 92 | 93 | % write to file 94 | fid = fopen(filename,'w'); 95 | %fwrite(fid,rawbytes,'char'); 96 | fwrite(fid,rawbytes,'uint8'); 97 | fclose(fid); 98 | 99 | % return a _column_ vector 100 | function A=encode_int(val,Nbytes) 101 | 102 | for i=1:Nbytes 103 | A(i) = bitand(bitshift(val, -8*(Nbytes-i)), 255); 104 | end 105 | 106 | 107 | function bytes=encode_var_length(val) 108 | 109 | % What should be done for fractional deltatime values? 110 | % Need to do this round() before anything else, including 111 | % that first check for val<128 (or results in bug for some fractional values). 112 | % Probably should do rounding elsewhere and require 113 | % this function to take an integer. 114 | val = round(val) 115 | 116 | if val<128 % covers 99% cases! 117 | bytes = val; 118 | return 119 | end 120 | binStr = dec2base(round(val),2); 121 | Nbytes = ceil(length(binStr)/7); 122 | binStr = ['00000000' binStr]; 123 | bytes = []; 124 | for i=1:Nbytes 125 | if (i==1) 126 | lastbit = '0'; 127 | else 128 | lastbit = '1'; 129 | end 130 | B = bin2dec([lastbit binStr(end-i*7+1:end-(i-1)*7)]); 131 | bytes = [B; bytes]; 132 | end 133 | 134 | 135 | function bytes=encode_midi_msg(msg, run_mode) 136 | 137 | bytes = []; 138 | 139 | if (run_mode ~= 1) 140 | bytes = msg.type; 141 | % channel: 142 | bytes = bytes + msg.chan; % lower nibble should be chan 143 | end 144 | 145 | bytes = [bytes; msg.data]; 146 | 147 | function bytes=encode_meta_msg(msg) 148 | 149 | bytes = 255; 150 | bytes = [bytes; msg.type]; 151 | bytes = [bytes; encode_var_length(length(msg.data))]; 152 | bytes = [bytes; msg.data]; 153 | 154 | -------------------------------------------------------------------------------- /tests/example.m: -------------------------------------------------------------------------------- 1 | midi = readmidi('jesu.mid') 2 | 3 | %----- converting MIDI to audio ---------- 4 | 5 | % (Fs = sample rate. here, uses default 44.1k.) 6 | [y,Fs] = midi2audio(midi); 7 | 8 | %% listen in matlab: 9 | soundsc(y, Fs); % FM-synth 10 | 11 | % a couple other very basic synth methods included: 12 | y = midi2audio(midi, Fs, 'sine'); 13 | soundsc(y,Fs); 14 | 15 | y = midi2audio(midi, Fs, 'saw'); 16 | soundsc(y,Fs); 17 | 18 | % save to file: 19 | % (normalize so as not clipped in writing to wav) 20 | y = .95.*y./max(abs(y)); 21 | wavwrite(y, Fs, 'out.wav'); 22 | 23 | %----- analyze MIDI info ---------- 24 | 25 | %% just display info: 26 | midiInfo(midi); 27 | 28 | %% convert to 'Notes' matrix: 29 | Notes = midiInfo(midi,0); 30 | 31 | %% compute piano-roll: 32 | [PR,t,nn] = piano_roll(Notes); 33 | 34 | %% display piano-roll: 35 | figure; 36 | imagesc(t,nn,PR); 37 | axis xy; 38 | xlabel('time (sec)'); 39 | ylabel('note number'); 40 | 41 | %% also, can do piano-roll showing velocity: 42 | [PR,t,nn] = piano_roll(Notes,1); 43 | 44 | figure; 45 | imagesc(t,nn,PR); 46 | axis xy; 47 | xlabel('time (sec)'); 48 | ylabel('note number'); 49 | 50 | %------------------------------------------------------------ 51 | 52 | % initialize matrix: 53 | N = 13; % num notes 54 | M = zeros(N,6); 55 | 56 | M(:,1) = 1; % all in track 1 57 | M(:,2) = 1; % all in channel 1 58 | M(:,3) = (60:72)'; % note numbers: one ocatave starting at middle C (60) 59 | M(:,4) = round(linspace(80,120,N))'; % lets have volume ramp up 80->120 60 | M(:,5) = (.5:.5:6.5)'; % note on: notes start every .5 seconds 61 | M(:,6) = M(:,5) + .5; % note off: each note has duration .5 seconds 62 | 63 | midi_new = matrix2midi(M); 64 | writemidi(midi_new, 'testout.mid'); 65 | 66 | %------------------------------------------------------------ 67 | 68 | % initialize matrix: 69 | N = 200; 70 | M = zeros(N,6); 71 | 72 | M(:,1) = 1; % all in track 1 73 | M(:,2) = 1; % all in channel 1 74 | 75 | M(:,3) = 30 + round(60*rand(N,1)); % random note numbers 76 | 77 | M(:,4) = 60 + round(40*rand(N,1)); % random volumes 78 | 79 | M(:,5) = 10 * rand(N,1); 80 | %M(:,6) = M(:,5) + .2 + 2 * rand(N,1); 81 | M(:,6) = M(:,5) + .2; 82 | 83 | midi_new = matrix2midi(M); 84 | writemidi(midi_new, 'testout2.mid'); 85 | 86 | -------------------------------------------------------------------------------- /tests/midi/jesu.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kts/matlab-midi/cef19d2f8bd8bb170f661c8d448283802d39559d/tests/midi/jesu.mid --------------------------------------------------------------------------------