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