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, >grid;
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
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);
56 clipLo = min(minval, maxval);
57 clipHi = max(minval, maxval);
61 this.init; // rechoose the constrainfunc
62 this.changed(\minval);
67 this.changed(\maxval);
70 warp = w.asWarp(this);
77 constrain { arg value;
78 ^value.asFloat.clip(clipLo, clipHi).round(step)
80 range { ^maxval - minval }
81 ratio { ^maxval / minval }
83 // maps a value from [0..1] to spec range
84 ^warp.map(value.clip(0.0, 1.0)).round(step);
87 // maps a value from spec range to [0..1]
88 ^warp.unmap(value.round(step).clip(clipLo, clipHi));
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;
100 // others could go here.
105 grid { ^grid ?? {GridLines(this)} }
107 looseRange { |data, defaultRange = 1.0|
110 newMin = data.minItem;
111 newMax = data.maxItem;
112 if(newMin == newMax) {
113 newMin = newMin - (defaultRange / 2);
114 newMax = newMax + (defaultRange / 2);
116 # newMin, newMax = this.grid.looseRange(newMin,newMax);
117 ^this.copy.minval_(newMin).maxval_(newMax);
120 calcRange { |data, defaultRange = 1.0|
123 newMin = data.minItem;
124 newMax = data.maxItem;
125 if(newMin == newMax) {
126 newMin = newMin - (defaultRange / 2);
127 newMax = newMax + (defaultRange / 2);
129 ^this.copy.minval_(newMin).maxval_(newMax);
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)
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)
145 gridValues { |n = 20, min, max, base = 10|
147 var low = if(min.notNil) { this.unmap(min) } { 0.0 };
148 var high = if(max.notNil) { this.unmap(max) } { 1.0 };
150 ^this.grid.getParams(low,high,numTicks:n).at('lines')
154 ^this.copy.minval_(minval * ratio).maxval_(maxval * ratio)
158 ^this.copy.minval_(minval + amount).maxval_(maxval + amount)
162 ^minval.sign != maxval.sign
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")
204 ^this.class.newFrom(this)
210 // Warps specify the mapping from 0..1 and the control range
216 ^super.newCopyArgs(spec.asSpec);
218 map { arg value; ^value }
219 unmap { arg value; ^value }
221 *asWarp { arg spec; ^this.new(spec) }
224 // support Symbol-asWarp
225 warps = IdentityDictionary[
227 \exp -> ExponentialWarp,
232 \linear -> LinearWarp,
233 \exponential -> ExponentialWarp
235 // CurveWarp is specified by a number, not a Symbol
238 ^warps.findKeyForValue(this.class)
241 if(this === that,{ ^true; });
242 if(that.class !== this.class,{ ^false });
254 // maps a value from [0..1] to spec range
255 ^value * spec.range + spec.minval
258 // maps a value from spec range to [0..1]
259 ^(value - spec.minval) / spec.range
263 ExponentialWarp : Warp {
264 // minval and maxval must both be non zero and have the same sign.
266 // maps a value from [0..1] to spec range
267 ^(spec.ratio ** value) * spec.minval
270 // maps a value from spec range to [0..1]
271 ^log(value/spec.minval) / log(spec.ratio)
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);
285 if(curve.exclusivelyBetween(-0.001,0.001),{
289 a = spec.range / (1.0 - grow);
293 // maps a value from [0..1] to spec range
294 ^b - (a * pow(grow, value));
297 // maps a value from spec range to [0..1]
298 ^log((b - value) / a) / curve
300 asSpecifier { ^curve }
303 ^this.compareObject(that, [\curve])
307 ^this.instVarHash([\curve])
311 CosineWarp : LinearWarp {
313 // maps a value from [0..1] to spec range
314 ^super.map(0.5 - (cos(pi * value) * 0.5))
317 // maps a value from spec range to [0..1]
318 ^acos(1.0 - (super.unmap(value) * 2.0)) / pi
322 SineWarp : LinearWarp {
324 // maps a value from [0..1] to spec range
325 ^super.map(sin(0.5pi * value))
328 // maps a value from spec range to [0..1]
329 ^asin(super.unmap(value)) / 0.5pi
334 // useful mapping for amplitude faders
336 // maps a value from [0..1] to spec range
337 ^if(spec.range.isPositive) {
338 value.squared * spec.range + spec.minval
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
346 // maps a value from spec range to [0..1]
347 ^if(spec.range.isPositive) {
348 ((value - spec.minval) / spec.range).sqrt
350 1 - sqrt(1 - ((value - spec.minval) / spec.range))
356 // useful mapping for amplitude faders
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
363 ((1 - (1-value).squared) * range + spec.minval.dbamp).ampdb
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
371 1 - sqrt(1 - ((value.dbamp - spec.minval.dbamp) / (spec.maxval.dbamp - spec.minval.dbamp)))