Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / SCClassLibrary / Common / GUI / ControlModel.sc
blobb3649e5d1fe51ea0030901229cf6a425df60cc2c
1 // ControlSpec - defines the range and curve of a control.
3 Spec {
4         classvar <>specs;
5         *initClass {
6                 specs = IdentityDictionary.new;
7         }
8         *add { arg name, args;
9                 var spec = args.asSpec;
10                 specs.put(name, spec);
11                 ^spec
12         }
13         asSpec { ^this }
14         defaultControl {
15                 ^this.subclassResponsibility(thisMethod)
16         }
17         == { arg that;
18                 ^this.compareObject(that)
19         }
20         hash {
21                 ^this.instVarHash
22         }
23         findKey {
24                 ^Spec.specs.findKeyForValue(this)
25         }
26         printOn { arg stream;
27                 var args;
28                 this.printClassNameOn(stream);
29                 args = this.storeArgs;
30                 if(args.notEmpty) {
31                         stream << "(" <<<* args << ")";
32                 }
33         }
37 ControlSpec : Spec {
38         var <minval, <maxval, <warp, <step, <>default, <>units, >grid;
39         var <clipLo, <clipHi;
41         *new { arg minval=0.0, maxval=1.0, warp='lin', step=0.0, default, units, grid;
42                 ^super.newCopyArgs(minval, maxval, warp, step,
43                                 default ? minval, units ? "", grid
44                         ).init
45         }
47         *newFrom { arg similar;
48                 ^this.new(similar.minval, similar.maxval, similar.warp.asSpecifier,
49                         similar.step, similar.default, similar.units)
50         }
52         storeArgs { ^[minval, maxval, warp.asSpecifier, step, default, units] }
54         init {
55                 warp = warp.asWarp(this);
56                 clipLo = min(minval, maxval);
57                 clipHi = max(minval, maxval);
58         }
59         minval_ { arg v;
60                 minval = v;
61                 this.init;      // rechoose the constrainfunc
62                 this.changed(\minval);
63         }
64         maxval_ { arg v;
65                 maxval = v;
66                 this.init;
67                 this.changed(\maxval);
68         }
69         warp_ { arg w;
70                 warp = w.asWarp(this);
71                 this.changed(\warp);
72         }
73         step_ { arg s;
74                 step = s;
75                 this.changed(\step);
76         }
77         constrain { arg value;
78                 ^value.asFloat.clip(clipLo, clipHi).round(step)
79         }
80         range { ^maxval - minval }
81         ratio { ^maxval / minval }
82         map { arg value;
83                 // maps a value from [0..1] to spec range
84                 ^warp.map(value.clip(0.0, 1.0)).round(step);
85         }
86         unmap { arg value;
87                 // maps a value from spec range to [0..1]
88                 ^warp.unmap(value.round(step).clip(clipLo, clipHi));
89         }
91         guessNumberStep {
92                         // first pass, good for linear warp
93                 var temp, numStep = this.range * 0.01;
95                         // for exponential warps, guess  again (hopefully educated)
96                 if (warp.asSpecifier == \exp) {
97                         temp = [minval, maxval].abs.minItem;
98                         ^numStep = min(temp, numStep) * 0.1;
99                 };
100                         // others could go here.
102                 ^numStep
103         }
105         grid { ^grid ?? {GridLines(this)} }
107         looseRange { |data, defaultRange = 1.0|
108                 var newMin, newMax;
109                 data = data.flat;
110                 newMin = data.minItem;
111                 newMax = data.maxItem;
112                 if(newMin == newMax) {
113                         newMin = newMin - (defaultRange / 2);
114                         newMax = newMax + (defaultRange / 2);
115                 };
116                 # newMin, newMax = this.grid.looseRange(newMin,newMax);
117                 ^this.copy.minval_(newMin).maxval_(newMax);
118         }
119         // can deprec
120         calcRange { |data, defaultRange = 1.0|
121                 var newMin, newMax;
122                 data = data.flat;
123                 newMin = data.minItem;
124                 newMax = data.maxItem;
125                 if(newMin == newMax) {
126                         newMin = newMin - (defaultRange / 2);
127                         newMax = newMax + (defaultRange / 2);
128                 };
129                 ^this.copy.minval_(newMin).maxval_(newMax);
130         }
132         normalize { |min, max|
133                 if(min.isNil) { min = if(this.hasZeroCrossing) { -1.0 } { 0.0 } };              if(max.isNil) { max = 1.0 };
134                 ^this.copy.minval_(min).maxval_(max)
135         }
137         roundRange { |base = 10|
138                 var extent = absdif(minval, maxval);
139                 var r = 10 ** ((log10(extent) * log10(base)).trunc - 1);
140                 var newMin = minval.round(r);
141                 var newMax = maxval.roundUp(r);
142                 ^this.copy.minval_(newMin).maxval_(newMax)
143         }
145         gridValues { |n = 20, min, max, base = 10|
146                 var val, exp;
147                 var low = if(min.notNil) { this.unmap(min) } { 0.0 };
148                 var high = if(max.notNil) { this.unmap(max) } { 1.0 };
149                 if(n < 1) { ^[] };
150                 ^this.grid.getParams(low,high,numTicks:n).at('lines')
151         }
153         zoom { |ratio = 1|
154                 ^this.copy.minval_(minval * ratio).maxval_(maxval * ratio)
155         }
157         shift { |amount = 1|
158                 ^this.copy.minval_(minval + amount).maxval_(maxval + amount)
159         }
161         hasZeroCrossing {
162                 ^minval.sign != maxval.sign
163         }
166         *initClass {
167                 Class.initClassTree(Warp);
168                 Class.initClassTree(Server);
169                 specs = specs.addAll([
170                         // set up some ControlSpecs for common mappings
171                         // you can add your own after the fact.
173                         \unipolar -> ControlSpec(0, 1),
174                         \bipolar -> ControlSpec(-1, 1, default: 0),
176                         \freq -> ControlSpec(20, 20000, \exp, 0, 440, units: " Hz"),
177                         \lofreq -> ControlSpec(0.1, 100, \exp, 0, 6, units: " Hz"),
178                         \midfreq -> ControlSpec(25, 4200, \exp, 0, 440, units: " Hz"),
179                         \widefreq -> ControlSpec(0.1, 20000, \exp, 0, 440, units: " Hz"),
180                         \phase -> ControlSpec(0, 2pi),
181                         \rq -> ControlSpec(0.001, 2, \exp, 0, 0.707),
183                         \audiobus -> ControlSpec(0, Server.default.options.numAudioBusChannels-1, step: 1),
184                         \controlbus -> ControlSpec(0, Server.default.options.numControlBusChannels-1, step: 1),
186                         \midi -> ControlSpec(0, 127, default: 64),
187                         \midinote -> ControlSpec(0, 127, default: 60),
188                         \midivelocity -> ControlSpec(1, 127, default: 64),
190                         \db -> ControlSpec(0.ampdb, 1.ampdb, \db, units: " dB"),
191                         \amp -> ControlSpec(0, 1, \amp, 0, 0),
192                         \boostcut -> ControlSpec(-20, 20, units: " dB",default: 0),
194                         \pan -> ControlSpec(-1, 1, default: 0),
195                         \detune -> ControlSpec(-20, 20, default: 0, units: " Hz"),
196                         \rate -> ControlSpec(0.125, 8, \exp, 0, 1),
197                         \beats -> ControlSpec(0, 20, units: " Hz"),
199                         \delay -> ControlSpec(0.0001, 1, \exp, 0, 0.3, units: " secs")
200                 ]);
201         }
203         copy {
204                 ^this.class.newFrom(this)
205         }
210 // Warps specify the mapping from 0..1 and the control range
212 Warp {
213         classvar <>warps;
214         var <>spec;
215         *new { arg spec;
216                 ^super.newCopyArgs(spec.asSpec);
217         }
218         map { arg value; ^value }
219         unmap { arg value; ^value }
221         *asWarp { arg spec; ^this.new(spec) }
222         asWarp { ^this }
223         *initClass {
224                 // support Symbol-asWarp
225                 warps = IdentityDictionary[
226                         \lin -> LinearWarp,
227                         \exp -> ExponentialWarp,
228                         \sin -> SineWarp,
229                         \cos -> CosineWarp,
230                         \amp -> FaderWarp,
231                         \db -> DbFaderWarp,
232                         \linear -> LinearWarp,
233                         \exponential -> ExponentialWarp
234                 ];
235                 // CurveWarp is specified by a number, not a Symbol
236         }
237         asSpecifier {
238                 ^warps.findKeyForValue(this.class)
239         }
240         == { arg that;
241                 if(this === that,{ ^true; });
242                 if(that.class !== this.class,{ ^false });
243                 //^spec == that.spec
244                 ^true
245         }
246         hash {
247                 ^this.class.hash
248         }
252 LinearWarp : Warp {
253         map { arg value;
254                 // maps a value from [0..1] to spec range
255                 ^value * spec.range + spec.minval
256         }
257         unmap { arg value;
258                 // maps a value from spec range to [0..1]
259                 ^(value - spec.minval) / spec.range
260         }
263 ExponentialWarp : Warp {
264         // minval and maxval must both be non zero and have the same sign.
265         map { arg value;
266                 // maps a value from [0..1] to spec range
267                 ^(spec.ratio ** value) * spec.minval
268         }
269         unmap { arg value;
270                 // maps a value from spec range to [0..1]
271                 ^log(value/spec.minval) / log(spec.ratio)
272         }
275 CurveWarp : Warp {
276         var a, b, grow, <curve;
277         *new { arg spec, curve = -2;
278                 // prevent math blow up
279                 if (abs(curve) < 0.001, { ^LinearWarp(spec) });
281                 ^super.new(spec.asSpec).init(curve);
282         }
283         init { arg argCurve;
284                 curve = argCurve;
285                 if(curve.exclusivelyBetween(-0.001,0.001),{
286                         curve = 0.001;
287                 });
288                 grow = exp(curve);
289                 a = spec.range / (1.0 - grow);
290                 b = spec.minval + a;
291         }
292         map { arg value;
293                 // maps a value from [0..1] to spec range
294                 ^b - (a * pow(grow, value));
295         }
296         unmap { arg value;
297                 // maps a value from spec range to [0..1]
298                 ^log((b - value) / a) / curve
299         }
300         asSpecifier { ^curve }
302         == { arg that;
303                 ^this.compareObject(that, [\curve])
304         }
306         hash {
307                 ^this.instVarHash([\curve])
308         }
311 CosineWarp : LinearWarp {
312         map { arg value;
313                 // maps a value from [0..1] to spec range
314                 ^super.map(0.5 - (cos(pi * value) * 0.5))
315         }
316         unmap { arg value;
317                 // maps a value from spec range to [0..1]
318                 ^acos(1.0 - (super.unmap(value) * 2.0)) / pi
319         }
322 SineWarp : LinearWarp {
323         map { arg value;
324                 // maps a value from [0..1] to spec range
325                 ^super.map(sin(0.5pi * value))
326         }
327         unmap { arg value;
328                 // maps a value from spec range to [0..1]
329                 ^asin(super.unmap(value)) / 0.5pi
330         }
333 FaderWarp : Warp {
334         //  useful mapping for amplitude faders
335         map { arg value;
336                 // maps a value from [0..1] to spec range
337                 ^if(spec.range.isPositive) {
338                         value.squared * spec.range + spec.minval
339                 }{
340                                 // formula can be reduced to (2*v) - v.squared
341                                 // but the 2 subtractions would be faster
342                         (1 - (1-value).squared) * spec.range + spec.minval
343                 };
344         }
345         unmap { arg value;
346                 // maps a value from spec range to [0..1]
347                 ^if(spec.range.isPositive) {
348                         ((value - spec.minval) / spec.range).sqrt
349                 }{
350                         1 - sqrt(1 - ((value - spec.minval) / spec.range))
351                 }
352         }
355 DbFaderWarp : Warp {
356         //  useful mapping for amplitude faders
357         map { arg value;
358                 // maps a value from [0..1] to spec range
359                 var     range = spec.maxval.dbamp - spec.minval.dbamp;
360                 ^if(range.isPositive) {
361                         (value.squared * range + spec.minval.dbamp).ampdb
362                 }{
363                         ((1 - (1-value).squared) * range + spec.minval.dbamp).ampdb
364                 }
365         }
366         unmap { arg value;
367                 // maps a value from spec range to [0..1]
368                 ^if(spec.range.isPositive) {
369                         ((value.dbamp - spec.minval.dbamp) / (spec.maxval.dbamp - spec.minval.dbamp)).sqrt
370                 }{
371                         1 - sqrt(1 - ((value.dbamp - spec.minval.dbamp) / (spec.maxval.dbamp - spec.minval.dbamp)))
372                 }
373         }