change display range of a freq param

hi all,

actually i’ve setup a freq param this way (range from -6 to 6 octaves):

 configParam(FREQ_PARAM, -72.0, 72.0, 0.0, "Freq", " hz", rack::dsp::FREQ_SEMITONE, rack::dsp::FREQ_C4);

now, i’d like to change this range based on a module’s bool value (such as isLowerRange), setting the range from -4 to 0 octaves.

i know Rack doesn’t support param Min/max value change. So a way could be set Min/max normalized to 0/1, and rescale accordely on DSP code due to that bool param.

but what about display value? which are the base/multiplier/offset values (which I’ll set on step() function, ModuleWidget, when isLowerRange change) to set keeping the display in hz from normalized values?

with actual configParam i don’t find a possibile solution (but maybe its my fault and you know). or perhaps i just need to subclass ParamWidget and implement a switch case (but if necessary; better kept with actual code).

thanks for any tips

I haven’t tried this myself, but I think you can just do

paramQuantities[FREQ_PARAM]->setDisplayValue(x);

paramQuantities is a member of Module that is configured when you call configParam()

Check out ParamQuantity.hpp for more stuff you can do.

well, i prefer to delegate correctly the scaling on ParamQuantity, instead of force it to a fixed value.

also, a user can insert a value from interface, so it must to scale correctly based on actual range.

this is basically the function that will be applied, but i’m not able to think a way to rescale v before v = std::pow(displayBase, v) (which is what should be done I believe…).

Not with frequencies, however I recently went down the “change min/maxValue” rabbit hole scaling my min/maxValue based on a context-menu. It worked fine while running the patch, however when later loading the patch it would scale the values once again.

For a module I needed an attenuate knob to change is range between -1/1, -2/2, -5/5 and -10/10 based on context-menu. For this struct I left the actual mix/maxValue range to -1/1 and then I simply scale input/output-values in getDisplayValue (the one shown in tooltip/and populating the entry field when you right-click the knob) and setDisplayValue (the one converting the entry-value when accepting the input after right-clicking).

struct infNoiseAttRngQnt : ParamQuantity {
	enum attRange { ar_1x, ar_2x, ar_5x, ar_10x };
	const float attRangeFactors[4] = { 1.f, 2.f, 5.f, 10.f };
	attRange range = ar_1x;  // Active range (defaults to 1x).

	infNoiseAttRngQnt() {
		unit = " x";
		displayMultiplier = 1.f;
		minValue = -1.f;
		maxValue = 1.f;
		defaultValue = 1.f;

		name = getRangeName();
	}

	float getDisplayValue() override {
		return getValue() * attRangeFactors[(int)range];
	}

	void setDisplayValue(float displayValue) override {
		setValue(displayValue / attRangeFactors[(int)range]);
	}

	void setRange(attRange range, std::string newName = "Scale", bool appendScaleRange = true) {
		this->range = range;
		name = getRangeName(newName, appendScaleRange);

		// Default to 1V, no matter range.
		defaultValue = 1.f / attRangeFactors[(int)range];
	}

	std::string getRangeName(std::string newName = "Scale", bool appendScaleRange = true) {
		std::string scaleRangeName = (appendScaleRange)
			? " (" + std::to_string((int)-attRangeFactors[(int)range]) + "x" +
			" to + " + std::to_string((int)attRangeFactors[(int)range]) + "x)"
			: "";
		return newName + scaleRangeName;
	}
};

To change the (logical) range I simply call setRange, which also updated the “name” of ParamQuantity, so it shows the correct “range” when the user hoovers the mouse ouver the knob. In your case you in stead need getDisplayValue/setDisplayValue to scale between your fysical/logical range according to freq.

so as thought there is no way with actual configParam to setup this, it seems i need to subclass ParamQuantity and make my own display get/set methods :slight_smile:

You can also change the display range directly on the ParamQuantity without using a subclass:

void updateParamScaling() {
    auto* pq = getParamQuantity(SOME_PARAM);

    if (use_alt_range) {
        pq->displayMultiplier = rack::dsp::FREQ_C4 * 0.25f;
        pq->displayBase = 4.0f;
    } else {
        pq->displayMultiplier = rack::dsp::FREQ_C4;
        pq->displayBase = 64.0f;
    }
}

Just make sure to serialize the value of use_alt_range in dataToJson/dataFromJson. You’ll need to call updateParamScaling() from your context menu item, and in dataFromJson to make sure it loads correctly.

Edit: I originally put in random values here as an example, but I edited them to use the real values.

1 Like

how those range will scale between base * 2^(-6 to 6) and base * 2^(-4 to 0)?

thats what im not able to accomplish hehe

set base/multiplier/offset can’t cover both range when display them, or am i missing a point?

Unless I’m misunderstanding, you can change the displayBase adjust the range. If you use minValue = -1.0f and maxValue = 1.0f then:

For the range b * 2^(-6 to 6), use displayBase = 64.0f and displayMultiplier = b.

For the range b * 2^(-4 to 0), use displayBase = 4.0f and displayMultiplier = b * 0.25f

(I edited the previous post to use these values instead of the sample values I previously had)

1 Like

nice trick, thanks :slight_smile:

is there a generic formula for different range?

for example, what if i need a range between -5 and +2? (instead of symmetric range as above…)

Yup! When the parameter is -1 to 1, and you want to map to m * 2^(a to b), set: displayBase = 2^((b - a)/2) and displayMultiplier = m * 2^((a + b)/2).

You can do it with a parameter 0 to 1 as well if that is more convenient: displayBase = 2^(b - a) and displayMultiplier = m * 2^a

This works using the identity exp(x) * exp(y) = exp(x + y), which will work for any base.

3 Likes

very very very nice trick :slight_smile: thanks dude

of course this works considering that:

  • rescale from [a, b] to [-1, 1] => (a + b)/2 + x*(b - a)/2
  • rescale from [a, b] to [ 0, 1] => a + x*(b-a)

correct? (so i close the reasoning) :stuck_out_tongue:

1 Like

Correct :slight_smile:

1 Like