1## Copyright (C) 2019-2021 John Donoghue <john.donoghue@ieee.org> 2## 3## This program is free software: you can redistribute it and/or modify it 4## under the terms of the GNU General Public License as published by 5## the Free Software Foundation, either version 3 of the License, or 6## (at your option) any later version. 7## 8## This program is distributed in the hope that it will be useful, but 9## WITHOUT ANY WARRANTY; without even the implied warranty of 10## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11## GNU General Public License for more details. 12## 13## You should have received a copy of the GNU General Public License 14## along with this program. If not, see 15## <https://www.gnu.org/licenses/>. 16 17classdef midimsg 18 ## -*- texinfo -*- 19 ## @deftypefn {} {@var{msg} =} midimsg (0) 20 ## @deftypefnx {} {@var{msg} =} midimsg (@var{type} ....) 21 ## @deftypefnx {} {@var{msg} =} midimsg ("note", @var{channel}, @var{note}, @var{velocity}, @var{duration}, @var{timestamp}) 22 ## @deftypefnx {} {@var{msg} =} midimsg ("noteon", @var{channel}, @var{note}, @var{velocity}, @var{timestamp}) 23 ## @deftypefnx {} {@var{msg} =} midimsg ("noteoff", @var{channel}, @var{note}, @var{velocity}, @var{timestamp}) 24 ## @deftypefnx {} {@var{msg} =} midimsg ("programchange", @var{channel}, @var{prog}, @var{timestamp}) 25 ## @deftypefnx {} {@var{msg} =} midimsg ("controlchange", @var{channel}, @var{ccnum}, @var{ccval}, @var{timestamp}) 26 ## @deftypefnx {} {@var{msg} =} midimsg ("polykeypressure", @var{channel}, @var{note}, @var{keypressure}, @var{timestamp}) 27 ## @deftypefnx {} {@var{msg} =} midimsg ("channelpressure", @var{channel}, @var{chanpressure}, @var{timestamp}) 28 ## @deftypefnx {} {@var{msg} =} midimsg ("localcontrol", @var{channel}, @var{localcontrol}, @var{timestamp}) 29 ## @deftypefnx {} {@var{msg} =} midimsg ("pitchbend", @var{channel}, @var{pitchchange}, @var{timestamp}) 30 ## @deftypefnx {} {@var{msg} =} midimsg ("polyon", @var{channel}, @var{timestamp}) 31 ## @deftypefnx {} {@var{msg} =} midimsg ("monoon", @var{channel}, @var{monochannels}, @var{timestamp}) 32 ## @deftypefnx {} {@var{msg} =} midimsg ("omnion", @var{channel}, @var{timestamp}) 33 ## @deftypefnx {} {@var{msg} =} midimsg ("omnioff", @var{channel}, @var{timestamp}) 34 ## @deftypefnx {} {@var{msg} =} midimsg ("allsoundoff", @var{channel}, @var{timestamp}) 35 ## @deftypefnx {} {@var{msg} =} midimsg ("allnotesoff", @var{channel}, @var{timestamp}) 36 ## @deftypefnx {} {@var{msg} =} midimsg ("resetallcontrollers", @var{channel}, @var{timestamp}) 37 ## @deftypefnx {} {@var{msg} =} midimsg ("start", @var{timestamp}) 38 ## @deftypefnx {} {@var{msg} =} midimsg ("stop", @var{timestamp}) 39 ## @deftypefnx {} {@var{msg} =} midimsg ("continue", @var{timestamp}) 40 ## @deftypefnx {} {@var{msg} =} midimsg ("systemreset", @var{timestamp}) 41 ## @deftypefnx {} {@var{msg} =} midimsg ("activesensing", @var{timestamp}) 42 ## @deftypefnx {} {@var{msg} =} midimsg ("timingclock", @var{timestamp}) 43 ## @deftypefnx {} {@var{msg} =} midimsg ("systemexclusive", @var{timestamp}) 44 ## @deftypefnx {} {@var{msg} =} midimsg ("systemexclusive", @var{bytes}, @var{timestamp}) 45 ## @deftypefnx {} {@var{msg} =} midimsg ("eox", @var{timestamp}) 46 ## @deftypefnx {} {@var{msg} =} midimsg ("data", @var{bytes}, @var{timestamp}) 47 ## @deftypefnx {} {@var{msg} =} midimsg ("songselect", @var{song}, @var{timestamp}) 48 ## @deftypefnx {} {@var{msg} =} midimsg ("songpositionpointer", @var{songposition}, @var{timestamp}) 49 ## @deftypefnx {} {@var{msg} =} midimsg ("tunerequest", @var{timestamp}) 50 ## @deftypefnx {} {@var{msg} =} midimsg ("miditimecodequarterframe", @var{timeseq}, @var{timevalue}, @var{timestamp}) 51 ## Create a midimsg object 52 ## 53 ## If the input parameter is 0, create an empty midi message object 54 ## Otherwise the first variable is the type of message to create, followed by the additional 55 ## parameters for the message. 56 ## 57 ## For each message type, the timestamp value is optional. 58 ## 59 ## @subsubheading Inputs 60 ## @var{type} - string message type or a midimsgtype.@* 61 ## @var{timestamp} - optional seconds time stamp for the event@* 62 ## @var{channel} - the channel to use for the message (1..16)@* 63 ## @var{note} - the value of the note to play/stop@* 64 ## @var{velocity} - the velocity value for a note on/off, with 0 stopping a note from sounding.@* 65 ## @var{duration} - seconds between starting and stopping a note when created a 'note' message.@* 66 ## @var{prog} - program number when doing a program change message.@* 67 ## @var{ccnum} - control change control number.@* 68 ## @var{ccval} - control change control value.@* 69 ## @var{keypressure} - key pressure value when creating a key pressure message.@* 70 ## @var{chanpressure} - channel pressure value when creating a channelpressure message.@* 71 ## @var{pitchchange} - pitch change value when creating a pitch bend message.@* 72 ## @var{localcontrol} - boolean value when creating a localcontrol message.@* 73 ## @var{monochannels} - channels specified for a mono on message.@* 74 ## @var{bytes} - array of data in range of 0 to 127 specified as part of a data message or 75 ## system exclusive message.@* 76 ## @var{song} - song selection number for a song selection message.@* 77 ## @var{songposition} - song position value for a song position message.@* 78 ## @var{timeseq} - timecode sequence number for a miditimecodequarterframe message.@* 79 ## @var{timevalue} - timecode value number for a miditimecodequarterframe message.@* 80 ## 81 ## @subsubheading Outputs 82 ## @var{msg} - a midimsg object containing the midi data of the message 83 ## 84 ## @subsubheading Properties 85 ## @var{timestamp} - timestamp of the message, or an array or timestamps if the the message is a 86 ## compound message.@* 87 ## @var{msgbytes} - the raw message bytes that make up the MIDI message.@* 88 ## @var{nummsgbytes} - the number of message bytes that make up the MIDI message.@* 89 ## @var{type} - string or midimsgtype that represents the message type.@* 90 ## @var{channel} - the channel number for message.@* 91 ## @var{note} - the note value for message (Only valid for noteon/off and polykeypressure).@* 92 ## @var{velocity} - the velocity value for message (Only valid for noteon/off).@* 93 ## @var{keypressure} - the keypressure value for message (Only valid for polykeypressure).@* 94 ## @var{channelpressure} - the chanpressure value for message (Only valid for channelpressure).@* 95 ## @var{localcontrol} - the localcontrol value for message (Only valid for localcontrol messages).@* 96 ## @var{monochannels} - channels specified for a mono on message.@* 97 ## @var{program} - program number specified for a program change message.@* 98 ## @var{ccnumber} - control change number specified for a control change message.@* 99 ## @var{ccvalue} - control change value specified for a control change message.@* 100 ## @var{song} - song number for a song selection message.@* 101 ## @var{songposition} - song position value for a song position message.@* 102 ## @var{pitchchange} - pitch change value for a pitch bend message.@* 103 ## @var{timecodesequence} - timecode sequence number for a miditimecodequarterframe message.@* 104 ## @var{timecodevalue} - timecode value number for a miditimecodequarterframe message.@* 105 ## 106 ## @subsubheading Examples 107 ## Create a note on/off pair with a duration of 1.5 seconds 108 ## @example 109 ## @code { 110 ## msg = midimsg('note', 1, 60, 100, 1.5) 111 ## } 112 ## @end example 113 ## 114 ## Create a separate note on/off pair with a time between them of 1.5 seconds 115 ## @example 116 ## @code { 117 ## msg = [midimsg('noteon', 1, 60, 100, 0), midimsg('noteoff', 1, 60, 0, 1.5)] 118 ## } 119 ## @end example 120 ## 121 ## Create a system reset message 122 ## @example 123 ## @code { 124 ## msg = midimsg('systemreset') 125 ## } 126 ## @end example 127 ## 128 ## @seealso{midifileread, midisend, midireceive, midimsgtype} 129 ## @end deftypefn 130 131 properties (SetAccess = private, GetAccess = public) 132 data = {}; 133 timestamp = {}; 134 endproperties 135 136 properties (SetAccess = private, GetAccess = public) 137 type; 138 endproperties 139 140 methods 141 142 function this = midimsg (typev, varargin) 143 this.data = {}; 144 this.timestamp = {}; 145 146 if nargin < 1 || (! ischar (typev) && !isscalar (typev) && ! (class(typev) == "midimsgtype")) 147 error ("Expected midi type or size"); 148 endif 149 150 if strcmp (class(typev), "midimsgtype") 151 typev = char(typev); 152 elseif isscalar (typev) 153 if typev == 0 154 return; 155 else 156 error ("Non zero size not supported yet"); 157 endif 158 endif 159 160 typev = lower (typev); 161 switch typev 162 case "noteon" 163 # channel,note,velocity,timestamp 164 if nargin < 4 165 error ('noteon expects at least channel,note,velocity') 166 endif 167 # TODO: change channel to be 1 indexed 168 chan = this.check_channel(varargin{1})-1; 169 note = this.check_value127("note", varargin{2}); 170 vel = this.check_value127("velocity", varargin{3}); 171 timestamp = 0; 172 if nargin > 4 173 timestamp = this.check_timestamp(varargin{4}); 174 endif 175 this.data{end+1} = uint8 ([bitor(0x90, chan), note, vel]); 176 this.timestamp{end+1} = timestamp; 177 case "noteoff" 178 # channel,note,velocity,timestamp 179 if nargin < 4 180 error ('noteoff expects at least channel,note,velocity') 181 endif 182 chan = this.check_channel(varargin{1})-1; 183 note = this.check_value127("note", varargin{2}); 184 vel = this.check_value127("velocity", varargin{3}); 185 timestamp = 0; 186 if nargin > 4 187 timestamp = this.check_timestamp(varargin{4}); 188 endif 189 this.data{end+1} = uint8 ([bitor(0x80, chan), note, vel]); 190 this.timestamp{end+1} = timestamp; 191 case "note" 192 # channel,note,velocity,duration, timestamp 193 if nargin < 5 194 error ('note expects at least channel,note,velocity,duration') 195 endif 196 chan = this.check_channel(varargin{1})-1; 197 note = this.check_value127("note", varargin{2}); 198 vel = this.check_value127("velocity", varargin{3}); 199 dur = varargin{4}; 200 if !isscalar(dur) || !isnumeric(dur) || dur < 0 201 error ('note expects at least duration to be a number >= 0') 202 endif 203 timestamp = 0; 204 if nargin > 5 205 timestamp = this.check_timestamp(varargin{5}); 206 endif 207 this.data{end+1} = uint8 ([bitor(0x90, chan), note, vel]); 208 this.timestamp{end+1} = timestamp; 209 210 timestamp = timestamp + dur; 211 this.data{end+1} = uint8 ([bitor(0x90, chan), note, 0]); 212 this.timestamp{end+1} = timestamp; 213 case "programchange" 214 # channel,prog, timestamp 215 if nargin < 3 216 error ('programchange expects at least channel,program') 217 endif 218 chan = this.check_channel(varargin{1})-1; 219 prog = this.check_value127("program", varargin{2}); 220 timestamp = 0; 221 if nargin > 3 222 timestamp = this.check_timestamp(varargin{3}); 223 endif 224 this.data{end+1} = uint8([bitor(0xc0, chan), prog]); 225 this.timestamp{end+1} = timestamp; 226 227 case "controlchange" 228 # channel,ccnum, ccval,timestamp 229 if nargin < 4 230 error ('controlchange expects at least channel,ccnum,ccval') 231 endif 232 chan = this.check_channel(varargin{1})-1; 233 ccnum = this.check_value119("ccnum", varargin{2}); 234 ccval = this.check_value127("ccval", varargin{3}); 235 timestamp = 0; 236 if nargin > 4 237 timestamp = this.check_timestamp(varargin{4}); 238 endif 239 this.data{end+1} = uint8([bitor(0xb0, chan), ccnum, ccval]); 240 this.timestamp{end+1} = timestamp; 241 242 case "polykeypressure" 243 # channel, note, pressure, timestamp 244 if nargin < 4 245 error ('polykeypressure expects at least channel,note,keypressure') 246 endif 247 chan = this.check_channel(varargin{1})-1; 248 note = this.check_value127("note", varargin{2}); 249 pres = this.check_value127("pressure", varargin{3}); 250 timestamp = 0; 251 if nargin > 4 252 timestamp = this.check_timestamp(varargin{4}); 253 endif 254 this.data{end+1} = uint8([bitor(0xa0, chan), note pres]); 255 this.timestamp{end+1} = timestamp; 256 257 case "channelpressure" 258 # channel, pressure, timestamp 259 if nargin < 3 260 error ('channelpressure expects at least channel,keypressure') 261 endif 262 chan = this.check_channel(varargin{1})-1; 263 pres = this.check_value127("pressure", varargin{2}); 264 timestamp = 0; 265 if nargin > 3 266 timestamp = this.check_timestamp(varargin{3}); 267 endif 268 this.data{end+1} = uint8([bitor(0xd0, chan), pres]); 269 this.timestamp{end+1} = timestamp; 270 271 case "localcontrol" 272 # channel, localcontrol, timestamp 273 if nargin < 3 274 error ('localcontrol expects at least channel,localcontrol') 275 endif 276 chan = this.check_channel(varargin{1})-1; 277 local = this.check_value1("localcontrol", varargin{2}); 278 timestamp = 0; 279 if nargin > 3 280 timestamp = this.check_timestamp(varargin{3}); 281 endif 282 this.data{end+1} = uint8([bitor(0xb0, chan), 122, local]); 283 this.timestamp{end+1} = timestamp; 284 285 case "pitchbend" 286 # channel, pitchchange, timestamp 287 if nargin < 3 288 error ('pitchbend expects at least channel,pitchchange') 289 endif 290 chan = this.check_channel(varargin{1})-1; 291 # pitch is 0 .. 16383 where 8120 is no change 292 # pitch = uint16(varargin{2} + 0x2000); 293 pitch = uint16(this.check_value16383("pitchchange", varargin{2})); 294 pitchlo = bitand(pitch, uint16(0x7F)); 295 pitchhi = bitand(bitshift(pitch, -7), uint16(0x7f)); 296 timestamp = 0; 297 if nargin > 3 298 timestamp = this.check_timestamp(varargin{3}); 299 endif 300 this.data{end+1} = uint8([bitor(0xe0, chan), pitchlo pitchhi]); 301 this.timestamp{end+1} = timestamp; 302 303 case "polyon" 304 # channel, timestamp 305 if nargin < 2 306 error ('polyon expects at least channel') 307 endif 308 chan = this.check_channel(varargin{1})-1; 309 timestamp = 0; 310 if nargin > 2 311 timestamp = this.check_timestamp(varargin{2}); 312 endif 313 this.data{end+1} = uint8([bitor(0xb0, chan), 127]); 314 this.timestamp{end+1} = timestamp; 315 316 case "monoon" 317 # channel, monochan, timestamp 318 if nargin < 3 319 error ('monoon expects at least channel and monochannels') 320 endif 321 chan = this.check_channel(varargin{1})-1; 322 mono = this.check_value16("monochannels", varargin{2}); 323 timestamp = 0; 324 if nargin > 3 325 timestamp = this.check_timestamp(varargin{3}); 326 endif 327 this.data{end+1} = uint8([bitor(0xb0, chan), 126, mono]); 328 this.timestamp{end+1} = timestamp; 329 330 case "omnion" 331 # channel, timestamp 332 if nargin < 2 333 error ('omnion expects at least channel') 334 endif 335 chan = this.check_channel(varargin{1})-1; 336 timestamp = 0; 337 if nargin > 2 338 timestamp = this.check_timestamp(varargin{2}); 339 endif 340 this.data{end+1} = uint8([bitor(0xb0, chan), 125]); 341 this.timestamp{end+1} = timestamp; 342 343 case "omnioff" 344 # channel, timestamp 345 if nargin < 2 346 error ('omnioff expects at least channel') 347 endif 348 chan = this.check_channel(varargin{1})-1; 349 timestamp = 0; 350 if nargin > 2 351 timestamp = this.check_timestamp(varargin{2}); 352 endif 353 this.data{end+1} = uint8([bitor(0xb0, chan), 124]); 354 this.timestamp{end+1} = timestamp; 355 356 case "allsoundoff" 357 # channel, timestamp 358 if nargin < 2 359 error ('allsoundoff expects at least channel') 360 endif 361 chan = this.check_channel(varargin{1})-1; 362 timestamp = 0; 363 if nargin > 2 364 timestamp = this.check_timestamp(varargin{2}); 365 endif 366 this.data{end+1} = uint8([bitor(0xb0, chan), 120]); 367 this.timestamp{end+1} = timestamp; 368 369 case "allnotesoff" 370 # channel, timestamp 371 if nargin < 2 372 error ('allnotesoff expects at least channel') 373 endif 374 chan = this.check_channel(varargin{1})-1; 375 timestamp = 0; 376 if nargin > 2 377 timestamp = this.check_timestamp(varargin{2}); 378 endif 379 this.data{end+1} = uint8([bitor(0xb0, chan), 123]); 380 this.timestamp{end+1} = timestamp; 381 382 case "resetallcontrollers" 383 # channel, timestamp 384 if nargin < 2 385 error ('resetallcontrollers expects at least channel') 386 endif 387 chan = this.check_channel(varargin{1})-1; 388 timestamp = 0; 389 if nargin > 2 390 timestamp = this.check_timestamp(varargin{2}); 391 endif 392 this.data{end+1} = uint8([bitor(0xb0, chan), 121]); 393 this.timestamp{end+1} = timestamp; 394 395 case "songselect" 396 if nargin < 2 397 error ('songselect expects at least song number') 398 endif 399 timestamp = 0; 400 song = this.check_value127("songnumber", varargin{1}); 401 if nargin > 2 402 timestamp = this.check_timestamp(varargin{2}); 403 endif 404 this.data{end+1} = uint8([0xF3 song]); 405 this.timestamp{end+1} = timestamp; 406 407 case "songpositionpointer" 408 if nargin < 2 409 error ('song expects at least song position') 410 endif 411 timestamp = 0; 412 songpos = uint16(this.check_value16383("songposiition", varargin{1})); 413 songlo = bitand(songpos, uint16(0x7F)); 414 songhi = bitand(bitshift(songpos, -7), uint16(0x7f)); 415 if nargin > 2 416 timestamp = this.check_timestamp(varargin{2}); 417 endif 418 this.data{end+1} = uint8([0xF2 songlo songhi]); 419 this.timestamp{end+1} = timestamp; 420 421 case "miditimecodequarterframe" 422 # timeseq, timeval, timestamp 423 if nargin < 3 424 error ('miditimecodequarterframe expects at least timeseq and value') 425 endif 426 seq = this.check_value7("timeseq", varargin{1}); 427 val = this.check_value15("timeval", varargin{2}); 428 seq = bitand(uint8(seq), 3); 429 val = bitand(uint8(val), 7); 430 data = bitshift(seq, 3) + val; 431 timestamp = 0; 432 if nargin > 3 433 timestamp = this.check_timestamp(varargin{3}); 434 endif 435 this.data{end+1} = uint8([0xF1 data]); 436 this.timestamp{end+1} = timestamp; 437 438 case "start" 439 timestamp = 0; 440 if nargin > 1 441 timestamp = this.check_timestamp(varargin{1}); 442 endif 443 this.data{end+1} = uint8([0xFA]); 444 this.timestamp{end+1} = timestamp; 445 446 case "stop" 447 timestamp = 0; 448 if nargin > 1 449 timestamp = this.check_timestamp(varargin{1}); 450 endif 451 this.data{end+1} = uint8([0xFC]); 452 this.timestamp{end+1} = timestamp; 453 454 case "continue" 455 timestamp = 0; 456 if nargin > 1 457 timestamp = this.check_timestamp(varargin{1}); 458 endif 459 this.data{end+1} = uint8([0xFB]); 460 this.timestamp{end+1} = timestamp; 461 462 case "systemreset" 463 timestamp = 0; 464 if nargin > 1 465 timestamp = this.check_timestamp(varargin{1}); 466 endif 467 this.data{end+1} = uint8([0xFF]); 468 this.timestamp{end+1} = timestamp; 469 470 case "activesensing" 471 timestamp = 0; 472 if nargin > 1 473 timestamp = this.check_timestamp(varargin{1}); 474 endif 475 this.data{end+1} = uint8([0xFE]); 476 this.timestamp{end+1} = timestamp; 477 478 case "timingclock" 479 timestamp = 0; 480 if nargin > 1 481 timestamp = this.check_timestamp(varargin{1}); 482 endif 483 this.data{end+1} = uint8([0xF8]); 484 this.timestamp{end+1} = timestamp; 485 486 case "tunerequest" 487 timestamp = 0; 488 if nargin > 1 489 timestamp = this.check_timestamp(varargin{1}); 490 endif 491 this.data{end+1} = uint8([0xF6]); 492 this.timestamp{end+1} = timestamp; 493 494 case "data" 495 # data, timestamp 496 if nargin < 2 497 error ('data type expects at least data') 498 endif 499 500 timestamp = 0; 501 if nargin > 2 502 timestamp = this.check_timestamp(varargin{2}); 503 endif 504 this.data{end+1} = uint8(varargin{1}); 505 this.timestamp{end+1} = timestamp; 506 507 case "eox" 508 timestamp = 0; 509 if nargin > 1 510 timestamp = this.check_timestamp(varargin{1}); 511 endif 512 this.data{end+1} = uint8([0xF7]); 513 this.timestamp{end+1} = timestamp; 514 515 case "systemexclusive" 516 # timestamp 517 # or data + timestamp 518 if nargin == 1 519 timestamp = 0; 520 data = []; 521 elseif nargin == 2 522 if !isscalar(varargin{1}) && isvector(varargin{1}) 523 data = uint8(varargin{1}); 524 timestamp = 0; 525 else 526 data = []; 527 timestamp = this.check_timestamp(varargin{1}); 528 endif 529 elseif nargin == 3 530 data = uint8(varargin{1}); 531 timestamp = this.check_timestamp(varargin{2}); 532 else 533 error ("systemexclusive expects optional data and timestamp only"); 534 endif 535 536 this.data{end+1} = uint8([0xF0]); 537 this.timestamp{end+1} = timestamp; 538 539 # want to build a full SOX data EOX 540 if !isempty(data) 541 this.data{end+1} = data; 542 this.timestamp{end+1} = timestamp; 543 544 this.data{end+1} = uint8([0xF7]); 545 this.timestamp{end+1} = timestamp; 546 endif 547 548 case "metaevent" 549 if nargin < 3 550 error ('metaevent expects at least metatype and data') 551 endif 552 timestamp = 0; 553 if nargin > 3 554 timestamp = this.check_timestamp(varargin{3}); 555 endif 556 # TODO: could be a integer or char type for metatype 557 event = this.check_value127("metatype", varargin{1}); 558 data = uint8(varargin{2}); # TODO: check validity of the data <= 127 559 datasize = midimsg.makevariable(length(data)); 560 this.data{end+1} = uint8([0xFF event datasize data]); 561 this.timestamp{end+1} = timestamp; 562 563 otherwise 564 error ("Unknown midi type '%s", typev); 565 endswitch 566 567 endfunction 568 569 function a = horzcat (a, varargin) 570 571 if !isa(a, 'midimsg') 572 error ("Cannot concatenate non midimsg elements"); 573 endif 574 575 for i = 1:nargin-1 576 b = varargin{i}; 577 if !isa(b, 'midimsg') 578 error ("Cannot concatenate non midimsg elements"); 579 endif 580 for idx=1:length (b.data) 581 a.data{end+1} = b.data{idx}; 582 a.timestamp{end+1} = b.timestamp{idx}; 583 endfor 584 endfor 585 endfunction 586 587 function e = isempty (this) 588 e = isempty(this.data); 589 endfunction 590 591 function e = length (this) 592 e = length(this.data); 593 endfunction 594 595 function this = subsasgn (this, s, rhs) 596 if isempty(s) 597 error ("midimsg.subsref missing index"); 598 endif 599 600 switch (s(1).type) 601 case "." 602 if length(this.timestamp) > 1 603 error ("Can not set %s on mutiple messages yet", s(1).subs); 604 endif 605 switch tolower(s(1).subs) 606 case "timestamp" 607 this.timestamp{1} = this.check_timestamp(rhs); 608 609 case "channel" 610 chan = this.check_channel(rhs); 611 data = this.data{1}; 612 data(1) = bitor(bitand(data(1), 0xF0), (chan-1)); 613 this.data{1} = data; 614 615 case "note" 616 data = this.data{1}; 617 cmd = bitand(data(1), 0xF0); 618 if !(cmd == 0x80 || cmd == 0x90 || cmd == 0xA0) 619 error ("note property only valid for noteon/off and polykeypressure"); 620 endif 621 data(2) = this.check_value127("note", rhs); 622 this.data{1} = data; 623 624 case "velocity" 625 data = this.data{1}; 626 cmd = bitand(data(1), 0xF0); 627 if !(cmd == 0x80 || cmd == 0x90) 628 error ("velocity property only valid for noteon/off"); 629 endif 630 data(3) = this.check_value127("velocity", rhs); 631 this.data{1} = data; 632 633 case "channelpressure" 634 data = this.data{1}; 635 cmd = bitand(data(1), 0xF0); 636 if !(cmd == 0xD0) 637 error ("channel property only valid for channelpressure messages"); 638 endif 639 data(2) = this.check_value127("channelpressure", rhs); 640 this.data{1} = data; 641 642 case "keypressure" 643 data = this.data{1}; 644 cmd = bitand(data(1), 0xF0); 645 if !(cmd == 0xA0) 646 error ("keypressure property only valid for polykeypressure messages"); 647 endif 648 data(3) = this.check_value127("keypressure", rhs); 649 this.data{1} = data; 650 651 case "ccnumber" 652 data = this.data{1}; 653 cmd = bitand(data(1), 0xF0); 654 if cmd != 0xB0 || data(2) > 119 655 error ("ccnumber property only valid for controlchange messages"); 656 endif 657 data(2) = this.check_value119("ccnumber", rhs); 658 this.data{1} = data; 659 660 case "ccvalue" 661 data = this.data{1}; 662 cmd = bitand(data(1), 0xF0); 663 if cmd != 0xB0 || data(2) > 119 664 error ("ccnumber property only valid for controlchange messages"); 665 endif 666 data(3) = this.check_value127("ccvalue", rhs); 667 this.data{1} = data; 668 669 case "song" 670 data = this.data{1}; 671 cmd = data(1); 672 if cmd != 0xF3 673 error ("song property only valid for song select messages"); 674 endif 675 data(2) = this.check_value127("song", rhs); 676 this.data{1} = data; 677 678 case "songposition" 679 data = this.data{1}; 680 cmd = data(1); 681 if cmd != 0xF2 682 error ("songposition property only valid for songpositionpointer messages"); 683 endif 684 685 songpos = uint16(this.check_value16383("songposition", rhs)); 686 songlo = bitand(songpos, uint16(0x7F)); 687 songhi = bitand(bitshift(songpos, -7), uint16(0x7f)); 688 689 data(2) = songlo; 690 data(3) = songhi; 691 692 this.data{1} = data; 693 694 case "pitchchange" 695 data = this.data{1}; 696 cmd = bitand(data(1), 0xF0); 697 if cmd != 0xE0 698 error ("pitchchange property only valid for pitchbend messages"); 699 endif 700 701 pitchchange = uint16(this.check_value16383("pitchchange", rhs)); 702 pitchlo = bitand(pitchchange, uint16(0x7F)); 703 pitchhi = bitand(bitshift(pitchchange, -7), uint16(0x7f)); 704 705 data(2) = pitchlo; 706 data(3) = pitchhi; 707 708 this.data{1} = data; 709 710 case "timecodesequence" 711 data = this.data{1}; 712 cmd = data(1); 713 if cmd != 0xF1 714 error ("timecodesequence property only valid for miditimecodequaterframe messages"); 715 endif 716 717 seq = bitshift(data(2), -3); 718 val = bitand(data(2), 7); 719 720 seq = this.check_value7("timecodesequence", rhs); 721 722 data(2) = bitshift(seq, 3) + val; 723 724 this.data{1} = data; 725 726 case "timecodevalue" 727 data = this.data{1}; 728 cmd = data(1); 729 if cmd != 0xF1 730 error ("value property only valid for miditimecodequaterframe messages"); 731 endif 732 733 seq = bitshift(data(2), -3); 734 val = bitand(data(2), 7); 735 736 val = this.check_value15("timecodevalue", rhs); 737 val = bitand(uint8(val), 7); 738 739 data(2) = bitshift(seq, 3) + val; 740 741 this.data{1} = data; 742 743 otherwise 744 error("unimplemented midimsg.subsasgn property '%s'", s(1).subs); 745 endswitch 746 747 case "()" 748 idx = s(1).subs; 749 if (numel (idx) != 1) 750 error ("@midimsg/subsasgn: needs exactly one index"); 751 endif 752 if numel (s) == 1 753 # assign a value to here - so verify is a midimsg 754 if !isa(rhs, "midimsg") 755 error ("midimsg.subsasgn rhs of indexed value must be a midimsg"); 756 endif 757 val = rhs; 758 else 759 # extract out midimsg value and do assign on it 760 val = midimsg.createMessage(this.data{idx{1}}, this.timestamp{idx{1}}); 761 val = subsasgn (val, s(2:end), rhs); 762 endif 763 # store the modded data back in our object 764 this.data{idx{1}} = val.data{1}; 765 this.timestamp{idx{1}} = val.timestamp{1}; 766 767 otherwise 768 error("unimplemented midimsg.subsasgn type"); 769 endswitch 770 endfunction 771 772 function val = subsref (this, s) 773 if isempty(s) 774 error ("midimsg.subsref missing index"); 775 endif 776 777 switch (s(1).type) 778 case "()" 779 idx = s(1).subs; 780 if (numel (idx) != 1) 781 error ("@midimsg/subsref: need exactly one index"); 782 endif 783 val = midimsg.createMessage(this.data{idx{1}}, this.timestamp{idx{1}}); 784 case "." 785 switch tolower(s(1).subs) 786 case "timestamp" 787 if length(this.timestamp) == 1 788 val = this.timestamp{1}; 789 else 790 val = this.timestamp; 791 endif 792 case "msgbytes" 793 if length(this.data) == 1 794 val = this.data{1}; 795 else 796 val = this.data; 797 endif 798 case "nummsgbytes" 799 if length(this.data) > 0 800 val = length(this.data{1}); 801 802 if length(this.data) > 1 803 for idx = 2:length(this.data) 804 val = [val length(this.data{idx})]; 805 endfor 806 endif 807 else 808 val = 0; 809 endif 810 case "type" 811 if length(this.data) > 0 812 data = this.data{1}; 813 val = this.type_enum(this.data{1}); 814 if length(this.data) > 1 815 # we cant override cellstr yes, so just use strings for multiples 816 val = {char(val)}; 817 for idx = 2:length(this.data) 818 val{end+1} = this.type_str(this.data{idx}); 819 endfor 820 endif 821 else 822 val = midimsgtype.Undefined; 823 endif 824 case "channel" 825 if length(this.data) > 0 826 data = this.data{1}; 827 val = bitand(data(1), 0x0F) + 1; 828 if length(this.data) > 1 829 for idx = 2:length(this.data) 830 data = this.data{idx}; 831 val = [val (bitand(data(1), 0x0F)+1)]; 832 endfor 833 endif 834 val = double(val); 835 endif 836 case "note" 837 if length(this.data) > 0 838 data = this.data{1}; 839 cmd = bitand(data(1), 0xF0); 840 if cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 841 val = data(2); 842 else 843 error ("note property only valid for noteon/off and polykeypressure"); 844 endif 845 if length(this.data) > 1 846 for idx = 2:length(this.data) 847 data = this.data{idx}; 848 cmd = bitand(data(1), 0xF0); 849 if cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 850 val = [val data(2)]; 851 else 852 error ("note property only valid for noteon/off and polykeypressure"); 853 endif 854 endfor 855 endif 856 val = double(val); 857 endif 858 case "velocity" 859 if length(this.data) > 0 860 data = this.data{1}; 861 cmd = bitand(data(1), 0xF0); 862 if cmd == 0x80 || cmd == 0x90 863 val = data(3); 864 else 865 error ("velocity property only valid for noteon/off"); 866 endif 867 if length(this.data) > 1 868 for idx = 2:length(this.data) 869 data = this.data{idx}; 870 cmd = bitand(data(1), 0xF0); 871 if cmd == 0x80 || cmd == 0x90 872 val = [val data(3)]; 873 else 874 error ("velocity property only valid for noteon/off"); 875 endif 876 endfor 877 endif 878 val = double(val); 879 endif 880 case "keypressure" 881 if length(this.data) > 0 882 data = this.data{1}; 883 cmd = bitand(data(1), 0xF0); 884 if cmd == 0xA0 885 val = data(3); 886 else 887 error ("keypressure property only valid for polykeypressure"); 888 endif 889 if length(this.data) > 1 890 for idx = 2:length(this.data) 891 data = this.data{idx}; 892 cmd = bitand(data(1), 0xF0); 893 if cmd == 0xA0 894 val = [val data(3)]; 895 else 896 error ("keypressure property only valid for polykeypressure"); 897 endif 898 endfor 899 endif 900 val = double(val); 901 endif 902 903 case "channelpressure" 904 if length(this.data) > 0 905 data = this.data{1}; 906 cmd = bitand(data(1), 0xF0); 907 if cmd == 0xD0 908 val = data(2); 909 else 910 error ("channelpressure property only valid for channelpressure"); 911 endif 912 if length(this.data) > 1 913 for idx = 2:length(this.data) 914 data = this.data{idx}; 915 cmd = bitand(data(1), 0xF0); 916 if cmd == 0xD0 917 val = [val data(2)]; 918 else 919 error ("keypressure property only valid for channelpressure"); 920 endif 921 endfor 922 endif 923 val = double(val); 924 endif 925 926 case "localcontrol" 927 if length(this.data) > 0 928 data = this.data{1}; 929 cmd = bitand(data(1), 0xF0); 930 if cmd == 0xB0 && data(2) == 122 931 val = data(3); 932 else 933 error ("localcontrol property only valid for localcontrol messages"); 934 endif 935 if length(this.data) > 1 936 for idx = 2:length(this.data) 937 data = this.data{idx}; 938 cmd = bitand(data(1), 0xF0); 939 if cmd == 0xB0 && data(2) == 122 940 val = [val data(3)]; 941 else 942 error ("localcontrol property only valid for localcontrol messages"); 943 endif 944 endfor 945 endif 946 val = double(val); 947 endif 948 case "monochannels" 949 if length(this.data) > 0 950 data = this.data{1}; 951 cmd = bitand(data(1), 0xF0); 952 if cmd == 0xB0 && data(2) == 126 953 val = data(3); 954 else 955 error ("monochannels property only valid for monoon messages"); 956 endif 957 if length(this.data) > 1 958 for idx = 2:length(this.data) 959 data = this.data{idx}; 960 cmd = bitand(data(1), 0xF0); 961 if cmd == 0xB0 && data(2) == 126 962 val = [val data(3)]; 963 else 964 error ("monochannels property only valid for monoon messages"); 965 endif 966 endfor 967 endif 968 val = double(val); 969 endif 970 case "program" 971 if length(this.data) > 0 972 data = this.data{1}; 973 cmd = bitand(data(1), 0xF0); 974 if cmd == 0xC0 975 val = data(2); 976 else 977 error ("program property only valid for programchange messages"); 978 endif 979 if length(this.data) > 1 980 for idx = 2:length(this.data) 981 data = this.data{idx}; 982 cmd = bitand(data(1), 0xF0); 983 if cmd == 0xC0 984 val = [val data(2)]; 985 else 986 error ("program property only valid for programchange messages"); 987 endif 988 endfor 989 endif 990 val = double(val); 991 endif 992 case "ccnumber" 993 if length(this.data) > 0 994 data = this.data{1}; 995 cmd = bitand(data(1), 0xF0); 996 if cmd == 0xB0 && data(2) <= 119 997 val = data(2); 998 else 999 error ("ccnumber property only valid for controlchange messages"); 1000 endif 1001 if length(this.data) > 1 1002 for idx = 2:length(this.data) 1003 data = this.data{idx}; 1004 cmd = bitand(data(1), 0xF0); 1005 if cmd == 0xB0 && data(2) <= 119 1006 val = [val data(2)]; 1007 else 1008 error ("ccnumber property only valid for controlchange messages"); 1009 endif 1010 endfor 1011 endif 1012 val = double(val); 1013 endif 1014 case "ccvalue" 1015 if length(this.data) > 0 1016 data = this.data{1}; 1017 cmd = bitand(data(1), 0xF0); 1018 if cmd == 0xB0 && data(2) <= 119 1019 val = data(3); 1020 else 1021 error ("ccvalue property only valid for controlchange messages"); 1022 endif 1023 if length(this.data) > 1 1024 for idx = 2:length(this.data) 1025 data = this.data{idx}; 1026 cmd = bitand(data(1), 0xF0); 1027 if cmd == 0xB0 && data(2) <= 119 1028 val = [val data(3)]; 1029 else 1030 error ("ccvalue property only valid for controlchange messages"); 1031 endif 1032 endfor 1033 endif 1034 val = double(val); 1035 endif 1036 1037 case "song" 1038 if length(this.data) > 0 1039 data = this.data{1}; 1040 cmd = data(1); 1041 if cmd == 0xF3 1042 val = data(2); 1043 else 1044 error ("song property only valid for songselect messages"); 1045 endif 1046 if length(this.data) > 1 1047 for idx = 2:length(this.data) 1048 data = this.data{idx}; 1049 cmd = data(1); 1050 if cmd == 0xF3 1051 val = [val data(2)]; 1052 else 1053 error ("song property only valid for songselect messages"); 1054 endif 1055 endfor 1056 endif 1057 val = double(val); 1058 endif 1059 1060 case "songposition" 1061 1062 if length(this.data) > 0 1063 data = this.data{1}; 1064 cmd = data(1); 1065 if cmd == 0xF2 1066 val = bitshift(int16(data(3)), 7) + int16(data(2)); 1067 else 1068 error ("songpostion property only valid for songpositionpointer messages"); 1069 endif 1070 if length(this.data) > 1 1071 for idx = 2:length(this.data) 1072 data = this.data{idx}; 1073 cmd = data(1); 1074 if cmd == 0xF2 1075 v = bitshift(int16(data(3)), 7) + int16(data(2)); 1076 val = [val v]; 1077 else 1078 error ("songposition property only valid for songpositionpointer messages"); 1079 endif 1080 endfor 1081 endif 1082 val = double(val); 1083 endif 1084 1085 case "pitchchange" 1086 1087 if length(this.data) > 0 1088 data = this.data{1}; 1089 cmd = bitand(data(1), 0xF0); 1090 if cmd == 0xE0 1091 val = bitshift(int16(data(3)), 7) + int16(data(2)); 1092 else 1093 error ("pitchchange property only valid for pitchbend messages"); 1094 endif 1095 if length(this.data) > 1 1096 for idx = 2:length(this.data) 1097 data = this.data{idx}; 1098 cmd = bitand(data(1), 0xF0); 1099 if cmd == 0xF2 1100 v = bitshift(int16(data(3)), 7) + int16(data(2)); 1101 val = [val v]; 1102 else 1103 error ("picthchange property only valid for pitchbend messages"); 1104 endif 1105 endfor 1106 endif 1107 val = double(val); 1108 endif 1109 1110 case "timecodesequence" 1111 1112 if length(this.data) > 0 1113 data = this.data{1}; 1114 cmd = data(1); 1115 if cmd == 0xF1 1116 val = bitshift(data(2), -3); 1117 else 1118 error ("timecodesequence property only valid for miditimecodequarterframe messages"); 1119 endif 1120 if length(this.data) > 1 1121 for idx = 2:length(this.data) 1122 data = this.data{idx}; 1123 cmd = data(1); 1124 if cmd == 0xF1 1125 v = bitshift(data(2), -3); 1126 val = [val v]; 1127 else 1128 error ("timecodesequence property only valid for miditimecodequaterframe messages"); 1129 endif 1130 endfor 1131 endif 1132 val = double(val); 1133 endif 1134 1135 case "timecodevalue" 1136 1137 if length(this.data) > 0 1138 data = this.data{1}; 1139 cmd = data(1); 1140 if cmd == 0xF1 1141 val = bitand(data(2), 7); 1142 else 1143 error ("timecodevalue property only valid for miditimecodequarterframe messages"); 1144 endif 1145 if length(this.data) > 1 1146 for idx = 2:length(this.data) 1147 data = this.data{idx}; 1148 cmd = data(1); 1149 if cmd == 0xF1 1150 v = bitand(data(2), 7); 1151 val = [val v]; 1152 else 1153 error ("timecodevalue property only valid for miditimecodequaterframe messages"); 1154 endif 1155 endfor 1156 endif 1157 val = double(val); 1158 endif 1159 1160 case "metatype" 1161 1162 if length(this.data) > 0 1163 data = this.data{1}; 1164 cmd = data(1); 1165 if cmd == 0xFF && length(data) > 1 1166 val = data(2); 1167 else 1168 error ("metatype property only valid for metaevent messages"); 1169 endif 1170 if length(this.data) > 1 1171 for idx = 2:length(this.data) 1172 data = this.data{idx}; 1173 cmd = data(1); 1174 if cmd == 0xFF && length(data) > 1 1175 val = data(2); 1176 val = [val v]; 1177 else 1178 error ("metatype property only valid for metaevent messages"); 1179 endif 1180 endfor 1181 endif 1182 val = double(val); 1183 endif 1184 1185 case "metadata" 1186 1187 if length(this.data) > 0 1188 data = this.data{1}; 1189 cmd = data(1); 1190 len = length(data); 1191 if cmd == 0xFF && len > 1 1192 if len < 128 1193 val = data(4:len); 1194 elseif len < 128*128 1195 val = data(5:len); 1196 elseif len < 128*128*128 1197 val = data(6:len); 1198 else 1199 val = data(7:len); 1200 endif 1201 else 1202 error ("metadata property only valid for metaevent messages"); 1203 endif 1204 if length(this.data) > 1 1205 for idx = 2:length(this.data) 1206 data = this.data{idx}; 1207 cmd = data(1); 1208 len = length(data); 1209 if cmd == 0xFF && len > 1 1210 if len < 128 1211 v = data(4:len); 1212 elseif len < 128*128 1213 v = data(5:len); 1214 elseif len < 128*128*128 1215 v = data(6:len); 1216 else 1217 v = data(7:len); 1218 endif 1219 val = [val v]; 1220 else 1221 error ("metadata property only valid for metaevent messages"); 1222 endif 1223 endfor 1224 endif 1225 endif 1226 1227 otherwise 1228 error("unimplemented midimsg.subsref property '%s'", s(1).subs); 1229 endswitch 1230 otherwise 1231 error("unimplemented midimsg.subsref type"); 1232 endswitch 1233 1234 if (numel (s) > 1) 1235 val = subsref (val, s(2:end)); 1236 endif 1237 endfunction 1238 1239 function msg = sort (this) 1240 msg = midimsg(0); 1241 1242 # TODO: store timestamp as matrix not a cell 1243 # then could just use sort, and no swaps ? 1244 [~, s_idx] = sort(cell2mat(this.timestamp')); 1245 1246 s_idx = s_idx'; 1247 1248 for idx = 1:numel(s_idx) 1249 msg.timestamp{end+1} = this.timestamp{s_idx(idx)}; 1250 msg.data{end+1} = this.data{s_idx(idx)}; 1251 endfor 1252 1253 endfunction 1254 1255 function out = disp (this) 1256 if nargout == 0 1257 disp(" MIDI message:"); 1258 else 1259 out = ""; 1260 endif 1261 for idx=1:length(this.data) 1262 data = this.data{idx}; 1263 cmd = data(1); 1264 types = this.type_str(data); 1265 1266 if bitand(cmd, 0xF0) != 0xF0 && bitand(cmd, 0x80) != 0 1267 chan = bitand(cmd, 0x0F) + 1; 1268 msgtext = sprintf ("%-10s Channel: %2d", types, chan); 1269 else 1270 msgtext = sprintf ("%-10s", types); 1271 endif 1272 1273 if strcmp(types, "NoteOn") || strcmp(types, "NoteOff") 1274 msgtext = [msgtext sprintf(" Note: %3d Velocity: %3d", data(2), data(3))]; 1275 endif 1276 if strcmp(types, "ProgramChange") 1277 msgtext = [msgtext sprintf(" Program: %3d", data(2))]; 1278 endif 1279 if strcmp(types, "ControlChange") 1280 msgtext = [msgtext sprintf(" CCNumber: %3d CCValue: %3d", data(2), data(3))]; 1281 endif 1282 if strcmp(types, "PitchBend") 1283 v = bitshift(int16(data(3)), 7) + int16(data(2)); 1284 msgtext = [msgtext sprintf(" PitchChange: %d", v)]; 1285 endif 1286 if strcmp(types, "ChannelPressure") 1287 msgtext = [msgtext sprintf(" ChannelPressure: %3d", data(2))]; 1288 endif 1289 if strcmp(types, "PolyKeyPressure") 1290 msgtext = [msgtext sprintf(" Note: %3d KeyPressure: %3d", data(2), data(3))]; 1291 endif 1292 if strcmp(types, "LocalControl") 1293 msgtext = [msgtext sprintf(" LocalControl: %3d", data(3))]; 1294 endif 1295 if strcmp(types, "MonoOn") 1296 msgtext = [msgtext sprintf(" MonoChannels: %3d", data(3))]; 1297 endif 1298 if strcmp(types, "SongSelect") 1299 msgtext = [msgtext sprintf(" Song: %3d", data(2))]; 1300 endif 1301 if strcmp(types, "SongPositionPointer") 1302 v = bitshift(int16(data(3)), 7) + int16(data(2)); 1303 msgtext = [msgtext sprintf(" SongPosition: %d", v)]; 1304 endif 1305 if strcmp(types, "MIDITimeCodeQuarterFrame") 1306 seq = bitshift(data(2), -3); 1307 val = bitand(data(2), 7); 1308 msgtext = [msgtext sprintf(" TimeCodeSequence: %d TimeCodeValue: %d", seq, val)]; 1309 endif 1310 if strcmp(types, "MetaEvent") 1311 metatype = data(2); 1312 msgtext = [msgtext sprintf(" MetaType: %d", metatype)]; 1313 endif 1314 1315 msgtext = [msgtext sprintf(" Timestamp: %f", this.timestamp{idx})]; 1316 msgtext = [msgtext sprintf(" [")]; 1317 msgtext = [msgtext sprintf(" 0x%02X", data)]; 1318 msgtext = [msgtext sprintf(" ]")]; 1319 if nargout == 0 1320 disp([" " msgtext]); 1321 else 1322 out = [out msgtext "\n"]; 1323 endif 1324 endfor 1325 endfunction 1326 1327 function d = uint8 (this) 1328 d = this.data; 1329 endfunction 1330 1331 endmethods 1332 1333 methods (Access = private) 1334 function t = check_timestamp(this, ts) 1335 if !isscalar (ts) || !isnumeric(ts) || ts < 0 1336 error ("expected timestamp to be a number >= 0"); 1337 endif 1338 t = ts; 1339 endfunction 1340 1341 function c = check_channel(this, chan) 1342 if !isscalar (chan) || !isnumeric(chan) || chan < 1 || chan > 16 1343 error ("expected channel to be a number between 1..16"); 1344 endif 1345 c = chan; 1346 endfunction 1347 1348 function v = check_value127(this, name, value) 1349 if !isscalar (value) || !isnumeric(value) || value < 0 || value > 127 1350 error ("expected %s to be a number between 0..127", name); 1351 endif 1352 v = value; 1353 endfunction 1354 1355 function v = check_value1(this, name, value) 1356 if !isscalar (value) || !(islogical(value) || isnumeric(value)) || value < 0 || value > 1 1357 error ("expected %s to be a number between 0..1", name); 1358 endif 1359 v = value; 1360 endfunction 1361 1362 function v = check_value7(this, name, value) 1363 if !isscalar (value) || !isnumeric(value) || value < 0 || value > 7 1364 error ("expected %s to be a number between 0..7", name); 1365 endif 1366 v = value; 1367 endfunction 1368 1369 function v = check_value15(this, name, value) 1370 if !isscalar (value) || !isnumeric(value) || value < 0 || value > 7 1371 error ("expected %s to be a number between 0..15", name); 1372 endif 1373 v = value; 1374 endfunction 1375 1376 function v = check_value16(this, name, value) 1377 if !isscalar (value) || !isnumeric(value) || value < 0 || value > 16 1378 error ("expected %s to be a number between 0..16", name); 1379 endif 1380 v = value; 1381 endfunction 1382 1383 function v = check_value119(this, name, value) 1384 if !isscalar (value) || !isnumeric(value) || value < 0 || value > 119 1385 error ("expected %s to be a number between 0..119", name); 1386 endif 1387 v = value; 1388 endfunction 1389 1390 function v = check_value16383(this, name, value) 1391 if !isscalar (value) || !isnumeric(value) || value < 0 || value > 16383 1392 error ("expected %s to be a number between 0..16386", name); 1393 endif 1394 v = value; 1395 endfunction 1396 1397 function v = type_enum (this, data) 1398 cmd = 0; 1399 b1 = 0; 1400 if length(data) > 0 1401 cmd = data(1); 1402 endif 1403 if length(data) > 1 1404 b1 = data(2); 1405 endif 1406 1407 cmdgrp = bitand(cmd, 0xF0); 1408 switch (cmdgrp) 1409 case 0x80 1410 v = midimsgtype.NoteOff; 1411 case 0x90 1412 v = midimsgtype.NoteOn; 1413 case 0xA0 1414 v = midimsgtype.PolyKeyPressure; 1415 case 0xB0 1416 # depends on next byte for actual msg 1417 if b1 == 120 1418 v = midimsgtype.AllSoundOff; 1419 elseif b1 == 121 1420 v = midimsgtype.ResetAllControllers; 1421 elseif b1 == 122 1422 v = midimsgtype.LocalControl; 1423 elseif b1 == 123 1424 v = midimsgtype.AllNotesOff; 1425 elseif b1 == 124 1426 v = midimsgtype.OmniOff; 1427 elseif b1 == 125 1428 v = midimsgtype.OmniOn; 1429 elseif b1 == 126 1430 v = midimsgtype.MonoOn; 1431 elseif b1 == 127 1432 v = midimsgtype.PolyOn; 1433 else 1434 v = midimsgtype.ControlChange; 1435 endif 1436 case 0xC0 1437 v = midimsgtype.ProgramChange; 1438 case 0xD0 1439 v = midimsgtype.ChannelPressure; 1440 case 0xE0 1441 v = midimsgtype.PitchBend; 1442 case 0xF0 1443 if cmd == 0xF0 1444 v = midimsgtype.SystemExclusive; 1445 elseif cmd == 0xF1 1446 v = midimsgtype.MIDITimeCodeQuarterFrame; 1447 elseif cmd == 0xF2 1448 v = midimsgtype.SongPositionPointer; 1449 elseif cmd == 0xF3 1450 v = midimsgtype.SongSelect; 1451 elseif cmd == 0xF4 1452 v = midimsgtype.Reserved; 1453 elseif cmd == 0xF5 1454 v = midimsgtype.Reserved; 1455 elseif cmd == 0xF6 1456 v = midimsgtype.TuneRequest; 1457 elseif cmd == 0xF7 1458 v = midimsgtype.EOX; 1459 elseif cmd == 0xF8 1460 v = midimsgtype.TimingClock; 1461 elseif cmd == 0xF9 1462 v = midimsgtype.Reserved; 1463 elseif cmd == 0xFA 1464 v = midimsgtype.Start; 1465 elseif cmd == 0xFB 1466 v = midimsgtype.Continue; 1467 elseif cmd == 0xFC 1468 v = midimsgtype.Stop; 1469 elseif cmd == 0xFD 1470 v = midimsgtype.Reserved; 1471 elseif cmd == 0xFE 1472 v = midimsgtype.ActiveSensing; 1473 elseif cmd == 0xFF 1474 v = midimsgtype.SystemReset; 1475 if length(data) > 1 1476 v = midimsgtype.MetaEvent; 1477 endif 1478 endif 1479 # depend on other bytes ? 1480 otherwise 1481 if length(data) < 1 1482 v = midimsgtype.Undefined; 1483 else 1484 v = midimsgtype.Data; 1485 endif 1486 endswitch 1487 1488 endfunction 1489 1490 function v = type_str (this, data) 1491 v = char(this.type_enum(data)); 1492 endfunction 1493 1494 endmethods 1495 1496 methods (Static=true) 1497 function msg = createMessage (data, ts) 1498 if nargin < 1 || !isa (data, 'uint8') || !ismatrix (data) 1499 error ("Expected matrix of uint8 data") 1500 endif 1501 if nargin < 2 1502 ts = 0; 1503 endif 1504 msg = midimsg(0); 1505 msg.data{end+1} = data; 1506 msg.timestamp{end+1} = ts; 1507 endfunction 1508 endmethods 1509 1510 methods (Static = true) 1511 # octave 5 wont allow us to call private funcs from the class 1512 # so a copy of private/makevariable is here 1513 function data = makevariable (value) 1514 t = uint64(value); 1515 if t < 128 1516 v = uint8(t); 1517 data = [v]; 1518 else 1519 tmp = dec2bin(t); 1520 while mod(length(tmp), 7) != 0 1521 tmp = [ "0" tmp ]; 1522 endwhile 1523 data = []; 1524 for i=1:7:length(tmp) 1525 v = tmp(i:i+6); 1526 v = uint8(bin2dec(v)); 1527 if i < length(tmp) -7 1528 v = 128 + v; 1529 endif 1530 1531 data = [data v]; 1532 endfor 1533 endif 1534 endfunction 1535 1536 endmethods 1537 1538endclassdef 1539 1540%!fail midimsg('badtype') 1541 1542%!fail midimsg 1543 1544%!test 1545%! a = midimsg(0); 1546%! assert(isa(a, "midimsg")); 1547%! assert(length(a) == 0); 1548%! assert(isempty(a)); 1549 1550%!fail midimsg("note", 0, 60, 127, 2) 1551%!fail midimsg("note", 17, 60, 127, 2) 1552%!fail midimsg("note", 1, 128, 127, 2) 1553%!fail midimsg("note", 1, -1, 127, 2) 1554%!fail midimsg("note", 1, 60, 128, 2) 1555%!fail midimsg("note", 1, 60, -1, 2) 1556%!fail midimsg("note", 1, 60, 127, -1) 1557%!fail midimsg("note", 1, 60, 127, 2, -1) 1558 1559%!test 1560%! a = midimsg("note", 1, 60, 127, 2); 1561%! assert(isa(a, "midimsg")); 1562%! assert(length(a) == 2); 1563%! assert(!isempty(a)); 1564%! assert(a.channel, [1 1]); 1565%! a = midimsg("note", 2, 60, 127, 1.2); 1566%! t = a.type; 1567%! assert(length(t) == 2); 1568%! assert(strcmp(t{1}, "NoteOn")) 1569%! assert(strcmp(t{2}, "NoteOn")) 1570%! t = a.timestamp; 1571%! assert(length(t) == 2); 1572%! assert(t{1}, 0.0) 1573%! assert(t{2}, 1.2) 1574%! assert(a.channel, [2 2]); 1575 1576%!test 1577%! a = midimsg("noteon", 1, 60, 20); 1578%! assert(isa(a, "midimsg")); 1579%! assert(length(a) == 1); 1580%! assert(a.type == "NoteOn"); 1581%! assert(a.channel, 1); 1582%! assert(a.msgbytes, uint8([0x90 0x3C 0x14])); 1583%! assert(!isempty(a)); 1584%! a = midimsg("noteon", 2, 60, 20); 1585%! assert(a.channel, 2); 1586%! assert(a.msgbytes, uint8([0x91 0x3C 0x14])); 1587 1588%!test 1589%! % using midimsgtype enum 1590%! a = midimsg(midimsgtype.NoteOn, 1, 60, 20); 1591%! assert(isa(a, "midimsg")); 1592%! assert(a.type == "NoteOn"); 1593 1594%!test 1595%! a = midimsg("noteoff", 1, 60, 20); 1596%! assert(isa(a, "midimsg")); 1597%! assert(length(a) == 1); 1598%! assert(a.type == "NoteOff"); 1599%! assert(a.nummsgbytes, 3); 1600%! assert(!isempty(a)); 1601%! assert(a.msgbytes, uint8([0x80 0x3C 0x14])); 1602 1603%!test 1604%! a = midimsg("programchange", 1, 60); 1605%! assert(isa(a, "midimsg")); 1606%! assert(length(a) == 1); 1607%! assert(a.type == "ProgramChange"); 1608%! assert(a.program, 60); 1609%! assert(a.channel, 1); 1610%! assert(a.nummsgbytes, 2); 1611%! assert(a.msgbytes, uint8([0xC0 60])); 1612%! assert(!isempty(a)); 1613 1614%!test 1615%! a = midimsg("controlchange", 1, 60, 65); 1616%! assert(isa(a, "midimsg")); 1617%! assert(length(a) == 1); 1618%! assert(a.type == "ControlChange"); 1619%! assert(a.nummsgbytes, 3); 1620%! assert(!isempty(a)); 1621%! assert(a.ccnumber, 60) 1622%! assert(a.ccvalue, 65) 1623%! a.ccnumber = 0; 1624%! a.ccvalue = 4; 1625%! assert(a.ccnumber, 0) 1626%! assert(a.ccvalue, 4) 1627 1628%!test 1629%! a = midimsg("polykeypressure", 1, 60, 65); 1630%! assert(isa(a, "midimsg")); 1631%! assert(length(a) == 1); 1632%! assert(a.type == "PolyKeyPressure"); 1633%! assert(a.nummsgbytes, 3); 1634%! assert(a.note, 60); 1635%! assert(a.keypressure, 65); 1636%! assert(!isempty(a)); 1637%! 1638%! a.keypressure = 40; 1639%! assert(a.keypressure, 40); 1640 1641%!test 1642%! a = midimsg("pitchbend", 1, 8192); 1643%! assert(isa(a, "midimsg")); 1644%! assert(length(a) == 1); 1645%! assert(a.type == "PitchBend"); 1646%! assert(a.nummsgbytes, 3); 1647%! assert(!isempty(a)); 1648%! assert(a.msgbytes, uint8([0xE0 0x00 0x40])); 1649%! assert(a.pitchchange, 8192); 1650%! assert(a.channel, 1); 1651%! a.pitchchange = 8200; 1652%! assert(a.pitchchange, 8200); 1653 1654%!test 1655%! a = midimsg("channelpressure", 1, 60); 1656%! assert(isa(a, "midimsg")); 1657%! assert(length(a) == 1); 1658%! assert(a.type == "ChannelPressure"); 1659%! assert(a.nummsgbytes, 2); 1660%! assert(!isempty(a)); 1661%! assert(a.channelpressure, 60); 1662%! 1663%! a.channelpressure = 40; 1664%! assert(a.channelpressure, 40); 1665 1666%!test 1667%! a = midimsg("localcontrol", 1, 1); 1668%! assert(isa(a, "midimsg")); 1669%! assert(length(a) == 1); 1670%! assert(a.type == "LocalControl"); 1671%! assert(a.nummsgbytes, 3); 1672%! assert(a.localcontrol, 1); 1673%! assert(!isempty(a)); 1674%! assert(a.msgbytes, uint8([0xB0 122 0x01])); 1675%! 1676%! a = midimsg("localcontrol", 2, 0); 1677%! assert(a.msgbytes, uint8([0xB1 122 0x00])); 1678%! assert(a.localcontrol, 0); 1679%! assert(a.channel, 2); 1680 1681%!test 1682%! a = midimsg("polyon", 1); 1683%! assert(isa(a, "midimsg")); 1684%! assert(length(a) == 1); 1685%! assert(a.type == "PolyOn"); 1686%! assert(a.nummsgbytes, 2); 1687%! assert(!isempty(a)); 1688 1689%!test 1690%! a = midimsg("monoon", 1, 0); 1691%! assert(isa(a, "midimsg")); 1692%! assert(length(a) == 1); 1693%! assert(a.type == "MonoOn"); 1694%! assert(a.monochannels, 0); 1695%! assert(a.nummsgbytes, 3); 1696%! assert(!isempty(a)); 1697 1698%!test 1699%! a = midimsg("omnion", 1); 1700%! assert(isa(a, "midimsg")); 1701%! assert(length(a) == 1); 1702%! assert(a.type == "OmniOn"); 1703%! assert(a.nummsgbytes, 2); 1704%! assert(!isempty(a)); 1705 1706%!test 1707%! a = midimsg("omnioff", 1); 1708%! assert(isa(a, "midimsg")); 1709%! assert(length(a) == 1); 1710%! assert(a.type == "OmniOff"); 1711%! assert(a.nummsgbytes, 2); 1712%! assert(!isempty(a)); 1713 1714%!test 1715%! a = midimsg("allsoundoff", 1); 1716%! assert(isa(a, "midimsg")); 1717%! assert(length(a) == 1); 1718%! assert(a.type == "AllSoundOff"); 1719%! assert(a.nummsgbytes, 2); 1720%! assert(!isempty(a)); 1721 1722%!test 1723%! a = midimsg("allnotesoff", 1); 1724%! assert(isa(a, "midimsg")); 1725%! assert(length(a) == 1); 1726%! assert(a.type == "AllNotesOff"); 1727%! assert(a.nummsgbytes, 2); 1728%! assert(!isempty(a)); 1729 1730%!test 1731%! a = midimsg("resetallcontrollers", 1); 1732%! assert(isa(a, "midimsg")); 1733%! assert(length(a) == 1); 1734%! assert(a.type == "ResetAllControllers"); 1735%! assert(a.nummsgbytes, 2); 1736%! assert(!isempty(a)); 1737 1738%!test 1739%! a = midimsg("systemreset"); 1740%! assert(isa(a, "midimsg")); 1741%! assert(length(a) == 1); 1742%! assert(a.type == "SystemReset"); 1743%! assert(a.nummsgbytes, 1); 1744%! assert(!isempty(a)); 1745%! assert(a.msgbytes, uint8([0xFF])); 1746 1747%!test 1748%! a = midimsg("start"); 1749%! assert(isa(a, "midimsg")); 1750%! assert(length(a) == 1); 1751%! assert(a.type == "Start"); 1752%! assert(a.nummsgbytes, 1); 1753%! assert(!isempty(a)); 1754%! assert(a.msgbytes, uint8([0xFA])); 1755 1756%!test 1757%! a = midimsg("stop"); 1758%! assert(isa(a, "midimsg")); 1759%! assert(length(a) == 1); 1760%! assert(a.type == "Stop"); 1761%! assert(a.nummsgbytes, 1); 1762%! assert(!isempty(a)); 1763%! assert(a.msgbytes, uint8([0xFC])); 1764 1765%!test 1766%! a = midimsg("continue"); 1767%! assert(isa(a, "midimsg")); 1768%! assert(length(a) == 1); 1769%! assert(a.type == "Continue"); 1770%! assert(a.nummsgbytes, 1); 1771%! assert(!isempty(a)); 1772%! assert(a.msgbytes, uint8([0xFB])); 1773 1774%!test 1775%! a = midimsg("activesensing"); 1776%! assert(isa(a, "midimsg")); 1777%! assert(length(a) == 1); 1778%! assert(a.type == "ActiveSensing"); 1779%! assert(a.nummsgbytes, 1); 1780%! assert(!isempty(a)); 1781%! assert(a.msgbytes, uint8([0xFE])); 1782 1783%!test 1784%! a = midimsg("timingclock"); 1785%! assert(isa(a, "midimsg")); 1786%! assert(length(a) == 1); 1787%! assert(a.type == "TimingClock"); 1788%! assert(a.nummsgbytes, 1); 1789%! assert(!isempty(a)); 1790%! assert(a.msgbytes, uint8([0xF8])); 1791 1792%!test 1793%! a = midimsg("eox"); 1794%! assert(isa(a, "midimsg")); 1795%! assert(length(a) == 1); 1796%! assert(a.type == "EOX"); 1797%! assert(a.nummsgbytes, 1); 1798%! assert(!isempty(a)); 1799%! assert(a.msgbytes, uint8([0xF7])); 1800 1801%!test 1802%! a = midimsg("data", [1 2 3]); 1803%! assert(isa(a, "midimsg")); 1804%! assert(length(a) == 1); 1805%! assert(a.type == "Data"); 1806%! assert(a.nummsgbytes, 3); 1807%! assert(!isempty(a)); 1808%! assert(a.msgbytes, uint8([1 2 3])); 1809 1810%!test 1811%! a = midimsg("songselect", 1); 1812%! assert(isa(a, "midimsg")); 1813%! assert(length(a) == 1); 1814%! assert(a.type == "SongSelect"); 1815%! assert(a.nummsgbytes, 2); 1816%! assert(!isempty(a)); 1817%! assert(a.msgbytes, uint8([0xF3 1])); 1818%! assert(a.song, 1); 1819%! a.song = 2; 1820%! assert(a.song, 2); 1821%! assert(a.msgbytes, uint8([0xF3 2])); 1822 1823%!test 1824%! a = midimsg("songpositionpointer", 0); 1825%! assert(isa(a, "midimsg")); 1826%! assert(length(a) == 1); 1827%! assert(a.type == "SongPositionPointer"); 1828%! assert(a.nummsgbytes, 3); 1829%! assert(!isempty(a)); 1830%! assert(a.msgbytes, uint8([0xF2 0 0])); 1831%! assert(a.songposition, 0); 1832%! a.songposition = 1000; 1833%! assert(a.songposition, 1000); 1834 1835%!test 1836%! a = midimsg("tunerequest"); 1837%! assert(isa(a, "midimsg")); 1838%! assert(length(a) == 1); 1839%! assert(a.type == "TuneRequest"); 1840%! assert(a.nummsgbytes, 1); 1841%! assert(!isempty(a)); 1842%! assert(a.msgbytes, uint8([0xF6])); 1843 1844%!test 1845%! a = midimsg("miditimecodequarterframe", 1, 1); 1846%! assert(isa(a, "midimsg")); 1847%! assert(length(a) == 1); 1848%! assert(a.type == "MIDITimeCodeQuarterFrame"); 1849%! assert(a.nummsgbytes, 2); 1850%! assert(!isempty(a)); 1851%! assert(a.msgbytes, uint8([0xF1 9])); 1852%! assert(a.timecodesequence, 1); 1853%! assert(a.timecodevalue, 1); 1854%! a.timecodesequence = 5; 1855%! assert(a.timecodesequence, 5); 1856%! assert(a.timecodevalue, 1); 1857%! a.timecodevalue = 2; 1858%! assert(a.timecodesequence, 5); 1859%! assert(a.timecodevalue, 2); 1860 1861%!test 1862%! a = midimsg("systemexclusive"); 1863%! assert(isa(a, "midimsg")); 1864%! assert(length(a) == 1); 1865%! assert(a.type == "SystemExclusive"); 1866%! assert(a.nummsgbytes, 1); 1867%! assert(!isempty(a)); 1868%! assert(a.msgbytes, uint8([0xF0])); 1869%! 1870%! a = midimsg("systemexclusive", 1.0); 1871%! assert(isa(a, "midimsg")); 1872%! assert(length(a) == 1); 1873%! assert(a.type == "SystemExclusive"); 1874%! assert(a.nummsgbytes, 1); 1875%! assert(!isempty(a)); 1876%! assert(a.msgbytes, uint8([0xF0])); 1877%! assert(a.timestamp, 1.0); 1878%! 1879%! a = midimsg("systemexclusive", [1 2 3]); 1880%! assert(isa(a, "midimsg")); 1881%! assert(length(a) == 3); 1882%! assert(a.type, {"SystemExclusive", "Data", "EOX"}); 1883%! assert(a.nummsgbytes, [1 3 1]); 1884%! assert(!isempty(a)); 1885%! assert(a(1).msgbytes, uint8([0xF0])); 1886%! assert(a(1).timestamp, 0.0); 1887%! assert(a(2).msgbytes, uint8([1 2 3])); 1888%! assert(a(2).timestamp, 0.0); 1889%! assert(a(3).msgbytes, uint8([0xF7])); 1890%! assert(a(3).timestamp, 0.0); 1891%! 1892%! a = midimsg("systemexclusive", [1 2 3], 5); 1893%! assert(isa(a, "midimsg")); 1894%! assert(length(a) == 3); 1895%! assert(a.type, {"SystemExclusive", "Data", "EOX"}); 1896%! assert(a.nummsgbytes, [1 3 1]); 1897%! assert(!isempty(a)); 1898%! assert(a(1).msgbytes, uint8([0xF0])); 1899%! assert(a(1).timestamp, 5.0); 1900%! assert(a(2).msgbytes, uint8([1 2 3])); 1901%! assert(a(2).timestamp, 5.0); 1902%! assert(a(3).msgbytes, uint8([0xF7])); 1903%! assert(a(3).timestamp, 5.0); 1904 1905%!test 1906%! a = midimsg("noteon", 1, 60, 20); 1907%! assert(isa(a, "midimsg")); 1908%! assert(length(a) == 1); 1909%! assert(a.type == "NoteOn"); 1910%! assert(a.note, 60); 1911%! assert(a.velocity, 20); 1912%! b = midimsg("noteoff", 2, 60, 10, 5.0); 1913%! assert(isa(b, "midimsg")); 1914%! assert(length(b) == 1); 1915%! assert(b.type == "NoteOff"); 1916%! assert(b.note, 60); 1917%! assert(b.velocity, 10); 1918%! c = [a b]; 1919%! assert(isa(c, "midimsg")); 1920%! assert(length(c) == 2); 1921%! assert(c.nummsgbytes, [3 3]); 1922%! assert(c.channel, [1 2]); 1923%! assert(c.note, [60 60]); 1924%! assert(c.velocity, [20 10]); 1925%! assert(c(1).type == "NoteOn"); 1926%! assert(c(1).channel, 1); 1927%! assert(c(1).note, 60); 1928%! assert(c(1).velocity, 20); 1929%! assert(c(2).type == "NoteOff"); 1930%! assert(c(2).timestamp, 5.0); 1931%! assert(c(2).channel, 2); 1932%! assert(c(2).note, 60); 1933%! assert(c(2).velocity, 10); 1934 1935%!test 1936%! a = midimsg("metaevent", 1, "hello"); 1937%! assert(isa(a, "midimsg")); 1938%! assert(length(a) == 1); 1939%! assert(a.type == "MetaEvent"); 1940%! assert(a.metatype, 1); 1941%! assert(a.metadata, uint8([0x68 0x65 0x6C 0x6C 0x6F])); 1942%! assert(a.msgbytes, uint8([0xFF 0x01 0x05 0x68 0x65 0x6C 0x6C 0x6F])) 1943 1944%!test 1945%! # basic assign operations 1946%! 1947%! a = midimsg("noteon", 1, 60, 127, 0); 1948%! assert(length(a) == 1); 1949%! assert(a.type == "NoteOn"); 1950%! assert(a.channel, 1); 1951%! assert(a.note, 60); 1952%! assert(a.velocity, 127); 1953%! assert(a.timestamp, 0); 1954%! 1955%! a.timestamp = 10; 1956%! a.channel = 2; 1957%! a.note = 61; 1958%! a.velocity = 100; 1959%! assert(a.timestamp, 10); 1960%! assert(a.channel, 2); 1961%! assert(a.note, 61); 1962%! assert(a.velocity, 100); 1963%! 1964%! fail ("a.channel = 0;"); 1965%! fail ("a.note = -1;"); 1966%! fail ("a.velocity = -1;"); 1967% 1968%! a = midimsg("note", 1, 60, 127, 2); 1969%! assert(length(a) == 2); 1970%! assert(a(1).timestamp, 0); 1971%! assert(a(2).timestamp, 2); 1972%! assert(a(1).note, 60); 1973%! assert(a(2).note, 60); 1974%! assert(a(1).channel, 1); 1975%! assert(a(2).channel, 1); 1976%! 1977%! a(1).timestamp = 10; 1978%! a(2).timestamp = 20; 1979%! fail ("a(3).timestamp = 1;"); 1980%! a(1).channel = 11; 1981%! a(2).channel = 12; 1982%! a(1).note = 71; 1983%! 1984%! assert(a(1).timestamp, 10); 1985%! assert(a(2).timestamp, 20); 1986%! assert(a(1).channel, 11); 1987%! assert(a(2).channel, 12); 1988%! assert(a(1).note, 71); 1989%! 1990%! fail ("a(1) = 1;"); 1991%! a(1) = midimsg("noteon", 1, 80, 100, 50); 1992%! assert(length(a) == 2); 1993%! assert(a(1).timestamp, 50); 1994%! assert(a(1).channel, 1); 1995%! assert(a(1).note, 80); 1996%! assert(a(1).velocity, 100); 1997%! 1998%! # 2nd index still same as was 1999%! assert(a(2).timestamp, 20); 2000%! assert(a(2).channel, 12); 2001%! assert(a(2).note, 60); 2002 2003