You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
4.4KB

  1. #include <tinyexpr.h>
  2. #include <Quantity.hpp>
  3. #include <string.hpp>
  4. namespace rack {
  5. float Quantity::getDisplayValue() {
  6. return getValue();
  7. }
  8. void Quantity::setDisplayValue(float displayValue) {
  9. setValue(displayValue);
  10. }
  11. int Quantity::getDisplayPrecision() {
  12. return 5;
  13. }
  14. std::string Quantity::getDisplayValueString() {
  15. float v = getDisplayValue();
  16. if (std::isnan(v))
  17. return "NaN";
  18. return string::f("%.*g", getDisplayPrecision(), math::normalizeZero(v));
  19. }
  20. /** Build-in variables for tinyexpr
  21. Because formulas are lowercased for case-insensitivity, all variables must be lowercase.
  22. */
  23. struct TeVariable {
  24. std::string name;
  25. double value;
  26. };
  27. static std::vector<TeVariable> teVariables;
  28. static std::vector<te_variable> teVars;
  29. static void teVarsInit() {
  30. if (!teVars.empty())
  31. return;
  32. // Math variables
  33. teVariables.push_back({"inf", INFINITY});
  34. // Note names
  35. struct Note {
  36. std::string name;
  37. int semi;
  38. };
  39. const static std::vector<Note> notes = {
  40. {"c", 0},
  41. {"d", 2},
  42. {"e", 4},
  43. {"f", 5},
  44. {"g", 7},
  45. {"a", 9},
  46. {"b", 11},
  47. };
  48. for (const Note& note : notes) {
  49. // Example: c, cs (or c#), and cb
  50. teVariables.push_back({string::f("%s", note.name.c_str()), 440.f * std::pow(2.0, (note.semi - 9) / 12.f)});
  51. teVariables.push_back({string::f("%ss", note.name.c_str()), 440.f * std::pow(2.0, (note.semi - 9 + 1) / 12.f)});
  52. teVariables.push_back({string::f("%sb", note.name.c_str()), 440.f * std::pow(2.0, (note.semi - 9 - 1) / 12.f)});
  53. for (int oct = 0; oct <= 9; oct++) {
  54. // Example: c4, cs4 (or c#4), and cb4
  55. teVariables.push_back({string::f("%s%d", note.name.c_str(), oct), 440.f * std::pow(2.0, oct - 4 + (note.semi - 9) / 12.f)});
  56. teVariables.push_back({string::f("%ss%d", note.name.c_str(), oct), 440.f * std::pow(2.0, oct - 4 + (note.semi - 9 + 1) / 12.f)});
  57. teVariables.push_back({string::f("%sb%d", note.name.c_str(), oct), 440.f * std::pow(2.0, oct - 4 + (note.semi - 9 - 1) / 12.f)});
  58. }
  59. }
  60. // Build teVars from teVariables
  61. teVars.reserve(teVariables.size());
  62. for (size_t i = 0; i < teVariables.size(); i++) {
  63. teVars.push_back({teVariables[i].name.c_str(), &teVariables[i].value, TE_VARIABLE, NULL});
  64. }
  65. // Add custom functions
  66. teVars.push_back({"log2", (void*) (double(*)(double)) std::log2, TE_FUNCTION1 | TE_FLAG_PURE, NULL});
  67. }
  68. void Quantity::setDisplayValueString(std::string s) {
  69. teVarsInit();
  70. // Uppercase letters aren't needed in formulas, so convert to lowercase in case user types INF or C4.
  71. s = string::lowercase(s);
  72. // Replace "#" with "s" so note names can be written as c#.
  73. std::replace(s.begin(), s.end(), '#', 's');
  74. // Compile string with tinyexpr
  75. te_expr* expr = te_compile(s.c_str(), teVars.data(), teVars.size(), NULL);
  76. if (!expr)
  77. return;
  78. double result = te_eval(expr);
  79. te_free(expr);
  80. if (std::isnan(result))
  81. return;
  82. setDisplayValue(result);
  83. }
  84. std::string Quantity::getString() {
  85. std::string s;
  86. std::string label = getLabel();
  87. std::string valueString = getDisplayValueString() + getUnit();
  88. s += label;
  89. if (label != "" && valueString != "")
  90. s += ": ";
  91. s += valueString;
  92. return s;
  93. }
  94. void Quantity::reset() {
  95. setValue(getDefaultValue());
  96. }
  97. void Quantity::randomize() {
  98. if (isBounded())
  99. setScaledValue(random::uniform());
  100. }
  101. bool Quantity::isMin() {
  102. return getValue() <= getMinValue();
  103. }
  104. bool Quantity::isMax() {
  105. return getValue() >= getMaxValue();
  106. }
  107. void Quantity::setMin() {
  108. setValue(getMinValue());
  109. }
  110. void Quantity::setMax() {
  111. setValue(getMaxValue());
  112. }
  113. void Quantity::toggle() {
  114. setValue(isMin() ? getMaxValue() : getMinValue());
  115. }
  116. void Quantity::moveValue(float deltaValue) {
  117. setValue(getValue() + deltaValue);
  118. }
  119. float Quantity::getRange() {
  120. return getMaxValue() - getMinValue();
  121. }
  122. bool Quantity::isBounded() {
  123. return std::isfinite(getMinValue()) && std::isfinite(getMaxValue());
  124. }
  125. float Quantity::toScaled(float value) {
  126. if (!isBounded())
  127. return value;
  128. else if (getMinValue() == getMaxValue())
  129. return 0.f;
  130. else
  131. return math::rescale(value, getMinValue(), getMaxValue(), 0.f, 1.f);
  132. }
  133. float Quantity::fromScaled(float scaledValue) {
  134. if (!isBounded())
  135. return scaledValue;
  136. else
  137. return math::rescale(scaledValue, 0.f, 1.f, getMinValue(), getMaxValue());
  138. }
  139. void Quantity::setScaledValue(float scaledValue) {
  140. setValue(fromScaled(scaledValue));
  141. }
  142. float Quantity::getScaledValue() {
  143. return toScaled(getValue());
  144. }
  145. void Quantity::moveScaledValue(float deltaScaledValue) {
  146. if (!isBounded())
  147. moveValue(deltaScaledValue);
  148. else
  149. moveValue(deltaScaledValue * getRange());
  150. }
  151. } // namespace rack