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.

212 lines
5.3KB

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