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