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 midicontrols < handle 18 ## -*- texinfo -*- 19 ## @deftypefn {} {@var{obj} =} midicontrols () 20 ## @deftypefnx {} {@var{obj} =} midicontrols (@var{ctrlid}) 21 ## @deftypefnx {} {@var{obj} =} midicontrols (@var{ctrlid}, @var{initialvalues}) 22 ## @deftypefnx {} {@var{obj} =} midicontrols (__, @var{propertyname}, @var{propertyvalue}) 23 ## Create a midi controls object 24 ## 25 ## @subsubheading Inputs 26 ## @var{ctrlid} - single control id or array of control ids to monitor, or [] to use any controller.@* 27 ## @var{initialvalues} - initial values to use for controls. It should be the same size as @var{ctrlid}@* 28 ## @var{propertyname}, @var{propertyvalue} - properties to set on the controller. If a device is not specified 29 ## the value from getpref("midi", "DefaultDevice", 0) will be used.@* 30 ## 31 ## 32 ## Known properties are: 33 ## @table @asis 34 ## @item mididevice 35 ## name of the mididevice to monitor. 36 ## @item outputmode 37 ## the scaling mode for values: 'rawmidi' will return values between 0 .. 127, 38 ## 'normalized' will use values between 0 .. 1. 39 ## @end table 40 ## 41 ## @subsubheading Outputs 42 ## @var{obj} - returns a midicontrols object 43 ## 44 ## @subsubheading Examples 45 ## Create a midicontrols object monitoring control id 2001 on the default midi device 46 ## @example 47 ## @code { 48 ## ctrl = midicontrols(2001) 49 ## } 50 ## @end example 51 ## 52 ## Create a midicontrols object monitoring control id 2001 on a a non default device 53 ## @example 54 ## @code { 55 ## ctrl = midicontrols(2001, 'mididevice', 1) 56 ## } 57 ## @end example 58 ## 59 ## @seealso{midiread, midisync} 60 ## @end deftypefn 61 62 properties (Access = private) 63 controls = []; 64 initialvalue = 0; 65 currentvalue = []; 66 device = []; 67 outscale = 1; 68 midiscale = 127; 69 endproperties 70 71 methods 72 73 function this = midicontrols (varargin) 74 devicename = ""; 75 76 if nargin > 0 77 controls = varargin{1}; 78 if !isnumeric(controls) 79 error ("Expected numeric controls ids"); 80 endif 81 if isscalar(controls) 82 this.controls = [controls]; 83 else 84 this.controls = controls; 85 endif 86 endif 87 88 if nargin > 1 89 if ischar (varargin{2}) 90 propstart = 2; 91 initvals = [0]; 92 else 93 propstart = 3; 94 initvals = varargin{2}; 95 96 if !isnumeric (initvals) 97 error ("Expected numeric initial values"); 98 endif 99 if isscalar (initvals) 100 initvals = [initvals]; 101 endif 102 endif 103 104 if mod (nargin-propstart + 1, 2) != 0 105 error ("midicontrols: expected property name, value pairs"); 106 endif 107 if !iscellstr (varargin (propstart:2:nargin)) 108 error ("midicontrols: expected property names to be strings"); 109 endif 110 111 for i = propstart:2:nargin 112 propname = tolower (varargin{i}); 113 propvalue = varargin{i+1}; 114 115 if strcmp (propname, "outputmode") 116 if !ischar (propvalue) 117 error ("output mode should be 'normalized' or 'rawmidi'") 118 elseif strcmpi (propvalue, "normalized") 119 this.outscale = 1; 120 elseif strcmpi (propvalue, "rawmidi") 121 this.outscale = 127; 122 else 123 error ("output mode should be 'normalized' or 'rawmidi'") 124 endif 125 elseif strcmp (propname, "mididevice") 126 devicename = propvalue; 127 else 128 error ("unknown property '%s'", propname) 129 endif 130 endfor 131 132 this.initialvalue = initvals/this.outscale; 133 endif 134 135 if length (this.controls) > 0 136 this.currentvalue = zeros (length(this.controls), 1); 137 else 138 this.currentvalue = zeros (1, 1); 139 endif 140 141 for i = 1:length (this.currentvalue) 142 this.currentvalue(i) = this.get_value (i, this.initialvalue); 143 endfor 144 145 if isempty(devicename) 146 devicename = getpref ("midi", "DefaultDevice", 0); 147 endif 148 this.device = mididevice (devicename); 149 endfunction 150 151 function send (this, values) 152 if nargin < 2 153 values = this.initialvalue; 154 else 155 if isscalar(values) 156 values = [values]; 157 endif 158 values = values/this.outscale; 159 endif 160 161 if isempty(this.controls) 162 warning ('Can not send control values when no specific controller ids were provided.') 163 val = this.get_value(1, values); 164 this.currentvalue(1) = val; 165 else 166 for i =1:length(this.controls) 167 ctrl = this.controls(i); 168 ch = int32(ctrl/1000); 169 id = mod(ctrl, 1000); 170 171 val = this.get_value(i, values); 172 this.currentvalue(i) = val; 173 174 midisend(this.device, midimsg("controlchange", ch, id, val*this.midiscale)); 175 endfor 176 endif 177 178 endfunction 179 180 function val = recv(this) 181 mx = midireceive(this.device); 182 while !isempty(mx) 183 for j = 1:length(mx) 184 m = mx(j); 185 if strcmp(m.type, "ControlChange") 186 if isempty(this.controls) 187 idx = 1; 188 else 189 ctrlid = m.channel*1000 + double(m.msgbytes(2)); 190 idx = find(this.controls==ctrlid, 1); 191 endif 192 if !isempty(idx) 193 val = double(m.msgbytes(3))/this.midiscale; 194 this.currentvalue(idx) = val; 195 endif 196 endif 197 endfor 198 mx = midireceive(this.device); 199 endwhile 200 201 val = zeros(length(this.currentvalue)); 202 for i = 1:length(this.currentvalue) 203 val(i) = this.get_value(i, this.currentvalue)*this.outscale; 204 endfor 205 endfunction 206 207 function val = get_value(this, ch, values) 208 if isscalar(values) 209 val = values; 210 else 211 if i > length(values) 212 val = values(i); 213 else 214 val = 0; 215 endif 216 endif 217 val = double(val); 218 endfunction 219 220 function out = disp (this) 221 if nargout == 0 222 disp(sprintf (" midicontrols object: listening for events on %s", this.device.Input)); 223 else 224 out = sprintf ("midicontrols object: listening for events on %s\n", this.device.Input); 225 endif 226 if ( isempty(this.controls)) 227 if nargout == 0 228 disp (" any control"); % any control on {devname} 229 else 230 out = [out " any control\n"]; 231 endif 232 else 233 if nargout == 0 234 disp ([" controls " sprintf("%d ", this.controls)]); 235 else 236 out = [out " controls " sprintf("%d ", this.controls) "\n"]; 237 endif 238 endif 239 endfunction 240 endmethods 241endclassdef 242