1 /*
2 Cockos WDL License
3 
4 Copyright (C) 2005 - 2015 Cockos Incorporated
5 Copyright (C) 2015 and later Auburn Sounds
6 
7 Portions copyright other contributors, see each source file for more information
8 
9 This software is provided 'as-is', without any express or implied warranty.  In no event will the authors be held liable for any damages arising from the use of this software.
10 
11 Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
12 
13 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
14 1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
15 1. This notice may not be removed or altered from any source distribution.
16 */
17 /**
18     Definitions of plug-in `Parameter`, and its many variants.
19 */
20 module dplug.client.params;
21 
22 import core.atomic;
23 import core.stdc.stdio;
24 import core.stdc..string;
25 
26 import std.math;
27 import std.algorithm.comparison;
28 import std..string;
29 import std.conv;
30 
31 import dplug.core.math;
32 import dplug.core.sync;
33 import dplug.core.nogc;
34 import dplug.core.vec;
35 import dplug.client.client;
36 
37 
38 /// Plugin parameter.
39 /// Implement the Observer pattern for UI support.
40 /// Note: Recursive mutexes are needed here because `getNormalized()`
41 /// could need locking an already taken mutex.
42 class Parameter
43 {
44 public:
45 nothrow:
46 @nogc:
47 
48     /// Returns: Parameters name. Displayed when the plugin has no UI.
49     string name() pure const
50     {
51         return _name;
52     }
53 
54     /// Returns: Parameters unit label.
55     string label() pure const
56     {
57         return _label;
58     }
59 
60     /// Returns: Index of parameter in the parameter list.
61     int index() pure const
62     {
63         return _index;
64     }
65 
66     /// From a normalized double [0..1], set the parameter value.
67     void setFromHost(double hostValue)
68     {
69         setNormalized(hostValue);
70         notifyListeners();
71     }
72 
73     /// Returns: A normalized double [0..1], represents the parameter value.
74     double getForHost()
75     {
76         return getNormalized();
77     }
78 
79     void toDisplayN(char* buffer, size_t numBytes)
80     {
81         toStringN(buffer, numBytes);
82     }
83 
84     /// Adds a parameter listener.
85     void addListener(IParameterListener listener)
86     {
87         _listeners.pushBack(listener);
88     }
89 
90     /// Removes a parameter listener.
91     void removeListener(IParameterListener listener)
92     {
93         int index = _listeners.indexOf(listener);
94         if (index != -1)
95             _listeners.removeAndReplaceByLastElement(index);
96     }
97 
98     /// Warns the host that a parameter will be edited.
99     /// Should only ever be called from the UI thread.
100     void beginParamEdit()
101     {
102         debug _editCount += 1;
103         _client.hostCommand().beginParamEdit(_index);
104         foreach(listener; _listeners)
105             listener.onBeginParameterEdit(this);
106     }
107 
108     /// Warns the host that a parameter has finished being edited.
109     /// Should only ever be called from the UI thread.
110     void endParamEdit()
111     {
112         _client.hostCommand().endParamEdit(_index);
113         foreach(listener; _listeners)
114             listener.onEndParameterEdit(this);
115         debug _editCount -= 1;
116     }
117 
118     /// Returns: A normalized double, representing the parameter value.
119     abstract double getNormalized();
120 
121     /// Returns: A normalized double, representing the default parameter value.
122     abstract double getNormalizedDefault();
123 
124     /// Returns: A string associated with the normalized value.
125     abstract void stringFromNormalizedValue(double normalizedValue, char* buffer, size_t len);
126 
127     /// Returns: A normalized value associated with the string.
128     /// Can throw Exceptions.
129     abstract bool normalizedValueFromString(const(char)[] valueString, out double result);
130 
131     /// Returns: `true` if the parameters has only discrete values, `false` if continuous.
132     abstract bool isDiscrete();
133 
134     ~this()
135     {
136         _valueMutex.destroy();
137         debug assert(_editCount == 0);
138     }
139 
140 protected:
141 
142     this(int index, string name, string label)
143     {
144         _client = null;
145         _name = name;
146         _label = label;
147         _index = index;
148         _valueMutex = makeMutex();
149         _listeners = makeVec!IParameterListener();
150     }
151 
152     /// From a normalized double, set the parameter value.
153     /// No guarantee at all that getNormalized will return the same,
154     /// because this value is rounded to fit.
155     abstract void setNormalized(double hostValue);
156 
157     /// Display parameter (without label). This always adds a terminal zero within `numBytes`.
158     abstract void toStringN(char* buffer, size_t numBytes);
159 
160     void notifyListeners()
161     {
162         foreach(listener; _listeners)
163             listener.onParameterChanged(this);
164     }
165 
166     void checkBeingEdited()
167     {
168         // If you fail here, you have changed the value of a Parameter from the UI
169         // without enclosing within a pair of `beginParamEdit()`/`endParamEdit()`.
170         // This will cause some hosts like Apple Logic not to record automation.
171         //
172         // When setting a Parameter from an UI widget, it's important to call `beginParamEdit()`
173         // and `endParamEdit()` too.
174         debug assert(_editCount > 0);
175     }
176 
177 package:
178 
179     /// Parameters are owned by a client, this is used to make them refer back to it.
180     void setClientReference(Client client)
181     {
182         _client = client;
183     }
184 
185 private:
186 
187     /// weak reference to parameter holder, set after parameter creation
188     Client _client;
189 
190     int _index;
191     string _name;
192     string _label;
193     Vec!IParameterListener _listeners;
194 
195     // Current number of calls into `beginParamEdit()`/`endParamEdit()` pair.
196     // Only checked in debug mode.
197     debug int _editCount = 0;
198 
199     UncheckedMutex _valueMutex;
200 }
201 
202 /// Parameter listeners are called whenever a parameter is changed from the host POV.
203 /// Intended making GUI controls call `setDirty()` and move with automation.
204 interface IParameterListener
205 {
206 nothrow:
207 @nogc:
208 
209     /// Called when a parameter value was changed
210     /// You'll probably want to call `setDirtyWhole()` or `setDirty()` in it
211     /// to make the graphics respond to host changing a parameter.
212     void onParameterChanged(Parameter sender);
213 
214     /// Called when a parameter value start being changed due to an UI element
215     void onBeginParameterEdit(Parameter sender);
216 
217     /// Called when a parameter value stops being changed
218     void onEndParameterEdit(Parameter sender);
219 }
220 
221 
222 /// A boolean parameter
223 class BoolParameter : Parameter
224 {
225 public:
226     this(int index, string name, bool defaultValue) nothrow @nogc
227     {
228         super(index, name, "");
229         _value = defaultValue;
230         _defaultValue = defaultValue;
231     }
232 
233     override void setNormalized(double hostValue)
234     {
235         _valueMutex.lock();
236         bool newValue = (hostValue >= 0.5);
237         atomicStore(_value, newValue);
238         _valueMutex.unlock();
239     }
240 
241     override double getNormalized() 
242     {
243         return value() ? 1.0 : 0.0;
244     }
245 
246     override double getNormalizedDefault() 
247     {
248         return _defaultValue ? 1.0 : 0.0;
249     }
250 
251     override void toStringN(char* buffer, size_t numBytes)
252     {
253         bool v = value();
254 
255         if (v)
256             snprintf(buffer, numBytes, "yes");
257         else
258             snprintf(buffer, numBytes, "no");
259     }
260 
261     /// Returns: A string associated with the normalized normalized.
262     override void stringFromNormalizedValue(double normalizedValue, char* buffer, size_t len)
263     {
264         bool value = (normalizedValue >= 0.5);
265         if (value)
266             snprintf(buffer, len, "yes");
267         else
268             snprintf(buffer, len, "no");
269     }
270 
271     /// Returns: A normalized normalized associated with the string.
272     override bool normalizedValueFromString(const(char)[] valueString, out double result)
273     {
274         if (valueString == "yes")
275         {
276             result = 1;
277             return true;
278         }
279         else if (valueString == "no")
280         {
281             result = 0;
282             return true;
283         }
284         else
285             return false;
286     }
287 
288     override bool isDiscrete() 
289     {
290         return true;
291     }
292     
293     /// Toggles the parameter value from the UI thread.
294     final void toggleFromGUI() nothrow @nogc
295     {
296         setFromGUI(!value());
297     }
298 
299     /// Sets the parameter value from the UI thread.
300     final void setFromGUI(bool newValue) nothrow @nogc
301     {
302         checkBeingEdited();
303         _valueMutex.lock();
304         atomicStore(_value, newValue);
305         double normalized = getNormalized();
306         _valueMutex.unlock();
307 
308         _client.hostCommand().paramAutomate(_index, normalized);
309         notifyListeners();
310     }
311 
312     /// Gets current value.
313     final bool value() nothrow @nogc
314     {
315         bool v = void;
316         _valueMutex.lock();
317         v = atomicLoad!(MemoryOrder.raw)(_value); // already sequenced by mutex locks
318         _valueMutex.unlock();
319         return v;
320     }
321 
322     /// Same as value but doesn't use locking, and doesn't use ordering.
323     /// Which make it a better fit for the audio thread.
324     final bool valueAtomic() nothrow @nogc
325     {
326         return atomicLoad!(MemoryOrder.raw)(_value);
327     }
328 
329     /// Returns: default value.
330     final bool defaultValue() pure const nothrow @nogc
331     {
332         return _defaultValue;
333     }
334 
335 private:
336     shared(bool) _value;
337     bool _defaultValue;
338 }
339 
340 /// An integer parameter
341 class IntegerParameter : Parameter
342 {
343 public:
344     this(int index, string name, string label, int min = 0, int max = 1, int defaultValue = 0) nothrow @nogc
345     {
346         super(index, name, label);
347         _name = name;
348         _value = _defaultValue = clampValue!int(defaultValue, min, max);
349         _min = min;
350         _max = max;
351     }
352 
353     override void setNormalized(double hostValue)
354     {
355         int newValue = fromNormalized(hostValue);
356         _valueMutex.lock();
357         atomicStore(_value, newValue);
358         _valueMutex.unlock();
359     }
360 
361     override double getNormalized()
362     {
363         int v = value();
364         double normalized = toNormalized(value());
365         return normalized;
366     }
367 
368     override double getNormalizedDefault()
369     {
370         double normalized = toNormalized(_defaultValue);
371         return normalized;
372     }
373 
374     override void toStringN(char* buffer, size_t numBytes)
375     {
376         int v =  value();
377         snprintf(buffer, numBytes, "%d", v);
378     }
379 
380     override void stringFromNormalizedValue(double normalizedValue, char* buffer, size_t len)
381     {
382         int denorm = fromNormalized(normalizedValue);
383         snprintf(buffer, len, "%d", denorm);
384     }
385 
386     override bool normalizedValueFromString(const(char)[] valueString, out double result)
387     {
388         if (valueString.length > 63)
389             return false;
390 
391         // Because the input string is not zero-terminated
392         char[64] buf;
393         snprintf(buf.ptr, buf.length, "%.*s", valueString.length, valueString.ptr);
394 
395         int denorm;
396         if (1 == sscanf(buf.ptr, "%d", denorm))
397         {
398             result = toNormalized(denorm);
399             return true;
400         }
401         else
402             return false;
403     }
404 
405     override bool isDiscrete() 
406     {
407         return true;
408     }
409 
410     /// Gets the current parameter value.
411     final int value() nothrow @nogc
412     {
413         int v = void;
414         _valueMutex.lock();
415         v = atomicLoad!(MemoryOrder.raw)(_value); // already sequenced by mutex locks
416         _valueMutex.unlock();
417         return v;
418     }
419 
420     /// Same as value but doesn't use locking, and doesn't use ordering.
421     /// Which make it a better fit for the audio thread.
422     final int valueAtomic() nothrow @nogc
423     {
424         return atomicLoad!(MemoryOrder.raw)(_value);
425     }
426 
427     final void setFromGUI(int value) nothrow @nogc
428     {
429         checkBeingEdited();
430         if (value < _min)
431             value = _min;
432         if (value > _max)
433             value = _max;
434 
435         _valueMutex.lock();
436         atomicStore(_value, value);
437         double normalized = getNormalized();
438         _valueMutex.unlock();
439 
440         _client.hostCommand().paramAutomate(_index, normalized);
441         notifyListeners();
442     }
443 
444     final void setFromGUINormalized(double normalizedValue) nothrow @nogc
445     {
446         assert(normalizedValue >= 0 && normalizedValue <= 1);
447         setFromGUI(fromNormalized(normalizedValue));
448     }
449 
450     /// Returns: minimum possible values.
451     final int minValue() pure const nothrow @nogc
452     {
453         return _min;
454     }
455 
456     /// Returns: maximum possible values.
457     final int maxValue() pure const nothrow @nogc
458     {
459         return _max;
460     }
461 
462     /// Returns: number of possible values.
463     final int numValues() pure const nothrow @nogc
464     {
465         return 1 + _max - _min;
466     }
467 
468     /// Returns: default value.
469     final int defaultValue() pure const nothrow @nogc
470     {
471         return _defaultValue;
472     }
473 
474 private:
475     shared(int) _value;
476     int _min;
477     int _max;
478     int _defaultValue;
479 
480     final int fromNormalized(double normalizedValue) nothrow @nogc
481     {
482         double mapped = _min + (_max - _min) * normalizedValue;
483 
484         // slightly incorrect rounding, but lround is crashing
485         int rounded = void;
486         if (mapped >= 0)
487             rounded = cast(int)(0.5f + mapped);
488         else
489             rounded = cast(int)(-0.5f + mapped);
490 
491         return clampValue!int(rounded, _min, _max);
492     }
493 
494     final double toNormalized(int value) nothrow @nogc
495     {
496         return clampValue!double( (cast(double)value - _min) / (_max - _min), 0.0, 1.0);
497     }
498 }
499 
500 class EnumParameter : IntegerParameter
501 {
502 public:
503     this(int index, string name, const(string[]) possibleValues, int defaultValue = 0) nothrow @nogc
504     {
505         super(index, name, "", 0, cast(int)(possibleValues.length) - 1, defaultValue);
506 
507         _possibleValues = possibleValues;
508     }
509 
510     override void toStringN(char* buffer, size_t numBytes)
511     {
512         int v = value();
513         int toCopy = max(0, min( cast(int)(numBytes) - 1, cast(int)(_possibleValues[v].length)));
514         memcpy(buffer, _possibleValues[v].ptr, toCopy);
515         // add terminal zero
516         if (numBytes > 0)
517             buffer[toCopy] = '\0';
518     }
519 
520     override void stringFromNormalizedValue(double normalizedValue, char* buffer, size_t len)
521     {
522         const(char[]) valueLabel = _possibleValues[ fromNormalized(normalizedValue) ];
523         snprintf(buffer, len, "%.*s", valueLabel.length, valueLabel.ptr);
524     }
525 
526     override bool normalizedValueFromString(const(char)[] valueString, out double result)
527     {
528         foreach(int i; 0..cast(int)(_possibleValues.length))
529             if (_possibleValues[i] == valueString)
530             {
531                 result = toNormalized(i);
532                 return true;
533             }
534 
535         return false;
536     }
537 
538     final string getValueString(int n) nothrow @nogc
539     {
540         return _possibleValues[n];
541     }
542 
543 private:
544     const(string[]) _possibleValues;
545 }
546 
547 private
548 {
549     T clampValue(T)(T x, T min, T max) pure nothrow @nogc
550     {
551         if (x < min)
552             return min;
553         else if (x > max)
554             return max;
555         else
556             return x;
557     }
558 }
559 
560 /// A float parameter
561 /// This is an abstract class, mapping from normalized to parmeter values is left to the user.
562 class FloatParameter : Parameter
563 {
564 public:
565     this(int index, string name, string label, double min, double max, double defaultValue) nothrow @nogc
566     {
567         super(index, name, label);
568 
569         // If you fail in this assertion, this means your default value is out of range.
570         assert(defaultValue >= min && defaultValue <= max);
571 
572         _defaultValue = defaultValue;
573         _name = name;
574         _value = _defaultValue;
575         _min = min;
576         _max = max;
577     }
578 
579     /// Gets current value.
580     final double value() nothrow @nogc
581     {
582         double v = void;
583         _valueMutex.lock();
584         v = atomicLoad!(MemoryOrder.raw)(_value); // already sequenced by mutex locks
585         _valueMutex.unlock();
586         return v;
587     }
588 
589     /// Same as value but doesn't use locking, and doesn't use ordering.
590     /// Which make it a better fit for the audio thread.
591     final double valueAtomic() nothrow @nogc
592     {
593         return atomicLoad!(MemoryOrder.raw)(_value);
594     }
595 
596     final double minValue() pure const nothrow @nogc
597     {
598         return _min;
599     }
600 
601     final double maxValue() pure const nothrow @nogc
602     {
603         return _max;
604     }
605 
606     final double defaultValue() pure const nothrow @nogc
607     {
608         return _defaultValue;
609     }
610 
611     final void setFromGUINormalized(double normalizedValue) nothrow @nogc
612     {
613         assert(normalizedValue >= 0 && normalizedValue <= 1);
614         setFromGUI(fromNormalized(normalizedValue));
615     }
616 
617     /// Sets the number of decimal digits after the dot to be displayed.
618     final void setDecimalPrecision(int digits) nothrow @nogc
619     {
620         assert(digits >= 0);
621         assert(digits <= 9);
622         _formatString[3] = cast(char)('0' + digits);
623     }
624 
625     final void setFromGUI(double value) nothrow @nogc
626     {
627         checkBeingEdited();
628         if (value < _min)
629             value = _min;
630         if (value > _max)
631             value = _max;
632 
633         _valueMutex.lock();
634         atomicStore(_value, value);
635         double normalized = getNormalized();
636         _valueMutex.unlock();
637 
638         _client.hostCommand().paramAutomate(_index, normalized);
639         notifyListeners();
640     }
641 
642     override void setNormalized(double hostValue)
643     {
644         double v = fromNormalized(hostValue);
645         _valueMutex.lock();
646         atomicStore(_value, v);
647         _valueMutex.unlock();
648     }
649 
650     override double getNormalized()
651     {
652         return toNormalized(value());
653     }
654 
655     override double getNormalizedDefault() 
656     {
657         return toNormalized(_defaultValue);
658     }
659 
660     override void toStringN(char* buffer, size_t numBytes)
661     {
662         snprintf(buffer, numBytes, _formatString.ptr, value());
663     }
664 
665     override void stringFromNormalizedValue(double normalizedValue, char* buffer, size_t len)
666     {
667         double denorm = fromNormalized(normalizedValue);
668         snprintf(buffer, len, _formatString.ptr, denorm);
669     }
670 
671     override bool normalizedValueFromString(const(char)[] valueString, out double result)
672     {
673         if (valueString.length > 63)
674             return false;
675 
676         // Because the input string is not zero-terminated
677         char[64] buf;
678         snprintf(buf.ptr, buf.length, "%.*s", valueString.length, valueString.ptr);
679 
680         int denorm;
681         if (1 == sscanf(buf.ptr, "%f", denorm))
682         {
683             result = toNormalized(denorm);
684             return true;
685         }
686         else
687             return false;
688     }
689 
690     override bool isDiscrete() 
691     {
692         return false; // continous
693     }
694 
695     /// Override it to specify mapping from parameter values to normalized [0..1]
696     abstract double toNormalized(double value) nothrow @nogc;
697 
698     /// Override it to specify mapping from normalized [0..1] to parameter value
699     abstract double fromNormalized(double value) nothrow @nogc;
700 
701 private:
702     shared(double) _value;
703     double _min;
704     double _max;
705     double _defaultValue;
706 
707     // format string for string conversion, is overwritten by `setDecimalPrecision`.
708     char[6] _formatString = "%2.2f"; 
709 }
710 
711 /// Linear-mapped float parameter (eg: dry/wet)
712 class LinearFloatParameter : FloatParameter
713 {
714     this(int index, string name, string label, float min, float max, float defaultValue) nothrow @nogc
715     {
716         super(index, name, label, min, max, defaultValue);
717     }
718 
719     override double toNormalized(double value)
720     {
721         return clampValue!double( (value - _min) / (_max - _min), 0.0, 1.0);
722     }
723 
724     override double fromNormalized(double normalizedValue)
725     {
726         return clampValue!double(_min + (_max - _min) * normalizedValue, _min, _max);
727     }
728 }
729 
730 /// Float parameter following an exponential type of mapping (eg: cutoff frequency)
731 class LogFloatParameter : FloatParameter
732 {
733     this(int index, string name, string label, double min, double max, double defaultValue) nothrow @nogc
734     {
735         assert(min > 0 && max > 0);
736         super(index, name, label, min, max, defaultValue);
737     }
738 
739     override double toNormalized(double value)
740     {
741         double result = log(value / _min) / log(_max / _min);
742         if (result < 0)
743             result = 0;
744         if (result > 1)
745             result = 1;
746         return result;
747     }
748 
749     override double fromNormalized(double normalizedValue)
750     {
751         return _min * exp(normalizedValue * log(_max / _min));
752     }
753 }
754 
755 /// A parameter with [-inf to value] dB log mapping
756 class GainParameter : FloatParameter
757 {
758     this(int index, string name, double max, double defaultValue, double shape = 2.0) nothrow @nogc
759     {
760         super(index, name, "dB", -double.infinity, max, defaultValue);
761         _shape = shape;
762         setDecimalPrecision(1);
763     }
764 
765     override double toNormalized(double value)
766     {
767         double maxAmplitude = deciBelToFloat(_max);
768         double result = ( deciBelToFloat(value) / maxAmplitude ) ^^ (1 / _shape);
769         if (result < 0)
770             result = 0;
771         if (result > 1)
772             result = 1;
773         assert(isFinite(result));
774         return result;
775     }
776 
777     override double fromNormalized(double normalizedValue)
778     {
779         return floatToDeciBel(  (normalizedValue ^^ _shape) * deciBelToFloat(_max));
780     }
781 
782 private:
783     double _shape;
784 }
785 
786 /// Float parameter following a x^N type mapping (eg: something that doesn't fit in the other categories)
787 class PowFloatParameter : FloatParameter
788 {
789     this(int index, string name, string label, double min, double max, double defaultValue, double shape) nothrow @nogc
790     {
791         super(index, name, label, min, max, defaultValue);
792         _shape = shape;
793     }
794 
795     override double toNormalized(double value)
796     {
797         double result = clampValue!double( (value - _min) / (_max - _min), 0.0, 1.0) ^^ (1 / _shape);
798         assert(result >= 0 && result <= 1);
799         return result;
800     }
801 
802     override double fromNormalized(double normalizedValue)
803     {
804         double v = _min + (normalizedValue ^^ _shape) * (_max - _min);
805         return clampValue!double(v, _min, _max);
806     }
807 
808 private:
809     double _shape;
810 }