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
8 information.
9 This software is provided 'as-is', without any express or implied warranty.
10 In no event will the authors be held liable for any damages arising from the
11 use of this software.
12 
13 Permission is granted to anyone to use this software for any purpose, including
14 commercial applications, and to alter it and redistribute it freely, subject to
15 the following restrictions:
16 
17 1. The origin of this software must not be misrepresented; you must not claim 
18    that you wrote the original software. If you use this software in a product, 
19    an acknowledgment in the product documentation would be appreciated but is 
20    not required.
21 1. Altered source versions must be plainly marked as such, and must not be 
22    misrepresented as being the original software.
23 1. This notice may not be removed or altered from any source distribution.
24 */
25 /**
26     Definitions of plug-in `Parameter`, and its many variants.
27 */
28 module dplug.client.params;
29 
30 import core.atomic;
31 import core.stdc.stdio;
32 import core.stdc.string;
33 
34 import std.math: isNaN, log, exp, isFinite;
35 
36 import dplug.core.math;
37 import dplug.core.sync;
38 import dplug.core.nogc;
39 import dplug.core.vec;
40 import dplug.core.string;
41 import dplug.client.client;
42 
43 /// Parameter listeners are called whenever:
44 /// - a parameter is changed, 
45 /// - a parameter starts/stops being edited,
46 /// - a parameter starts/stops being hovered by mouse
47 ///
48 /// Most common use case is for UI controls repaints (the `setDirtyXXX` calls)
49 interface IParameterListener
50 {
51 nothrow:
52 @nogc:
53 
54     /// A parameter value was changed, from the UI or the host (automation).
55     ///
56     /// Suggestion: Maybe call `setDirtyWhole()`/`setDirty()` in it to request
57     ///             a graphic repaint.
58     ///
59     /// WARNING: this WILL be called from any thread, including the audio 
60     ///          thread.
61     void onParameterChanged(Parameter sender);
62 
63     /// A parameter value _starts_ being changed due to an UI element.
64     void onBeginParameterEdit(Parameter sender);
65 
66     /// Called when a parameter value _stops_ being changed.
67     void onEndParameterEdit(Parameter sender);
68 
69     /// Called when a widget that can changes this parameter is mouseover, and wants to signal
70     /// that to the listeners.
71     ///
72     /// `onBeginParameterHover`/`onEndParameterHover` is called by widgets from an UI thread 
73     /// (typically on `onMouseEnter`/`onMouseLeave`) when the mouse has entered a widget that 
74     /// could change its parameter.
75     ///
76     /// It is useful be display the parameter value or related tooltips elsewhere in the UI,
77     /// in another widget.
78     ///
79     /// To dispatch such messages to listeners, see `Parameter.
80     ///
81     /// Not all widgets will want to signal this though, for example a widget that handles plenty
82     /// of Parameters will not want to signal them all the time.
83     ///
84     /// If `onBeginParameterHover` was ever called, then the same widget should also call 
85     /// `onEndParameterHover` when sensible.
86     void onBeginParameterHover(Parameter sender);
87     ///ditto
88     void onEndParameterHover(Parameter sender);
89 }
90 
91 
92 
93 /// Plugin parameter.
94 /// Implement the Observer pattern for UI support.
95 /// Note: Recursive mutexes are needed here because `getNormalized()`
96 /// could need locking an already taken mutex.
97 ///
98 /// Listener patter:
99 ///     Every `Parameter` maintain a list of `IParameterListener`.
100 ///     This is typically used by UI elements to update with parameters 
101 ///     changes from all 
102 ///     kinds of sources (UI itself, host automation...).
103 ///     But they are not necessarily UI elements.
104 ///
105 /// FUTURE: easier to make widget if every `Parameter` has a 
106 ///         `setFromGUINormalized` call.
107 class Parameter
108 {
109 public:
110 nothrow:
111 @nogc:
112 
113     /// Adds a parameter listener.
114     void addListener(IParameterListener listener)
115     {
116         _listeners.pushBack(listener);
117     }
118 
119     /// Removes a parameter listener.
120     void removeListener(IParameterListener listener)
121     {
122         int index = _listeners.indexOf(listener);
123         if (index != -1)
124             _listeners.removeAndReplaceByLastElement(index);
125     }
126 
127     /// Returns: Parameters name. Displayed when the plug-in has no UI.
128     string name() pure const
129     {
130         return _name;
131     }
132 
133     /// Returns: Parameters unit label.
134     string label() pure const
135     {
136         return _label;
137     }
138 
139     /// Returns: Index of parameter in the parameter list.
140     int index() pure const
141     {
142         return _index;
143     }
144 
145     /// Returns: Whether parameter is automatable.
146     bool isAutomatable() pure const
147     {
148         return _isAutomatable;
149     }
150 
151     /// Makes parameter non-automatable.
152     Parameter nonAutomatable()
153     {
154         _isAutomatable = false;
155         return this;
156     }
157 
158     /// From a normalized double [0..1], set the parameter value.
159     /// This is a Dplug internal call, not for plug-in code.
160     void setFromHost(double hostValue)
161     {
162         // If edited by a widget, REFUSE host changes, since they could be
163         // "in the past" and we know we want newer values anyway.
164         if (isEdited())
165             return; 
166 
167         setNormalized(hostValue);
168         notifyListeners();
169     }
170 
171     /// Returns: A normalized double [0..1], represents parameter value.
172     /// This is a Dplug internal call, not for plug-in code.
173     double getForHost()
174     {
175         return getNormalized();
176     }
177 
178     /// Output a string representation of a `Parameter`.
179     void toDisplayN(char* buffer, size_t numBytes)
180     {
181         toStringN(buffer, numBytes);
182     }
183 
184     /// Warns the host that a parameter will be edited.
185     /// Should only ever be called from the UI thread.
186     void beginParamEdit()
187     {
188         atomicOp!"+="(_editCount, 1);
189         _client.hostCommand().beginParamEdit(_index);
190         foreach(listener; _listeners)
191             listener.onBeginParameterEdit(this);
192     }
193 
194     /// Warns the host that a parameter has finished being edited.
195     /// Should only ever be called from the UI thread.
196     void endParamEdit()
197     {
198         _client.hostCommand().endParamEdit(_index);
199         foreach(listener; _listeners)
200             listener.onEndParameterEdit(this);
201         atomicOp!"-="(_editCount, 1);
202     }
203 
204     /// Warns the listeners that a parameter is being hovered in the UI.
205     /// Should only ever be called from the UI thread.
206     ///
207     /// This doesn't communicate anything to the host.
208     ///
209     /// Note: Widgets are not forced to signal this if they do not want other
210     /// widgets to display a parameter value.
211     void beginParamHover()
212     {
213         debug _hoverCount += 1;
214         foreach(listener; _listeners)
215             listener.onBeginParameterHover(this);
216     }
217 
218     /// Warns the listeners that a parameter has finished being hovered in the UI.
219     /// Should only ever be called from the UI thread.
220     /// This doesn't communicate anything to the host.
221     ///
222     /// Note: Widgets are not forced to signal this if they do not want other
223     /// widgets to display a parameter value. 
224     /// 
225     /// Warning: calls to `beginParamHover`/`endParamHover` must be balanced.
226     void endParamHover()
227     {
228         foreach(listener; _listeners)
229             listener.onEndParameterHover(this);
230         debug _hoverCount -= 1;
231     }
232 
233     /// Returns: A normalized double, representing the parameter value.
234     abstract double getNormalized();
235 
236     /// Returns: A normalized double, representing the default parameter value.
237     abstract double getNormalizedDefault();
238 
239     /// Returns: A string associated with the normalized value.
240     abstract void stringFromNormalizedValue(double normalizedValue, 
241                                             char* buffer, 
242                                             size_t len);
243 
244     /// Returns: A normalized value associated with the string.
245     abstract bool normalizedValueFromString(const(char)[] valueString, out double result);
246 
247     /// Returns: `true` if the parameters has only discrete values, `false` if continuous.
248     abstract bool isDiscrete();
249 
250     ~this()
251     {
252         _valueMutex.destroy();
253 
254         // If you fail here, it means your calls to beginParamEdit and endParamEdit 
255         // were not balanced correctly.
256         debug assert(atomicLoad(_editCount) == 0);
257 
258         // If you fail here, it means your calls to beginParamHover and endParamHover
259         // were not balanced correctly.
260         debug assert(_hoverCount == 0);
261     }
262 
263 protected:
264 
265     this(int index, string name, string label)
266     {
267         _client = null;
268         _name = name;
269         _label = label;
270         _index = index;
271         _valueMutex = makeMutex();
272         _listeners = makeVec!IParameterListener();
273     }
274 
275     /// From a normalized double, set the parameter value.
276     /// No guarantee at all that getNormalized will return the same,
277     /// because this value is rounded to fit.
278     abstract void setNormalized(double hostValue);
279 
280     /// Display parameter (without label). This always adds a terminal zero within `numBytes`.
281     abstract void toStringN(char* buffer, size_t numBytes);
282 
283     final void notifyListeners()
284     {
285         foreach(listener; _listeners)
286             listener.onParameterChanged(this);
287     }
288 
289     final void checkBeingEdited()
290     {
291         // If you fail here, you have changed the value of a Parameter from the UI
292         // without enclosing within a pair of `beginParamEdit()`/`endParamEdit()`.
293         // This will cause some hosts like Apple Logic not to record automation.
294         //
295         // When setting a Parameter from an UI widget, it's important to call `beginParamEdit()`
296         // and `endParamEdit()` too.
297         debug assert(isEdited());
298     }
299 
300     final bool isEdited()
301     {
302         return atomicLoad(_editCount) > 0;
303     }
304 
305 package:
306 
307     /// Parameters are owned by a client, this is used to make them refer back to it.
308     void setClientReference(Client client)
309     {
310         _client = client;
311     }
312 
313 private:
314 
315     /// weak reference to parameter holder, set after parameter creation
316     Client _client;
317 
318     int _index;
319     string _name;
320     string _label;
321     /// Parameter is automatable by default.
322     bool _isAutomatable = true;
323 
324     /// The list of active `IParameterListener` to receive parameter changes.
325     Vec!IParameterListener _listeners;
326 
327     // Current number of calls into `beginParamEdit()`/`endParamEdit()` pair.
328     // Only checked in debug mode.
329     shared(int) _editCount = 0; // if > 0, the UI is editing this parameter
330 
331     // Current number of calls into `beginParamHover()`/`endParamHover()` pair.
332     // Only checked in debug mode.
333     debug int _hoverCount = 0;
334 
335     UncheckedMutex _valueMutex;
336 }
337 
338 
339 /// A boolean parameter
340 class BoolParameter : Parameter
341 {
342 public:
343     this(int index, string name, bool defaultValue) nothrow @nogc
344     {
345         super(index, name, "");
346         _value = defaultValue;
347         _defaultValue = defaultValue;
348     }
349 
350     override void setNormalized(double hostValue)
351     {
352         _valueMutex.lock();
353         bool newValue = (hostValue >= 0.5);
354         atomicStore(_value, newValue);
355         _valueMutex.unlock();
356     }
357 
358     override double getNormalized() 
359     {
360         return value() ? 1.0 : 0.0;
361     }
362 
363     override double getNormalizedDefault() 
364     {
365         return _defaultValue ? 1.0 : 0.0;
366     }
367 
368     override void toStringN(char* buffer, size_t numBytes)
369     {
370         bool v = value();
371 
372         if (v)
373             snprintf(buffer, numBytes, "yes");
374         else
375             snprintf(buffer, numBytes, "no");
376     }
377 
378     /// Returns: A string associated with the normalized normalized.
379     override void stringFromNormalizedValue(double normalizedValue, char* buffer, size_t len)
380     {
381         bool value = (normalizedValue >= 0.5);
382         if (value)
383             snprintf(buffer, len, "yes");
384         else
385             snprintf(buffer, len, "no");
386     }
387 
388     /// Returns: A normalized normalized associated with the string.
389     override bool normalizedValueFromString(const(char)[] valueString, out double result)
390     {
391         if (valueString == "yes")
392         {
393             result = 1;
394             return true;
395         }
396         else if (valueString == "no")
397         {
398             result = 0;
399             return true;
400         }
401         else
402             return false;
403     }
404 
405     override bool isDiscrete() 
406     {
407         return true;
408     }
409     
410     /// Toggles the parameter value from the UI thread.
411     final void toggleFromGUI() nothrow @nogc
412     {
413         setFromGUI(!value());
414     }
415 
416     /// Sets the parameter value from the UI thread.
417     final void setFromGUI(bool newValue) nothrow @nogc
418     {
419         checkBeingEdited();
420         _valueMutex.lock();
421         atomicStore(_value, newValue);
422         double normalized = getNormalized();
423         _valueMutex.unlock();
424 
425         _client.hostCommand().paramAutomate(_index, normalized);
426         notifyListeners();
427     }
428 
429     /// Sets the value of the parameter from UI, using a normalized value.
430     /// Note: If `normValue` is < 0.5, this is set to false.
431     ///       If `normValue` is >= 0.5, this is set to true.
432     final void setFromGUINormalized(double normValue) nothrow @nogc
433     {
434         assert(!isNaN(normValue));
435         bool val = (normValue >= 0.5);
436         setFromGUI(val);
437     }
438 
439     /// Get current value.
440     final bool value() nothrow @nogc
441     {
442         bool v = void;
443         _valueMutex.lock();
444         v = atomicLoad!(MemoryOrder.raw)(_value); // already sequenced by mutex locks
445         _valueMutex.unlock();
446         return v;
447     }
448 
449     /// Get current value but doesn't use locking, using the `raw` memory order.
450     /// Which might make it a better fit for the audio thread.
451     /// The various `readParam!T` functions use that.²
452     final bool valueAtomic() nothrow @nogc
453     {
454         return atomicLoad!(MemoryOrder.raw)(_value);
455     }
456 
457     /// Returns: default value.
458     final bool defaultValue() pure const nothrow @nogc
459     {
460         return _defaultValue;
461     }
462 
463 private:
464     shared(bool) _value;
465     bool _defaultValue;
466 }
467 
468 /// An integer parameter
469 class IntegerParameter : Parameter
470 {
471 public:
472     this(int index, string name, string label, int min = 0, int max = 1, int defaultValue = 0) nothrow @nogc
473     {
474         super(index, name, label);
475         _name = name;
476         int clamped = defaultValue;
477         if (clamped < min) 
478             clamped = min;
479         if (clamped > max) 
480             clamped = max;
481         _value = clamped;
482         _defaultValue = clamped;
483         _min = min;
484         _max = max;
485     }
486 
487     override void setNormalized(double hostValue)
488     {
489         int newValue = fromNormalized(hostValue);
490         _valueMutex.lock();
491         atomicStore(_value, newValue);
492         _valueMutex.unlock();
493     }
494 
495     override double getNormalized()
496     {
497         double normalized = toNormalized(value());
498         return normalized;
499     }
500 
501     override double getNormalizedDefault()
502     {
503         double normalized = toNormalized(_defaultValue);
504         return normalized;
505     }
506 
507     override void toStringN(char* buffer, size_t numBytes)
508     {
509         int v =  value();
510         snprintf(buffer, numBytes, "%d", v);
511     }
512 
513     override void stringFromNormalizedValue(double normalizedValue, char* buffer, size_t len)
514     {
515         int denorm = fromNormalized(normalizedValue);
516         snprintf(buffer, len, "%d", denorm);
517     }
518 
519     override bool normalizedValueFromString(const(char)[] valueString, out double result)
520     {
521         if (valueString.length > 127)
522             return false;
523 
524         // Because the input string is not zero-terminated
525         char[128] buf;
526         snprintf(buf.ptr, buf.length, "%.*s", cast(int)(valueString.length), valueString.ptr);
527 
528         bool err = false;
529         int denorm = convertStringToInteger(buf.ptr, false, &err);
530         if (err)
531             return false;
532 
533         result = toNormalized(denorm);
534         return true;
535     }
536 
537     override bool isDiscrete() 
538     {
539         return true;
540     }
541 
542     /// Gets the current parameter value.
543     final int value() nothrow @nogc
544     {
545         int v = void;
546         _valueMutex.lock();
547         v = atomicLoad!(MemoryOrder.raw)(_value); // already sequenced by mutex locks
548         _valueMutex.unlock();
549         return v;
550     }
551 
552     /// Same as value but doesn't use locking, and doesn't use ordering.
553     /// Which make it a better fit for the audio thread.
554     final int valueAtomic() nothrow @nogc
555     {
556         return atomicLoad!(MemoryOrder.raw)(_value);
557     }
558 
559     /// Sets the parameter value from the UI thread.
560     /// If the parameter is outside [min .. max] inclusive, then it is
561     /// clamped. This is not an error to do so.
562     final void setFromGUI(int value) nothrow @nogc
563     {
564         checkBeingEdited();
565 
566         if (value < _min)
567             value = _min;
568         if (value > _max)
569             value = _max;
570 
571         _valueMutex.lock();
572         atomicStore(_value, value);
573         double normalized = getNormalized();
574         _valueMutex.unlock();
575 
576         _client.hostCommand().paramAutomate(_index, normalized);
577         notifyListeners();
578     }
579 
580     /// Sets the value of the parameter from UI, using a normalized value.
581     /// Note: If `normValue` is not inside [0.0 .. 1.0], then it is clamped.
582     ///       This is not an error.
583     final void setFromGUINormalized(double normValue) nothrow @nogc
584     {
585         assert(!isNaN(normValue));
586         if (normValue < 0.0) normValue = 0.0;
587         if (normValue > 1.0) normValue = 1.0;
588         setFromGUI(fromNormalized(normValue));
589     }
590 
591     /// Returns: minimum possible values.
592     final int minValue() pure const nothrow @nogc
593     {
594         return _min;
595     }
596 
597     /// Returns: maximum possible values.
598     final int maxValue() pure const nothrow @nogc
599     {
600         return _max;
601     }
602 
603     /// Returns: number of possible values.
604     final int numValues() pure const nothrow @nogc
605     {
606         return 1 + _max - _min;
607     }
608 
609     /// Returns: default value.
610     final int defaultValue() pure const nothrow @nogc
611     {
612         return _defaultValue;
613     }
614 
615     final int fromNormalized(double normalizedValue) nothrow @nogc
616     {
617         double mapped = _min + (_max - _min) * normalizedValue;
618 
619         // BUG slightly incorrect rounding, but lround is crashing
620         int rounded;
621         if (mapped >= 0)
622             rounded = cast(int)(0.5f + mapped);
623         else
624             rounded = cast(int)(-0.5f + mapped);
625 
626         if (rounded < _min) 
627             rounded = _min;
628         if (rounded > _max)
629             rounded = _max;
630         return rounded;
631     }
632 
633     final double toNormalized(int value) nothrow @nogc
634     {
635         double v = (cast(double)value - _min) / (_max - _min);
636         if (v < 0.0)
637             v = 0.0;
638         if (v > 1.0)
639             v = 1.0;
640         return v;
641     }
642 
643 private:
644     shared(int) _value;
645     int _min;
646     int _max;
647     int _defaultValue;
648 }
649 
650 class EnumParameter : IntegerParameter
651 {
652 public:
653     this(int index, string name, const(string[]) possibleValues, int defaultValue = 0) nothrow @nogc
654     {
655         super(index, name, "", 0, cast(int)(possibleValues.length) - 1, defaultValue);
656 
657         _possibleValues = possibleValues;
658     }
659 
660     override void toStringN(char* buffer, size_t numBytes)
661     {
662         int v = value();
663         int toCopy = cast(int)(_possibleValues[v].length);
664         int avail = cast(int)(numBytes) - 1;
665         if (toCopy > avail)
666             toCopy = avail;
667         if (toCopy < 0)
668             toCopy = 0;
669         memcpy(buffer, _possibleValues[v].ptr, toCopy); // memcpy OK
670         // add terminal zero
671         if (numBytes > 0)
672             buffer[toCopy] = '\0';
673     }
674 
675     override void stringFromNormalizedValue(double normalizedValue, char* buffer, size_t len)
676     {
677         const(char[]) valueLabel = _possibleValues[ fromNormalized(normalizedValue) ];
678         snprintf(buffer, len, "%.*s", cast(int)(valueLabel.length), valueLabel.ptr);
679     }
680 
681     override bool normalizedValueFromString(const(char)[] valueString, out double result)
682     {
683         foreach(int i; 0..cast(int)(_possibleValues.length))
684             if (_possibleValues[i] == valueString)
685             {
686                 result = toNormalized(i);
687                 return true;
688             }
689 
690         return false;
691     }
692 
693     final string getValueString(int n) nothrow @nogc
694     {
695         return _possibleValues[n];
696     }
697 
698 private:
699     const(string[]) _possibleValues;
700 }
701 
702 /// A float parameter
703 /// This is an abstract class, mapping from normalized to parmeter values is left to the user.
704 class FloatParameter : Parameter
705 {
706 public:
707     this(int index, string name, string label, double min, double max, double defaultValue) nothrow @nogc
708     {
709         super(index, name, label);
710 
711         // If you fail in this assertion, this means your default value is out of range.
712         // What to do: get back to your `buildParameters` function and give a default 
713         // in range of [min..max].
714         assert(defaultValue >= min && defaultValue <= max);
715 
716         _defaultValue = defaultValue;
717         _name = name;
718         _value = _defaultValue;
719         _min = min;
720         _max = max;
721     }
722 
723     /// Gets current value.
724     final double value() nothrow @nogc
725     {
726         double v = void;
727         _valueMutex.lock();
728         v = atomicLoad!(MemoryOrder.raw)(_value); // already sequenced by mutex locks
729         _valueMutex.unlock();
730         return v;
731     }
732 
733     /// Same as value but doesn't use locking, and doesn't use ordering.
734     /// Which make it a better fit for the audio thread.
735     final double valueAtomic() nothrow @nogc
736     {
737         return atomicLoad!(MemoryOrder.raw)(_value);
738     }
739 
740     final double minValue() pure const nothrow @nogc
741     {
742         return _min;
743     }
744 
745     final double maxValue() pure const nothrow @nogc
746     {
747         return _max;
748     }
749 
750     final double defaultValue() pure const nothrow @nogc
751     {
752         return _defaultValue;
753     }
754 
755     /// Sets the value of the parameter from UI, using a normalized value.
756     /// Note: If `normValue` is not inside [0.0 .. 1.0], then it is clamped.
757     ///       This is not an error.
758     final void setFromGUINormalized(double normValue) nothrow @nogc
759     {
760         assert(!isNaN(normValue));
761         if (normValue < 0.0) normValue = 0.0;
762         if (normValue > 1.0) normValue = 1.0;
763         setFromGUI(fromNormalized(normValue));
764     }
765 
766     /// Sets the number of decimal digits after the dot to be displayed.
767     final void setDecimalPrecision(int digits) nothrow @nogc
768     {
769         assert(digits >= 0);
770         assert(digits <= 9);
771         _formatString[3] = cast(char)('0' + digits);
772     }
773 
774     /// Helper for `setDecimalPrecision` that returns this, help when in parameter creation.
775     final FloatParameter withDecimalPrecision(int digits) nothrow @nogc
776     {
777         setDecimalPrecision(digits);
778         return this;
779     }
780 
781     /// Sets the value of the parameter from UI, using a normalized value.
782     /// Note: If `value` is not inside [min .. max], then it is clamped.
783     ///       This is not an error.
784     /// See_also: `setFromGUINormalized`
785     final void setFromGUI(double value) nothrow @nogc
786     {
787         assert(!isNaN(value));
788 
789         checkBeingEdited();
790         if (value < _min)
791             value = _min;
792         if (value > _max)
793             value = _max;
794 
795         _valueMutex.lock();
796         atomicStore(_value, value);
797         double normalized = getNormalized();
798         _valueMutex.unlock();
799 
800         _client.hostCommand().paramAutomate(_index, normalized);
801         notifyListeners();
802     }
803 
804     override void setNormalized(double hostValue)
805     {
806         double v = fromNormalized(hostValue);
807         _valueMutex.lock();
808         atomicStore(_value, v);
809         _valueMutex.unlock();
810     }
811 
812     override double getNormalized()
813     {
814         return toNormalized(value());
815     }
816 
817     override double getNormalizedDefault() 
818     {
819         return toNormalized(_defaultValue);
820     }
821 
822     override void toStringN(char* buffer, size_t numBytes)
823     {
824         snprintf(buffer, numBytes, _formatString.ptr, value());
825 
826         // DigitalMars's snprintf doesn't always add a terminal zero
827         version(DigitalMars)
828             if (numBytes > 0)
829             {
830                 buffer[numBytes-1] = '\0';
831             }
832     }
833 
834     override void stringFromNormalizedValue(double normalizedValue, char* buffer, size_t len)
835     {
836         double denorm = fromNormalized(normalizedValue);
837         snprintf(buffer, len, _formatString.ptr, denorm);
838     }
839 
840     override bool normalizedValueFromString(const(char)[] valueString, out double result)
841     {
842         if (valueString.length > 127) // ??? TODO doesn't bode well with VST3 constraints
843             return false;
844 
845         // Because the input string is not necessarily zero-terminated
846         char[128] buf;
847         snprintf(buf.ptr, buf.length, "%.*s", cast(int)(valueString.length), valueString.ptr);
848 
849         bool err = false;
850         double denorm = convertStringToDouble(buf.ptr, false, &err);
851         if (err)
852             return false; // didn't parse a double
853         result = toNormalized(denorm);
854         return true;
855     }
856 
857     override bool isDiscrete() 
858     {
859         return false; // continous
860     }
861 
862     /// Override it to specify mapping from parameter values to normalized [0..1]
863     abstract double toNormalized(double value) nothrow @nogc;
864 
865     /// Override it to specify mapping from normalized [0..1] to parameter value
866     abstract double fromNormalized(double value) nothrow @nogc;
867 
868 private:
869     shared(double) _value;
870     double _min;
871     double _max;
872     double _defaultValue;
873 
874     // format string for string conversion, is overwritten by `setDecimalPrecision`.
875     char[6] _formatString = "%2.2f"; 
876 }
877 
878 /// Linear-mapped float parameter (eg: dry/wet)
879 class LinearFloatParameter : FloatParameter
880 {
881     this(int index, string name, string label, float min, float max, float defaultValue) nothrow @nogc
882     {
883         super(index, name, label, min, max, defaultValue);
884     }
885 
886     override double toNormalized(double value)
887     {
888         double v = (value - _min) / (_max - _min);
889         if (v < 0.0)
890             v = 0.0;
891         if (v > 1.0)
892             v = 1.0;
893         return v;
894     }
895 
896     override double fromNormalized(double normalizedValue)
897     {
898         double v = _min + (_max - _min) * normalizedValue;
899         if (v < _min)
900             v = _min;
901         if (v > _max)
902             v = _max;
903         return v;
904     }
905 }
906 
907 /// Float parameter following an exponential type of mapping (eg: cutoff frequency)
908 class LogFloatParameter : FloatParameter
909 {
910     this(int index, string name, string label, double min, double max, double defaultValue) nothrow @nogc
911     {
912         assert(min > 0 && max > 0);
913         super(index, name, label, min, max, defaultValue);
914     }
915 
916     override double toNormalized(double value)
917     {
918         double result = log(value / _min) / log(_max / _min);
919         if (result < 0)
920             result = 0;
921         if (result > 1)
922             result = 1;
923         return result;
924     }
925 
926     override double fromNormalized(double normalizedValue)
927     {
928         return _min * exp(normalizedValue * log(_max / _min));
929     }
930 }
931 
932 /// A parameter with [-inf to value] dB log mapping
933 class GainParameter : FloatParameter
934 {
935     this(int index, string name, double max, double defaultValue, double shape = 2.0) nothrow @nogc
936     {
937         super(index, name, "dB", -double.infinity, max, defaultValue);
938         _shape = shape;
939         setDecimalPrecision(1);
940     }
941 
942     override double toNormalized(double value)
943     {
944         double maxAmplitude = convertDecibelToLinearGain(_max);
945         double result = ( convertDecibelToLinearGain(value) / maxAmplitude ) ^^ (1 / _shape);
946         if (result < 0)
947             result = 0;
948         if (result > 1)
949             result = 1;
950         assert(isFinite(result));
951         return result;
952     }
953 
954     override double fromNormalized(double normalizedValue)
955     {
956         return convertLinearGainToDecibel(  (normalizedValue ^^ _shape) * convertDecibelToLinearGain(_max));
957     }
958 
959 private:
960     double _shape;
961 }
962 
963 /// Float parameter following a x^N type mapping (eg: something that doesn't fit in the other categories)
964 class PowFloatParameter : FloatParameter
965 {
966     this(int index, string name, string label, double min, double max, double defaultValue, double shape) nothrow @nogc
967     {
968         super(index, name, label, min, max, defaultValue);
969         _shape = shape;
970     }
971 
972     override double toNormalized(double value)
973     {
974         double v = (value - _min) / (_max - _min);
975         if (v < 0.0)
976             v = 0.0;
977         if (v > 1.0)
978             v = 1.0;
979         v = v ^^ (1 / _shape);
980 
981         // Note: It's not entirely impossible to imagine a particular way that 1 would be exceeded, since pow
982         // is implemented with an exp approximation and a log approximation.
983         // TODO: produce ill case in isolation to see
984         assert(v >= 0 && v <= 1); // will still assert on NaN
985         return v;
986     }
987 
988     override double fromNormalized(double normalizedValue)
989     {
990         double v = _min + (normalizedValue ^^ _shape) * (_max - _min);
991          if (v < _min)
992             v = _min;
993         if (v > _max)
994             v = _max;
995         return v;
996     }
997 
998 private:
999     double _shape;
1000 }