#include #include #include // for C4 and A4 frequencies #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 */ struct TeVariable { std::string name; double value; }; static std::vector teVariables; static std::vector teVars; static void teVarsInit() { if (!teVars.empty()) return; // Add variables teVariables.push_back({"inf", INFINITY}); teVariables.push_back({"c", dsp::FREQ_C4}); teVariables.push_back({"a", dsp::FREQ_A4}); for (int i = 0; i <= 8; i++) teVariables.push_back({string::f("c%d", i), dsp::FREQ_C4 * std::pow(2.0, i - 4)}); for (int i = 0; i <= 8; i++) teVariables.push_back({string::f("a%d", i), dsp::FREQ_A4 * std::pow(2.0, i - 4)}); // Build teVars teVars.resize(teVariables.size()); for (size_t i = 0; i < teVariables.size(); i++) { teVars[i].name = teVariables[i].name.c_str(); teVars[i].address = &teVariables[i].value; teVars[i].type = TE_VARIABLE; teVars[i].context = 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); // 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