Knob value keeps changing after onDragEnd

So I’m trying to make a knob that snaps back to 0 when you release it. I’ve got it sort of working by a custom knob and onDragEnd, but sometimes it doesn’t work. Printing out the state each process I see that something is adjusting the knob’s value after I release it. I tried setting smoothEnabled to false but it is still happening. Anyone have any ideas what might be causing this?

Maybe show us your code for onDragEnd()

void onDragStart(const DragStartEvent& e) override {
	Davies1900hBlackKnob::onDragStart(e);
	if(e.button == GLFW_MOUSE_BUTTON_LEFT){
		((Blender*)this->module)->drag = true;
	}
}

void onDragEnd(const DragEndEvent& e) override {
	Davies1900hBlackKnob::onDragEnd(e);
	if(e.button == GLFW_MOUSE_BUTTON_LEFT){
		((Blender*)this->module)->drag = false;
	}
}

then inside of process

void process(const ProcessArgs& args) override {
	DEBUG("drag:%i knob:%f",drag,params[KNOB].getValue());
}

And in the logs like this

[6.884 debug src/Blender.cpp:197 process] drag:1 knob:0.152668
[6.884 debug src/Blender.cpp:197 process] drag:1 knob:0.152764
[6.884 debug src/Blender.cpp:197 process] drag:1 knob:0.152860
[6.884 debug src/Blender.cpp:197 process] drag:1 knob:0.152955
[6.892 debug src/Blender.cpp:197 process] drag:0 knob:0.153050
[6.892 debug src/Blender.cpp:197 process] drag:0 knob:0.153145
[6.892 debug src/Blender.cpp:197 process] drag:0 knob:0.153240
[6.892 debug src/Blender.cpp:197 process] drag:0 knob:0.153334

I don’t know much about Rack’s code and engine (e.g. how parameters are passed between the UI and audio thread), so this may not help, but the output logged from process probably isn’t too helpful because it is in a separate thread to your UI/drag code. If anything, it’s the output that I would expect: Rack loops over the current output buffer in the audio thread, one sample at a time, while a variable (your drag boolean) is changed in another thread.

Do you have any idea how long the buffer is? Ive tracked it out to 1000 samples out, which seems like it would be longer than any buffer issues.

The buffer is as big as you set it on the Audio module of your hardware device.
I don’t see any code which snaps your parameter back to zero after you release it?

1 Like

I took that code out to make the example clearer, but here is a more complete example. I’ve tried calling setValue right away, and I’ve tried delaying it after 1000 samples, and even then it still sometimes doesn’t work.:

#include "plugin.hpp"

struct KnobSnapTest : Module {
	enum ParamId {
		KNOB_PARAM,
		PARAMS_LEN
	};
	enum InputId {
		INPUTS_LEN
	};
	enum OutputId {
		OUTPUTS_LEN
	};
	enum LightId {
		LIGHTS_LEN
	};

	bool drag;
	bool drag_prev;

	float param_prev;
	int param_stable_count = 0;

	KnobSnapTest() {
		config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
		configParam(KNOB_PARAM, 0.f, 1.f, 0.f, "Test Knob");
	}

	void process(const ProcessArgs& args) override {
		if(drag_prev != drag){
			drag_prev = drag;
			DEBUG("Process now see's drag as %i",drag);
			if(!drag){
				params[KNOB_PARAM].setValue(0);
			}
		}
		param_stable_count++;
		float param = params[KNOB_PARAM].getValue();
		if(param_prev != param){
			param_prev = param;
			DEBUG("Param changed to %f after %i samples",param,param_stable_count);	
			param_stable_count = 0;
		}else{
			DEBUG("Param still to %f after %i samples",param,param_stable_count);	
		}
	}
};

struct CustomKnob : Davies1900hBlackKnob{

	void onDragStart(const DragStartEvent& e) override {
		Davies1900hBlackKnob::onDragStart(e);
		if(e.button == GLFW_MOUSE_BUTTON_LEFT){
			DEBUG("Widget setting drag to true");
			((KnobSnapTest*)this->module)->drag = true;
		}
	}

	void onDragEnd(const DragEndEvent& e) override {
		Davies1900hBlackKnob::onDragEnd(e);
		if(e.button == GLFW_MOUSE_BUTTON_LEFT){
			DEBUG("Widget setting drag to false");
			((KnobSnapTest*)this->module)->drag = false;
		}
	}

};

struct KnobSnapTestWidget : ModuleWidget {
	KnobSnapTestWidget(KnobSnapTest* module) {
		setModule(module);
		setPanel(createPanel(asset::plugin(pluginInstance, "res/Blank.svg")));

		addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
		addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
		addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
		addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

		addParam(createParamCentered<CustomKnob>(mm2px(Vec(40, 55)), module, KnobSnapTest::KNOB_PARAM));
	}
};

Model* modelKnobSnapTest = createModel<KnobSnapTest, KnobSnapTestWidget>("KnobSnapTest");

Select lines from the log file

[11.502 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.337537 after 1 samples
[11.502 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.337533 after 1 samples
[11.502 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.337529 after 1 samples
[11.502 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.337526 after 1 samples
[11.502 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.337522 after 1 samples
[11.521 debug src/KnobSnapTest.cpp:63 onDragEnd] Widget setting drag to false
[11.526 debug src/KnobSnapTest.cpp:33 process] Process now see's drag as 0
[11.526 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.000000 after 1 samples
[11.526 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.000457 after 1 samples
[11.527 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.000914 after 1 samples
[11.527 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.001370 after 1 samples
[11.527 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.001826 after 1 samples
[11.527 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.002280 after 1 samples
...
[11.762 debug src/KnobSnapTest.cpp:42 process] Param changed to 0.336145 after 1 samples
[11.762 debug src/KnobSnapTest.cpp:45 process] Param still to 0.336145 after 1 samples
[11.762 debug src/KnobSnapTest.cpp:45 process] Param still to 0.336145 after 2 samples
[11.762 debug src/KnobSnapTest.cpp:45 process] Param still to 0.336145 after 3 samples
[11.762 debug src/KnobSnapTest.cpp:45 process] Param still to 0.336145 after 4 samples

Here you can see the setValue does work, but then over the next quarter of a second something is still turning the knob back towards ~0.33

you forgot to set smooth = false and you should set the value directly in the UI thread and not in the audio thread. See code below:

#include "plugin.hpp"

struct KnobSnapTest : Module {
	enum ParamId {
		KNOB_PARAM,
		PARAMS_LEN
	};
	enum InputId {
		INPUTS_LEN
	};
	enum OutputId {
		OUTPUTS_LEN
	};
	enum LightId {
		LIGHTS_LEN
	};

	KnobSnapTest() {
		config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
		configParam(KNOB_PARAM, 0.f, 1.f, 0.f, "Test Knob");
	}

	void process(const ProcessArgs& args) override {

	}
};

struct CustomKnob : Davies1900hBlackKnob{

	CustomKnob() {
		smooth = false;
	}

	void onDragEnd(const DragEndEvent& e) override {
		((KnobSnapTest*)this->module)->params[KnobSnapTest::KNOB_PARAM].setValue(0);
		Davies1900hBlackKnob::onDragEnd(e);
	}
};

struct KnobSnapTestWidget : ModuleWidget {
	KnobSnapTestWidget(KnobSnapTest* module) {
		setModule(module);
		setPanel(createPanel(asset::plugin(pluginInstance, "res/Blank.svg")));

		addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
		addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
		addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
		addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

		addParam(createParamCentered<CustomKnob>(mm2px(Vec(40, 55)), module, KnobSnapTest::KNOB_PARAM));
	}
};

Model* modelKnobSnapTest = createModel<KnobSnapTest, KnobSnapTestWidget>("KnobSnapTest");

In my code above the user can still do a right click and enter a value for the knob. In this case, the knob will not reset to zero. To avoid this, you can avoid the knob’s context menu by adding the following code, but be aware that the user now has no way to enter a value by typing it.


	void onButton(const ButtonEvent& e) {
		if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) {
			// avoid context menu
			return;
		}
		Davies1900hBlackKnob::onButton(e);
	}

Have fun!

@Ahornberg thank you so much for your help.

Setting the knobs smooth to false was the trick. Before I was setting the ParamterQuantity’s smoothEnabled to false which wasn’t doing anything because I think it was already false.