deprecate SCViewHolder-layRight
[supercollider.git] / SCClassLibrary / Common / GUI / ControlModel.sc
blob87da5840ae66c55eeecdc3edfc89ccb64a2fbc05
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;
39         var <clipLo, <clipHi;
41         *new { arg minval=0.0, maxval=1.0, warp='lin', step=0.0, default, units;
42                 ^super.newCopyArgs(minval, maxval, warp, step,
43                                 default ? minval, units ? ""
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                 if(minval < maxval,{
57                         clipLo = minval;
58                         clipHi = maxval;
59                 }, {
60                         clipLo = maxval;
61                         clipHi = minval;
62                 });
63         }
64         minval_ { arg v;
65                 minval = v;
66                 this.init;      // rechoose the constrainfunc
67                 this.changed(\minval);
68         }
69         maxval_ { arg v;
70                 maxval = v;
71                 this.init;
72                 this.changed(\maxval);
73         }
74         warp_ { arg w;
75                 warp = w.asWarp(this);
76                 this.changed(\warp);
77         }
78         step_ { arg s;
79                 step = s;
80                 this.changed(\step);
81         }
82         constrain { arg value;
83                 ^value.asFloat.clip(clipLo, clipHi).round(step)
84         }
85         range { ^maxval - minval }
86         ratio { ^maxval / minval }
87         map { arg value;
88                 // maps a value from [0..1] to spec range
89                 ^warp.map(value.clip(0.0, 1.0)).round(step);
90         }
91         unmap { arg value;
92                 // maps a value from spec range to [0..1]
93                 ^warp.unmap(value.round(step).clip(clipLo, clipHi));
94         }
96         guessNumberStep {
97                         // first pass, good for linear warp
98                 var temp, numStep = this.range * 0.01;
100                         // for exponential warps, guess  again (hopefully educated)
101                 if (warp.asSpecifier == \exp) {
102                         temp = [minval, maxval].abs.minItem;
103                         ^numStep = min(temp, numStep) * 0.1;
104                 };
105                         // others could go here.
107                 ^numStep
108         }
110         calcRange { |data, defaultRange = 1.0|
111                 var newMin, newMax;
112                 data = data.flat;
113                 newMin = data.minItem;
114                 newMax = data.maxItem;
115                 if(newMin == newMax) {
116                         newMin = newMin - (defaultRange / 2);
117                         newMax = newMax + (defaultRange / 2);
118                 };
119                 ^this.copy.minval_(newMin).maxval_(newMax);
120         }
122         normalize { |min, max|
123                 if(min.isNil) { min = if(this.hasZeroCrossing) { -1.0 } { 0.0 } };              if(max.isNil) { max = 1.0 };
124                 ^this.copy.minval_(min).maxval_(max)
125         }
127         roundRange { |base = 10|
128                 var extent = absdif(minval, maxval);
129                 var r = 10 ** ((log10(extent) * log10(base)).trunc - 1);
130                 var newMin = minval.round(r);
131                 var newMax = maxval.roundUp(r);
132                 ^this.copy.minval_(newMin).maxval_(newMax)
133         }
135         gridValues { |n = 20, min, max, base = 10|
136                 var val, exp;
137                 var low = if(min.notNil) { this.unmap(min) } { 0.0 };
138                 var high = if(max.notNil) { this.unmap(max) } { 1.0 };
139                 if(n < 1) { ^[] };
140                 val = this.map((0..n-1).normalize(low, high));
141                 exp = log10(this.map(0.5).abs) * log10(base);
142                 val = val.round(base ** (exp.trunc - 1));
143                 ^val.as(Set).as(Array).sort
144         }
146         zoom { |ratio = 1|
147                 ^this.copy.minval_(minval * ratio).maxval_(maxval * ratio)
148         }
150         shift { |amount = 1|
151                 ^this.copy.minval_(minval + amount).maxval_(maxval + amount)
152         }
154         hasZeroCrossing {
155                 ^minval.sign != maxval.sign
156         }
159         *initClass {
160                 Class.initClassTree(Warp);
161                 Class.initClassTree(Server);
162                 specs = specs.addAll([
163                         // set up some ControlSpecs for common mappings
164                         // you can add your own after the fact.
166                         \unipolar -> ControlSpec(0, 1),
167                         \bipolar -> ControlSpec(-1, 1, default: 0),
169                         \freq -> ControlSpec(20, 20000, \exp, 0, 440, units: " Hz"),
170                         \lofreq -> ControlSpec(0.1, 100, \exp, 0, 6, units: " Hz"),
171                         \midfreq -> ControlSpec(25, 4200, \exp, 0, 440, units: " Hz"),
172                         \widefreq -> ControlSpec(0.1, 20000, \exp, 0, 440, units: " Hz"),
173                         \phase -> ControlSpec(0, 2pi),
174                         \rq -> ControlSpec(0.001, 2, \exp, 0, 0.707),
176                         \audiobus -> ControlSpec(0, Server.default.options.numAudioBusChannels-1, step: 1),
177                         \controlbus -> ControlSpec(0, Server.default.options.numControlBusChannels-1, step: 1),
179                         \midi -> ControlSpec(0, 127, default: 64),
180                         \midinote -> ControlSpec(0, 127, default: 60),
181                         \midivelocity -> ControlSpec(1, 127, default: 64),
183                         \db -> ControlSpec(0.ampdb, 1.ampdb, \db, units: " dB"),
184                         \amp -> ControlSpec(0, 1, \amp, 0, 0),
185                         \boostcut -> ControlSpec(-20, 20, units: " dB",default: 0),
187                         \pan -> ControlSpec(-1, 1, default: 0),
188                         \detune -> ControlSpec(-20, 20, default: 0, units: " Hz"),
189                         \rate -> ControlSpec(0.125, 8, \exp, 0, 1),
190                         \beats -> ControlSpec(0, 20, units: " Hz"),
192                         \delay -> ControlSpec(0.0001, 1, \exp, 0, 0.3, units: " secs")
193                 ]);
194         }
196         copy {
197                 ^this.class.newFrom(this)
198         }
203 // Warps specify the mapping from 0..1 and the control range
205 Warp {
206         classvar <>warps;
207         var <>spec;
208         *new { arg spec;
209                 ^super.newCopyArgs(spec.asSpec);
210         }
211         map { arg value; ^value }
212         unmap { arg value; ^value }
214         *asWarp { arg spec; ^this.new(spec) }
215         asWarp { ^this }
216         *initClass {
217                 // support Symbol-asWarp
218                 warps = IdentityDictionary[
219                         \lin -> LinearWarp,
220                         \exp -> ExponentialWarp,
221                         \sin -> SineWarp,
222                         \cos -> CosineWarp,
223                         \amp -> FaderWarp,
224                         \db -> DbFaderWarp,
225                         \linear -> LinearWarp,
226                         \exponential -> ExponentialWarp
227                 ];
228                 // CurveWarp is specified by a number, not a Symbol
229         }
230         asSpecifier {
231                 ^warps.findKeyForValue(this.class)
232         }
233         == { arg that;
234                 if(this === that,{ ^true; });
235                 if(that.class !== this.class,{ ^false });
236                 //^spec == that.spec
237                 ^true
238         }
239         hash {
240                 ^this.class.hash
241         }
245 LinearWarp : Warp {
246         map { arg value;
247                 // maps a value from [0..1] to spec range
248                 ^value * spec.range + spec.minval
249         }
250         unmap { arg value;
251                 // maps a value from spec range to [0..1]
252                 ^(value - spec.minval) / spec.range
253         }
256 ExponentialWarp : Warp {
257         // minval and maxval must both be non zero and have the same sign.
258         map { arg value;
259                 // maps a value from [0..1] to spec range
260                 ^(spec.ratio ** value) * spec.minval
261         }
262         unmap { arg value;
263                 // maps a value from spec range to [0..1]
264                 ^log(value/spec.minval) / log(spec.ratio)
265         }
268 CurveWarp : Warp {
269         var a, b, grow, <curve;
270         *new { arg spec, curve = -2;
271                 // prevent math blow up
272                 if (abs(curve) < 0.001, { ^LinearWarp(spec) });
274                 ^super.new(spec.asSpec).init(curve);
275         }
276         init { arg argCurve;
277                 curve = argCurve;
278                 if(curve.exclusivelyBetween(-0.001,0.001),{
279                         curve = 0.001;
280                 });
281                 grow = exp(curve);
282                 a = spec.range / (1.0 - grow);
283                 b = spec.minval + a;
284         }
285         map { arg value;
286                 // maps a value from [0..1] to spec range
287                 ^b - (a * pow(grow, value));
288         }
289         unmap { arg value;
290                 // maps a value from spec range to [0..1]
291                 ^log((b - value) / a) / curve
292         }
293         asSpecifier { ^curve }
295         == { arg that;
296                 ^this.compareObject(that, [\curve])
297         }
299         hash {
300                 ^this.instVarHash([\curve])
301         }
304 CosineWarp : LinearWarp {
305         map { arg value;
306                 // maps a value from [0..1] to spec range
307                 ^super.map(0.5 - (cos(pi * value) * 0.5))
308         }
309         unmap { arg value;
310                 // maps a value from spec range to [0..1]
311                 ^acos(1.0 - (super.unmap(value) * 2.0)) / pi
312         }
315 SineWarp : LinearWarp {
316         map { arg value;
317                 // maps a value from [0..1] to spec range
318                 ^super.map(sin(0.5pi * value))
319         }
320         unmap { arg value;
321                 // maps a value from spec range to [0..1]
322                 ^asin(super.unmap(value)) / 0.5pi
323         }
326 FaderWarp : Warp {
327         //  useful mapping for amplitude faders
328         map { arg value;
329                 // maps a value from [0..1] to spec range
330                 ^if(spec.range.isPositive) {
331                         value.squared * spec.range + spec.minval
332                 }{
333                                 // formula can be reduced to (2*v) - v.squared
334                                 // but the 2 subtractions would be faster
335                         (1 - (1-value).squared) * spec.range + spec.minval
336                 };
337         }
338         unmap { arg value;
339                 // maps a value from spec range to [0..1]
340                 ^if(spec.range.isPositive) {
341                         ((value - spec.minval) / spec.range).sqrt
342                 }{
343                         1 - sqrt(1 - ((value - spec.minval) / spec.range))
344                 }
345         }
348 DbFaderWarp : Warp {
349         //  useful mapping for amplitude faders
350         map { arg value;
351                 // maps a value from [0..1] to spec range
352                 var     range = spec.maxval.dbamp - spec.minval.dbamp;
353                 ^if(range.isPositive) {
354                         (value.squared * range + spec.minval.dbamp).ampdb
355                 }{
356                         ((1 - (1-value).squared) * range + spec.minval.dbamp).ampdb
357                 }
358         }
359         unmap { arg value;
360                 // maps a value from spec range to [0..1]
361                 ^if(spec.range.isPositive) {
362                         ((value.dbamp - spec.minval.dbamp) / (spec.maxval.dbamp - spec.minval.dbamp)).sqrt
363                 }{
364                         1 - sqrt(1 - ((value.dbamp - spec.minval.dbamp) / (spec.maxval.dbamp - spec.minval.dbamp)))
365                 }
366         }