Textfields + misc

Hi,

I am new to VCV Rack development, though not a noob programmer :slight_smile:

I have created an LedDisplayTextField in my module. I want to display some info in that text box. I also want to grab its content when the user changes it.

I am trying to wrap my head around how the UI elements interact with the module code. From what I can gather, by looking at other people’s code, it seems to revolve around dataToJson and dataFromJson calls? Am I correct? How do I attach the text field to json? Will the module get an event when the textfield’s value change? How do I set it? By inheriting LedDisplayTextField, and override the draw call?

If somebody can point me to something that works along this line, would be greatly appreciated.

Is there documentation on how the param system works? I looked through the api doc, can’t seem to find any.

Is there a place that I can find what has been declared for my modules? Looking through other people’s code, I often find myself going, wait, where did that variable come from? :smiley:

Lastly, I like to use print statements to do some debugging, is there a convention or a debug logging call that I should use? Where will the debug info go? i.e. the log file?

Thank you!

data to and from json is saved with the patch. GUI parameters such as those named in enum ParamIds will always be saved regardless.

Depends on what your text field is doing. You could use a pointer and set conditions based on this number to the address of a number in your dsp. The pointer could be sent to/from json. For example if the text field is outputting different strings you could store these in an array in the draw function and just send/receive the index position to/from json instead of a full string.

DEBUG(""); depending on your platform will output to the console in msys2. But using this in process() will output many times a sample, so you could set a condition to only output once like: on a button press use a dsp::SchmittTrigger to print once, if you really need to use it in process without this protection you could also use dsp::ClockDivider to limit the amount of time DEBUG processes so it doesn’t use too much CPU.

1 Like

Thank you!

A few more questions :smiley:

You say, all the params in enum ParamIds are always saved, but I still need to attach them to a GUI widget, no?

I need to get the content of the text field to name a piece of memory. Could you point me to an example of this please? Or the relevant documentation?

Much appreciated!!

One more question, this interaction between the panel and the module objects via json, is it documented anywhere?

Thank you.

You’re overcomplicating this.

void onChange(const event::Change& e) override {
	LedDisplayTextField::onChange(e);
	MyModule* myModule = dynamic_cast<MyModule*>(module);
	myModule->text = text;
}

or something. You could do it 100 other ways, depending on your style.

1 Like

Thank you, Andrew!

Yes, I figured that I was over complicating it. Your solution would be the most common approach.

But I can’t seem to find any documentation on how the gui elements work with the module elements.

I am programming max externals, vst/au plugins via Juce, pure data externals, etc. right now. Everybody got their own system.

If you can point me to the appropriate documentation or even code that somebody else has written, that would be great. Otherwise, I might just resort to download all of the VCV related code available, and grep through all of them :smiley:

Thank you.

Found an example. :smiley: I did end up cloning the entire library repo, and did a grep -r into it.

Thank you for pointing me towards the right direction.

What are “module elements”?

Sorry, what I meant is how the ModuleWidget objects interact with the Module objects.

I quickly gathered that inputs, outputs, lights are members of the Module class. But there is no predefined access to ModuleWidget.

So, I assume ModuleWidget is “pushing” events to its Module object. That makes sense, since GUI thread is often running at a much slower rate than the audio thread. But usually events have a way to register callbacks, etc. I didn’t find an obvious place to do that or hook into the events. Then there is the dataToJson functions, which I assume is how parameters are being sent or retrieved? They seemed to be similar the Audio parameter trees from Juce? Then the question comes, do I check them on a regular basis or do I get an event callback? And this event callback, is it via inheritance and overload or via registering? All of this wasn’t obvious.

I am ok with digging through code. It’s just bit tedious to build up an idea of how the different parts of VCV interact with each other. Like you said, there are 100 ways to do things, I just want to figure out the most efficient way to do things quickly within the scope of VCV Rack.

Hope this was clear :smiley:

Thank you.

The only thing data To/From is for is retrieving after a close. This is not saved in the module code rather it is saved in the patch, when the patch file .vcv is loaded it retrieves this data and updates the parameters for each instance of a module. For example if you have an array in the module code the only way to change the array values for that instance of the module is by retrieving them from the json the values get inserted to the array indexes and your code would update that instance of that module. If you add a new instance of the module to the Rack the array values would be initialised as they are in the module.cpp. When you close Rack the data for each instance is pushed to the .vcv file for retrieval upon opening Rack again. It is only called on open and pushed on close.

Yup. That sounds like the audio state tree in Juce.

For now, I just want to type something into the textfield widget and get its value in my Module object, so, I can do something with it.

Thank you for the reply!

Are you talking about a parameters textfield or a custom textfield?

tf

If the former

params[...].getValue();

if the latter probably the same way VCV does it using std::sscanf

Sorry, I wasn’t clear.

I need the text string, for example, “hello_test,” from a text box on the panel.

Thanks.

I know this is a late reply, but may help the OP or anyone else who finds this topic. The minimum code to access a Text field from the module is shown below. This will need some work, as in it’s current format will not display correctly in the browser, and accessing the text at the audio sample rate is probably undesired.


// DO NOT USE, please see posts below.

struct PitchLog : Module {
	enum ParamIds {
		NUM_PARAMS
	};
	enum InputIds {
		NUM_INPUTS
	};
	enum OutputIds {
		NUM_OUTPUTS
	};
	enum LightIds {
		NUM_LIGHTS
	};

	TextField* textField;

	PitchLog() {
		config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
	}

	void process(const ProcessArgs& args) override {
		textField->setText("hello world");
	}
};


struct PitchLogWidget : ModuleWidget {
	PitchLogWidget(PitchLog* module) {

		setModule(module);
		setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/template.svg")));

		if (module)
		{
			module->textField = createWidget<LedDisplayTextField>(mm2px(Vec(3.39962, 14.8373)));
			module->textField->box.size = mm2px(Vec(74.480, 102.753));
			addChild(module->textField);
		}
	}
};


Model* modelPitchLog = createModel<PitchLog, PitchLogWidget>("PitchLog");

You should not do it this way, the widget should pull the text from the module, you are pushing the text into the widget.
First is it a waste of resources because the GUI has a much lower refresh rate than audio processing, second it won’t work in Rack v2 in headless mode when a module runs without a ModuleWidget.

1 Like

Thanks for the quick response, My intention was the bare minimum code to show accessing a widget from a module, I am aware you would not do this at the audio sample rate, as I mentioned in my post.

I was not aware this would not work in V2, I would assume a null pointer check on the TextField in the module would be the correct way, I am always open to learning an improved method.

I am not intending to use this in a production module, but I am using this method in a debugging tool for my own use.

This is poor design. The ModuleWidget -> Module relationship should be strictly one-way. DSP code should not depend on UI code.

As @stoermelder says, the code you posted has some major problems. If somebody searches this forum for “textField” and finds this code, they could easily be lead in a direction that will cause them problems. This is how I would modify the code:

  1. PitchLog (your Module) should not have a TextField member at all
  2. PitchLog should have std::string variable (call it textValue) that stores the value that will be displayed in the text field
  3. PitchLogWidget (ModuleWidget) should have the TextField member:
struct PitchLogWidget : ModuleWidget {
  PitchLogWidget(PitchLog* module) {
    setModule(module);
    setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/template.svg")));
	textField = createWidget<LedDisplayTextField>(mm2px(Vec(3.39962, 14.8373)));
	textField->box.size = mm2px(Vec(74.480, 102.753));
	addChild(textField);
   }
  TextField* textField;
}
  1. override ModuleWidget::step() in PitchLogWidget to check for the existence of “module”, and set the value of its text field : textField->setText(module->textValue)
3 Likes

Thanks for the response, that all seems clear.

I have edited my code above to add a “Do not use, see posts below” comment, for any futures reader, apologies for any confusion caused.

1 Like

Could you show some sample code of bullet 4? How do you get access to module when it is called outside of the constructor in the step function?

1 Like