#include #include #include namespace rack { float Quantity::getDisplayValue() { return getValue(); } void Quantity::setDisplayValue(float displayValue) { setValue(displayValue); } int Quantity::getDisplayPrecision() { return 5; } std::string Quantity::getDisplayValueString() { float v = getDisplayValue(); if (std::isnan(v)) return "NaN"; return string::f("%.*g", getDisplayPrecision(), math::normalizeZero(v)); } /** Build-in variables for tinyexpr Because formulas are lowercased for case-insensitivity, all variables must be lowercase. */ struct TeVariable { std::string name; double value; }; static std::vector teVariables; static std::vector teVars; static void teVarsInit() { if (!teVars.empty()) return; // Math variables teVariables.push_back({"inf", INFINITY}); // Note names struct Note { std::string name; int semi; }; const static std::vector notes = { {"c", 0}, {"d", 2}, {"e", 4}, {"f", 5}, {"g", 7}, {"a", 9}, {"b", 11}, }; for (const Note& note : notes) { // Example: c, cs (or c#), and cb teVariables.push_back({string::f("%s", note.name.c_str()), 440.f * std::pow(2.0, (note.semi - 9) / 12.f)}); teVariables.push_back({string::f("%ss", note.name.c_str()), 440.f * std::pow(2.0, (note.semi - 9 + 1) / 12.f)}); teVariables.push_back({string::f("%sb", note.name.c_str()), 440.f * std::pow(2.0, (note.semi - 9 - 1) / 12.f)}); for (int oct = 0; oct <= 9; oct++) { // Example: c4, cs4 (or c#4), and cb4 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)}); 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)}); 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)}); } } // Build teVars from teVariables teVars.reserve(teVariables.size()); for (size_t i = 0; i < teVariables.size(); i++) { teVars.push_back({teVariables[i].name.c_str(), &teVariables[i].value, TE_VARIABLE, NULL}); } // Add custom functions teVars.push_back({"log2", (void*) (double(*)(double)) std::log2, TE_FUNCTION1 | TE_FLAG_PURE, NULL}); } void Quantity::setDisplayValueString(std::string s) { teVarsInit(); // Uppercase letters aren't needed in formulas, so convert to lowercase in case user types INF or C4. s = string::lowercase(s); // Replace "#" with "s" so note names can be written as c#. std::replace(s.begin(), s.end(), '#', 's'); // Compile string with tinyexpr te_expr* expr = te_compile(s.c_str(), teVars.data(), teVars.size(), NULL); if (!expr) return; double result = te_eval(expr); te_free(expr); if (std::isnan(result)) return; setDisplayValue(result); } std::string Quantity::getString() { std::string s; std::string label = getLabel(); std::string valueString = getDisplayValueString() + getUnit(); s += label; if (label != "" && valueString != "") s += ": "; s += valueString; return s; } void Quantity::reset() { setValue(getDefaultValue()); } void Quantity::randomize() { if (isBounded()) setScaledValue(random::uniform()); } bool Quantity::isMin() { return getValue() <= getMinValue(); } bool Quantity::isMax() { return getValue() >= getMaxValue(); } void Quantity::setMin() { setValue(getMinValue()); } void Quantity::setMax() { setValue(getMaxValue()); } void Quantity::toggle() { setValue(isMin() ? getMaxValue() : getMinValue()); } void Quantity::moveValue(float deltaValue) { setValue(getValue() + deltaValue); } float Quantity::getRange() { return getMaxValue() - getMinValue(); } bool Quantity::isBounded() { return std::isfinite(getMinValue()) && std::isfinite(getMaxValue()); } float Quantity::toScaled(float value) { if (!isBounded()) return value; else if (getMinValue() == getMaxValue()) return 0.f; else return math::rescale(value, getMinValue(), getMaxValue(), 0.f, 1.f); } float Quantity::fromScaled(float scaledValue) { if (!isBounded()) return scaledValue; else return math::rescale(scaledValue, 0.f, 1.f, getMinValue(), getMaxValue()); } void Quantity::setScaledValue(float scaledValue) { setValue(fromScaled(scaledValue)); } float Quantity::getScaledValue() { return toScaled(getValue()); } void Quantity::moveScaledValue(float deltaScaledValue) { if (!isBounded()) moveValue(deltaScaledValue); else moveValue(deltaScaledValue * getRange()); } } // namespace rack