How do I save and recall module's variables?

I’m still very new to making modules so maybe my question seems silly, but still I can’t figure it out…
Currently I’m making a module which has a two dimensional array of floats, which is initialized like this:
float store[8][8] = {};
later in my code values are saved in this array.
But when I close rack and reopen it, the initialization happens again and all the stored parameter values are gone.

I’ve thought about writing to an external file, but this would mean for every instance of the module there needs to be a separate file and each needs to remember which file it belongs to, which would then again need to be saved inside the patch… :fearful:

I know Rack automatically saves all plugin parameters in the patch when closing and reopening the program. Is there a way to save this array in the patch and automatically restore it upon initialisation?

You could use the to/from Json methods, so it gets saved in the patch. Here is an example of storing a 16x16 float array in a sequencer (see elsewhere in the code for loading):

2 Likes

That’s exactly what I’m looking for!
Now I just have to wrap my head around what’s happening here and find out who this Json is :slight_smile:

1 Like

The “easy” way is to just make a VCV “Parameter” for each variable you want to store. That is how modules with some knobs and switches save their state - it is provided free by VCV. For an array, like you are talking about, Marc’s method if probably preferable. To find out more, why don’t you look at the source of one of his modules, then look at what VCV is saving to the patch file?

I am looking, I am looking. My F12 key is getting hot from the definition lookups.

I kind of get how the toJson and fromJson functions are made up in each class that needs to save stuff. But while they appear all over the code, I can’t figure out where/when these functions are called? Where should I call these? I understand not to save it every step in step() :slight_smile:

I override the toJson() method in my Module class, and then call toJson() for all of the widgets contained by my module. While I’m debugging, it’s quite nice that I can crash, fix the bug, rerun, and end up right where I was before.

You shouldn’t - Rack calls them itself whenever a patch is loaded or saved (including autosave every fifteen seconds or whatever it is).

If you have a hierarchy of objects that want to load and save state, your top-level toJson and fromJson (called by Rack) could in turn call similar methods on the sub-objects in your hierarchy, but that’s just a convenient way to do it - Rack doesn’t impose any mechanism on how you do that.

Ok I think I get it now. so to do it neatly, I just have to give all my classes that need data saving a function to convert and save to/from json and then call those from the main tojson and fromjson that I create inside my module class and rack will magic the rest. cool cool cool

1 Like

Yes! It’s working, awesome!

I’ll leave my code here for when ever someone has the same question and wants an example.
(since the size is always a fixed 8x8 in the 2D array, I opted to save them in one json array of 64 and then split it again upon loading)

the two overrides:

json_t *toJson() override {

		json_t *rootJ = json_object();

		json_t *recordsJ = json_array();
		for (int i = 0; i < 8; i++){
			for (int j = 0; j < 8; j++){
				json_array_append_new(recordsJ, json_real(recorder.save(i,j)));
			}
		}
		json_object_set_new(rootJ, "recorder", recordsJ);
	
		return rootJ;
	}


	void fromJson(json_t *rootJ) override {
		// running
		json_t *recorderJ = json_object_get(rootJ, "recorder");
		for (int i = 0; i < 8; i++){
			for (int j = 0; j < 8; j++){
				recorder.load(i, j, json_real_value(json_array_get(recorderJ, 8 * i + j)));
			}
		}

	}

And the load and save functions inside the recorder struct:

	float store[8][8] = {}; //this gets populated elsewhere
	
	//Save
	float save(int i, int j){
		return store[i][j];
	}

	//Load
	void load(int i, int j, float val){
		store[i][j] = val;
	}
2 Likes

It’s good practice to check that the return value of json_array_get() and other functions is not NULL before using it, so invalid JSON doesn’t crash Rack.

1 Like

It looks like json does the checking for you, so code like:

data[index] = json_integer_value(json_array_get(noteData, index));

is safe because json_integer_value is defined as

json_int_t json_integer_value(const json_t *json)
{
    if(!json_is_integer(json))
        return 0;
    ...

where json_is_integer() is:

#define json_is_integer(json)  ((json) && json_typeof(json) == JSON_INTEGER)

What pattern do you see that requires additional checks?

Ah, you’re right. Just make sure that the default values are valid in your module state. Usually you want nonexistent values to not overwrite the default values, in case the user loads an old patch without a certain JSON object.

I imagine json_real_value() does the same kind of check? The default values are 0.0 so when 0 is returned it wouldn’t do any harm.

Yes, all json_*_value() and json_*_get() gracefully handle NULL.

I’ve been tinkering on (re)storing settings via json for a few days now, and I’m getting there. But as I understand from these few posts the PARAM values are stored in a patch by default, which I have been able to verify. This raises 2 questions:
1: how can I access them?
2: why is there a mechanism involving json if the params are stored by default anyway?

params[MY_PARAM].value

Some modules have internal state that is not determined by knob positions.

So the params are automatically saved, but I must restore any variable related to them myself (e.g. a boolean keeping track that a button was pressed)?

A bool as an int is implicit, the two can be connected.

bool isZero = true;
if (isZero == jsonValue) // true/false

As long as the json value is 0 or 1 you’re conditions would be executed or not.

Param values are saved AND LOADED automatically for you. But not all “widgets” set their state from a param. Knobs always do, but not all switches do. The slide switches in VCV mostly do, but many pushbuttons do not. You can write your own if you want. in some cases you can associate a widget with a parameter yourself, although it can get tricky. While you are learning, probably best to just stick with widgets that do what you want. Most modules do not do their own JSON serialization.