1 // ControlSpec - defines the range and curve of a control.
6 specs = IdentityDictionary.new;
9 var spec = args.asSpec;
10 specs.put(name, spec);
15 ^this.subclassResponsibility(thisMethod)
18 ^this.compareObject(that)
24 ^Spec.specs.findKeyForValue(this)
28 this.printClassNameOn(stream);
29 args = this.storeArgs;
31 stream << "(" <<<* args << ")";
38 var <minval, <maxval, <warp, <step, <>default, <>units;
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 ? ""
47 *newFrom { arg similar;
48 ^this.new(similar.minval, similar.maxval, similar.warp.asSpecifier,
49 similar.step, similar.default, similar.units)
52 storeArgs { ^[minval, maxval, warp.asSpecifier, step, default, units] }
55 warp = warp.asWarp(this);
66 this.init; // rechoose the constrainfunc
67 this.changed(\minval);
72 this.changed(\maxval);
75 warp = w.asWarp(this);
82 constrain { arg value;
83 ^value.asFloat.clip(clipLo, clipHi).round(step)
85 range { ^maxval - minval }
86 ratio { ^maxval / minval }
88 // maps a value from [0..1] to spec range
89 ^warp.map(value.clip(0.0, 1.0)).round(step);
92 // maps a value from spec range to [0..1]
93 ^warp.unmap(value.round(step).clip(clipLo, clipHi));
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;
105 // others could go here.
110 calcRange { |data, defaultRange = 1.0|
113 newMin = data.minItem;
114 newMax = data.maxItem;
115 if(newMin == newMax) {
116 newMin = newMin - (defaultRange / 2);
117 newMax = newMax + (defaultRange / 2);
119 ^this.copy.minval_(newMin).maxval_(newMax);
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)
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)
135 gridValues { |n = 20, min, max, base = 10|
137 var low = if(min.notNil) { this.unmap(min) } { 0.0 };
138 var high = if(max.notNil) { this.unmap(max) } { 1.0 };
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
147 ^this.copy.minval_(minval * ratio).maxval_(maxval * ratio)
151 ^this.copy.minval_(minval + amount).maxval_(maxval + amount)
155 ^minval.sign != maxval.sign
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")
197 ^this.class.newFrom(this)
203 // Warps specify the mapping from 0..1 and the control range
209 ^super.newCopyArgs(spec.asSpec);
211 map { arg value; ^value }
212 unmap { arg value; ^value }
214 *asWarp { arg spec; ^this.new(spec) }
217 // support Symbol-asWarp
218 warps = IdentityDictionary[
220 \exp -> ExponentialWarp,
225 \linear -> LinearWarp,
226 \exponential -> ExponentialWarp
228 // CurveWarp is specified by a number, not a Symbol
231 ^warps.findKeyForValue(this.class)
234 if(this === that,{ ^true; });
235 if(that.class !== this.class,{ ^false });
247 // maps a value from [0..1] to spec range
248 ^value * spec.range + spec.minval
251 // maps a value from spec range to [0..1]
252 ^(value - spec.minval) / spec.range
256 ExponentialWarp : Warp {
257 // minval and maxval must both be non zero and have the same sign.
259 // maps a value from [0..1] to spec range
260 ^(spec.ratio ** value) * spec.minval
263 // maps a value from spec range to [0..1]
264 ^log(value/spec.minval) / log(spec.ratio)
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);
278 if(curve.exclusivelyBetween(-0.001,0.001),{
282 a = spec.range / (1.0 - grow);
286 // maps a value from [0..1] to spec range
287 ^b - (a * pow(grow, value));
290 // maps a value from spec range to [0..1]
291 ^log((b - value) / a) / curve
293 asSpecifier { ^curve }
296 ^this.compareObject(that, [\curve])
300 ^this.instVarHash([\curve])
304 CosineWarp : LinearWarp {
306 // maps a value from [0..1] to spec range
307 ^super.map(0.5 - (cos(pi * value) * 0.5))
310 // maps a value from spec range to [0..1]
311 ^acos(1.0 - (super.unmap(value) * 2.0)) / pi
315 SineWarp : LinearWarp {
317 // maps a value from [0..1] to spec range
318 ^super.map(sin(0.5pi * value))
321 // maps a value from spec range to [0..1]
322 ^asin(super.unmap(value)) / 0.5pi
327 // useful mapping for amplitude faders
329 // maps a value from [0..1] to spec range
330 ^if(spec.range.isPositive) {
331 value.squared * spec.range + spec.minval
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
339 // maps a value from spec range to [0..1]
340 ^if(spec.range.isPositive) {
341 ((value - spec.minval) / spec.range).sqrt
343 1 - sqrt(1 - ((value - spec.minval) / spec.range))
349 // useful mapping for amplitude faders
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
356 ((1 - (1-value).squared) * range + spec.minval.dbamp).ampdb
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
364 1 - sqrt(1 - ((value.dbamp - spec.minval.dbamp) / (spec.maxval.dbamp - spec.minval.dbamp)))