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.

221 lines
5.5KB

  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. auto pushNoteName = [&](const std::string& name, int semi, int oct = 4) {
  50. double voltage = oct - 4 + semi / 12.0;
  51. teVariables.push_back({name, 440.0 * std::exp2(voltage - 9 / 12.0)});
  52. teVariables.push_back({name + "v", voltage});
  53. };
  54. // Example: c, cs (or c#), and cb
  55. // This overwrites Euler's number "e", but the note name is more important here, and you can type exp(1) instead.
  56. for (const Note& note : notes) {
  57. pushNoteName(string::f("%s", note.name), note.semi);
  58. pushNoteName(string::f("%ss", note.name), note.semi + 1);
  59. pushNoteName(string::f("%sb", note.name), note.semi - 1);
  60. }
  61. // Example: c4, cs4 (or c#4), and cb4
  62. for (const Note& note : notes) {
  63. for (int oct = 0; oct <= 9; oct++) {
  64. pushNoteName(string::f("%s%d", note.name, oct), note.semi, oct);
  65. pushNoteName(string::f("%ss%d", note.name, oct), note.semi + 1, oct);
  66. pushNoteName(string::f("%sb%d", note.name, oct), note.semi - 1, oct);
  67. }
  68. }
  69. // Build teVars from teVariables
  70. // After this point, the addresses of name.c_str() and values in teVariables can't be changed.
  71. teVars.reserve(teVariables.size());
  72. for (const TeVariable& teVariable : teVariables) {
  73. teVars.push_back({teVariable.name.c_str(), &teVariable.value, TE_VARIABLE, NULL});
  74. }
  75. // Add custom functions
  76. teVars.push_back({"log2", (void*) (double(*)(double)) std::log2, TE_FUNCTION1 | TE_FLAG_PURE, NULL});
  77. teVars.push_back({"gaintodb", (void*) (double(*)(double)) [](double x) -> double {
  78. return std::log10(x) * 20;
  79. }, TE_FUNCTION1 | TE_FLAG_PURE, NULL});
  80. teVars.push_back({"dbtogain", (void*) (double(*)(double)) [](double x) -> double {
  81. return std::pow(10, x / 20);
  82. }, TE_FUNCTION1 | TE_FLAG_PURE, NULL});
  83. teVars.push_back({"vtof", (void*) (double(*)(double)) [](double x) -> double {
  84. return std::pow(2, x) * dsp::FREQ_C4;
  85. }, TE_FUNCTION1 | TE_FLAG_PURE, NULL});
  86. teVars.push_back({"ftov", (void*) (double(*)(double)) [](double x) -> double {
  87. return std::log2(x / dsp::FREQ_C4);
  88. }, TE_FUNCTION1 | TE_FLAG_PURE, NULL});
  89. teVars.push_back({"vtobpm", (void*) (double(*)(double)) [](double x) -> double {
  90. return std::pow(2, x) * 120.f;
  91. }, TE_FUNCTION1 | TE_FLAG_PURE, NULL});
  92. teVars.push_back({"bpmtov", (void*) (double(*)(double)) [](double x) -> double {
  93. return std::log2(x / 120.f);
  94. }, TE_FUNCTION1 | TE_FLAG_PURE, NULL});
  95. }
  96. void Quantity::setDisplayValueString(std::string s) {
  97. teVarsInit();
  98. // Uppercase letters aren't needed in formulas, so convert to lowercase in case user types INF or C4.
  99. s = string::lowercase(s);
  100. // Replace "#" with "s" so note names can be written as c#.
  101. std::replace(s.begin(), s.end(), '#', 's');
  102. // Compile string with tinyexpr
  103. te_expr* expr = te_compile(s.c_str(), teVars.data(), teVars.size(), NULL);
  104. if (!expr)
  105. return;
  106. double result = te_eval(expr);
  107. te_free(expr);
  108. if (std::isnan(result))
  109. return;
  110. setDisplayValue(result);
  111. }
  112. std::string Quantity::getString() {
  113. std::string s;
  114. std::string label = getLabel();
  115. std::string valueString = getDisplayValueString() + getUnit();
  116. s += label;
  117. if (label != "" && valueString != "")
  118. s += ": ";
  119. s += valueString;
  120. return s;
  121. }
  122. void Quantity::reset() {
  123. setValue(getDefaultValue());
  124. }
  125. void Quantity::randomize() {
  126. if (isBounded())
  127. setScaledValue(random::uniform());
  128. }
  129. bool Quantity::isMin() {
  130. return getValue() <= getMinValue();
  131. }
  132. bool Quantity::isMax() {
  133. return getValue() >= getMaxValue();
  134. }
  135. void Quantity::setMin() {
  136. setValue(getMinValue());
  137. }
  138. void Quantity::setMax() {
  139. setValue(getMaxValue());
  140. }
  141. void Quantity::toggle() {
  142. setValue(isMin() ? getMaxValue() : getMinValue());
  143. }
  144. void Quantity::moveValue(float deltaValue) {
  145. setValue(getValue() + deltaValue);
  146. }
  147. float Quantity::getRange() {
  148. return getMaxValue() - getMinValue();
  149. }
  150. bool Quantity::isBounded() {
  151. return std::isfinite(getMinValue()) && std::isfinite(getMaxValue());
  152. }
  153. float Quantity::toScaled(float value) {
  154. if (!isBounded())
  155. return value;
  156. else if (getMinValue() == getMaxValue())
  157. return 0.f;
  158. else
  159. return math::rescale(value, getMinValue(), getMaxValue(), 0.f, 1.f);
  160. }
  161. float Quantity::fromScaled(float scaledValue) {
  162. if (!isBounded())
  163. return scaledValue;
  164. else
  165. return math::rescale(scaledValue, 0.f, 1.f, getMinValue(), getMaxValue());
  166. }
  167. void Quantity::setScaledValue(float scaledValue) {
  168. setValue(fromScaled(scaledValue));
  169. }
  170. float Quantity::getScaledValue() {
  171. return toScaled(getValue());
  172. }
  173. void Quantity::moveScaledValue(float deltaScaledValue) {
  174. if (!isBounded())
  175. moveValue(deltaScaledValue);
  176. else
  177. moveValue(deltaScaledValue * getRange());
  178. }
  179. } // namespace rack