1
0
basics/stfx/param-info.h

337 lines
9.9 KiB
C++

#ifndef SIGNALSMITH_STFX_STFX2_PARAM_INFO_H
#define SIGNALSMITH_STFX_STFX2_PARAM_INFO_H
#include <vector>
#include <cmath>
#include <string>
#include <sstream>
#include <iomanip>
#include <map>
namespace stfx {
class UnitRangeMap {
double a, b, d;
public:
UnitRangeMap() : a(0), b(1), d(0) {} // identity
UnitRangeMap(double min, double mid, double max) {
double k = (mid - min)/(max - mid);
a = min;
b = max*k - min;
d = k - 1;
}
double toUnit(double value) const {
return (value - a)/(b - value*d);
}
double fromUnit(double unit) const {
return (a + unit*b)/(1 + unit*d);
}
};
struct RangeParamInfo {
double defaultValue;
double low, mid, high;
std::string name, description;
RangeParamInfo(double defaultValue) : defaultValue(defaultValue), low(defaultValue), mid(defaultValue), high(defaultValue) {}
RangeParamInfo(const RangeParamInfo &other) = delete;
RangeParamInfo(RangeParamInfo &&other) = default;
RangeParamInfo & info(std::string pName, std::string pDescription) {
name = pName;
description = pDescription;
return *this;
}
RangeParamInfo & range(double pLow, double pHigh) {
return range(pLow, pHigh, (pLow + pHigh)*0.5);
}
RangeParamInfo & range(double pLow, double pMid, double pHigh) {
low = pLow;
mid = pMid;
high = pHigh;
// sanity check in case we mix the argument order up
if ((mid < high) != (low < high)) std::swap(mid, high); // middle crosses the high
if ((low < mid) != (low < high)) std::swap(low, mid); // middle crosses the low
rangeMap = {low, mid, high};
return *this;
}
typedef double((*DoubleMap)(double));
RangeParamInfo & unit(std::string suffix, int precision=2, DoubleMap fromDisplay=identityMap, DoubleMap toDisplay=identityMap, double validLow=doubleLowest, double validHigh=doubleMax) {
unitOptions.emplace_back(suffix, fromDisplay, toDisplay, validLow, validHigh, precision);
return *this;
}
RangeParamInfo & unit(std::string suffix, DoubleMap fromDisplay, DoubleMap toDisplay, double validLow=doubleLowest, double validHigh=doubleMax) {
return unit(suffix, 2, fromDisplay, toDisplay, validLow, validHigh);
}
RangeParamInfo & unit(std::string suffix, int precision, double validLow, double validHigh) {
return unit(suffix, precision, identityMap, identityMap, validLow, validHigh);
}
RangeParamInfo & unit(std::string suffix, double validLow, double validHigh) {
return unit(suffix, identityMap, identityMap, validLow, validHigh);
}
RangeParamInfo & exact(double v, std::string valueName) {
exactOptions.push_back({v, valueName});
return *this;
}
std::string toString(double value) const {
for (auto &option : exactOptions) {
if (value == option.value) return option.name;
}
for (const auto &unit : unitOptions) {
if (unit.valid(value)) return unit.toString(value);
}
if (unitOptions.empty()) return std::to_string(value);
return unitOptions[0].toString(value);
}
void toString(double value, std::string &valueString, std::string &unitString) const {
for (auto &option : exactOptions) {
if (value == option.value) {
valueString = option.name;
unitString = "";
return;
}
}
for (const auto &unit : unitOptions) {
if (unit.valid(value)) return unit.toString(value, valueString, unitString);
}
if (unitOptions.empty()) {
valueString = std::to_string(value);
unitString = "";
}
unitOptions[0].toString(value, valueString, unitString);
}
double fromString(const std::string &str) const {
bool hasDecimal = false, hasDigit = false;;
{ // check for a parseable number
size_t pos = 0;
while (str[pos] == '\t' || str[pos] == ' ') ++pos; // strip leading whitespace
if (str[pos] == '-') ++pos;
while (pos < str.length()) {
if (!hasDecimal && (str[pos] == '.' || str[pos] == ',')) {
hasDecimal = true;
} else if (str[pos] >= '0' && str[pos] <= '9') {
hasDigit = true;
} else {
break;
}
++pos;
}
}
// It's not a number - look for an exact match
if (!hasDigit) {
int longestMatch = -1;
double result = defaultValue;
for (auto &option : exactOptions) {
for (size_t i = 0; i < option.name.size() && i < str.size(); ++i) {
if (option.name[i] == str[i]) {
if (int(i) > longestMatch) {
longestMatch = i;
result = option.value;
}
} else {
break;
}
}
}
return result;
}
size_t pos = 0;
double result = std::stod(str, 0);
// Skip whitespace after the number
while (pos < str.length() && (str[pos] == ' ' || str[pos] == '\t')) ++pos;
size_t end = str.length(); // and at the end
while (end > pos && end > 0 && (str[end - 1] == ' ' || str[end - 1] == '\t')) --end;
std::string unit = str.substr(pos, end - pos);
for (const auto &u : unitOptions) {
if (u.unit == unit) {
return u.fromDisplay(result);
}
}
return result;
}
std::vector<std::string> getUnits() const {
std::vector<std::string> result;
for (size_t i = 0; i < unitOptions.size(); ++i) {
bool duplicate = false;
for (size_t j = 0; j < i; ++j) {
if (unitOptions[i].unit == unitOptions[j].unit) {
duplicate = true;
break;
}
}
if (!duplicate) result.push_back(unitOptions[i].unit);
}
return result;
}
double toUnit(double value) const {
return rangeMap.toUnit(value);
}
double fromUnit(double unit) const {
return rangeMap.fromUnit(unit);
}
private:
static constexpr double doubleLowest = std::numeric_limits<double>::lowest();
static constexpr double doubleMax = std::numeric_limits<double>::max();
static double identityMap(double v) {
return v;
}
// A map from [0, 1] to another range with specified midpoint, based on a 1/x curve
UnitRangeMap rangeMap;
protected:
struct ExactEntry {
double value;
std::string name;
};
std::vector<ExactEntry> exactOptions;
struct UnitEntry {
std::string fixedDisplay = "";
std::string unit;
bool addSpace = false, useFixed = false, keepZeros = false;
DoubleMap fromDisplay, toDisplay;
double validLow, validHigh;
int precision;
double precisionOffset = 0;
UnitEntry(std::string unit, DoubleMap fromDisplay, DoubleMap toDisplay, double validLow, double validHigh, int precision=2) : unit(unit), fromDisplay(fromDisplay), toDisplay(toDisplay), validLow(validLow), validHigh(validHigh), precision(precision) {
if (validLow > validHigh) {
std::swap(validLow, validHigh);
keepZeros = true;
}
if (unit[0] == ' ') {
addSpace = true;
this->unit = unit.substr(1, unit.size() - 1);
}
precisionOffset = 0.4999*std::pow(10, -precision);
}
bool valid(double value) const {
return value >= validLow && value <= validHigh;
}
void toString(double value, std::string &valueString, std::string &unitString) const {
std::ostringstream oss;
oss.precision(precision);
oss << std::fixed;
double offset = 0;
if (precision > 0) {
oss << toDisplay(value) + offset;
} else {
oss << (int)(toDisplay(value) + offset);
}
valueString = oss.str();
// Strip trailing zeroes
if (!keepZeros) for (int i = 0; i < (int)valueString.size(); ++i) {
if (valueString[i] == '.') {
int zeros = valueString.size();
while (zeros > i && valueString[zeros - 1] == '0') --zeros;
if (zeros == i + 1) --zeros;
valueString = valueString.substr(0, zeros);
break;
}
}
unitString = unit;
}
std::string toString(double value) const {
std::string valueString, unitString;
toString(value, valueString, unitString);
return valueString + (addSpace ? " " : "") + unitString;
}
};
std::vector<UnitEntry> unitOptions;
};
struct SteppedParamInfo {
int defaultValue;
int low, high;
std::string name, description;
SteppedParamInfo(int defaultValue) : defaultValue(defaultValue), low(defaultValue), high(defaultValue), nameIndex(defaultValue) {}
SteppedParamInfo(const SteppedParamInfo &other) = delete;
SteppedParamInfo(SteppedParamInfo &&other) = default;
SteppedParamInfo & info(std::string pName, std::string pDescription) {
name = pName;
description = pDescription;
return *this;
}
SteppedParamInfo & range(int pLow, int pHigh) {
low = pLow;
high = pHigh;
return *this;
}
SteppedParamInfo & label(const char *n) {
if (nameIndex > high && high >= low) high = nameIndex;
if (nameIndex > low && low > high) low = nameIndex;
nameMap[nameIndex] = n;
valueMap[n] = nameIndex;
return *this;
}
bool fullyLabelled() const {
return int(nameMap.size()) == (high - low + 1);
}
template<class ...Args>
SteppedParamInfo & label(const char *first, Args... others) {
label(first);
++nameIndex;
return label(others...);
}
template<class ...Args>
SteppedParamInfo & label(int start, const char *first, Args... others) {
nameIndex = start;
return label(first, others...);
}
const std::string * getLabel(int value) const {
auto pair = nameMap.find(value);
if (pair != nameMap.end()) {
return &pair->second;
}
return nullptr;
}
std::string toString(int value) const {
const std::string *maybeLabel = getLabel(value);
if (maybeLabel) return *maybeLabel;
return std::to_string(value);
}
int fromString(const std::string &str) const {
auto pair = valueMap.find(str);
if (pair != valueMap.end()) return pair->second;
size_t pos = 0;
if (str[0] == '-') ++pos;
while (pos < str.length() && str[pos] >= '0' && str[pos] <= '9') ++pos;
if (pos >= str.length() || pos == 0 || (pos == 1 && str[0] == '-')) {
return defaultValue;
}
int result = std::stoi(str, &pos);
if (high >= low) {
return std::max<int>(std::min<int>(high, result), low);
} else {
return std::max<int>(std::min<int>(low, result), high);
}
}
private:
int nameIndex;
protected:
std::map<int, std::string> nameMap;
std::map<std::string, int> valueMap;
};
} // namespace
#endif // include guard