Studio engineers used to fill in recall sheets: every knob on every channel, written down so a mix could be rebuilt next week. Big consoles later stored the positions for you, and even then an engineer had to turn each knob back by hand. Plugins promise to do all of that instantly. Mostly they do. Here's where they don't.
Play a few seconds and press Loudness match so the pedal learns a level. Then close the pedal with its round button and open it again: a fresh copy with the same settings. Which things came back, and which had to start again?
The knobs came back. Did the sound?
Guitarabin
A fixed pedalboard for spacious guitar.
- Loudness match On
- Drive 0.60
- Echo 0.30
Every knob returns, and Loudness match still says on. But the level it learned is gone, and the pedal has to listen again before it settles. That's deliberate: the right correction depends on what you're playing now. It's also exactly the kind of thing that makes someone say "the knobs were right, but the sound was different".
Before reading on, predict. You close a project and reopen it in a brand-new instance of a module. Which of these comes back by itself: knob positions, a loaded sample bank, an edited step pattern, a learned gain?
Four kinds of value, four ways back
Only the first one is free. Each of the others needs a decision from you.
How each value comes back
Parameters: the free ride
A parameter is a value with a stable name in the schema. Every host already saves parameters with the project, lets you automate them and maps them to MIDI controllers. So the first rule is simple: if a value behaves like a control, make it a parameter. Hiding a cutoff in some private save format loses automation and controller mapping for nothing.
One thing to protect: the parameter's path and its position in the list are what a DAW's automation lanes point at. Add new parameters at the end. Reorder them and next month's automation drives the wrong knob.
Loaded content: find it again
A sample bank can be most of an instrument's sound, but it isn't a number. The host loads it into the module, and the module can't hand it back (on purpose: copying 20 MB of samples into every project file would bloat every session). So recall depends on the host knowing which bank it was, precisely enough to find the same one on another machine. A filename isn't enough when two files share a name.
When the content can't be found, the honest behaviour is to say so, not to quietly load something else and pretend the session came back.
Saved state: data only your module understands
Some things a module edits aren't knobs and can't be rebuilt by anyone else. A step sequencer's pattern is the textbook case: nobody automates a whole grid of steps, and no host knows what your grid means. That's saved state, and a Maru Processor handles it with a pair of functions:
saveState(state, out: Uint8Array): number // bytes written, or negative to refuse restoreState(state, bytes: Uint8Array, length: number): boolean // false to refuse
Here's what a small 16-step pattern might look like. It's a sketch for your own module:
const STEPS = 16
const FORMAT = 1
export function saveState(state: PatternState, out: Uint8Array): number {
if (out.length < 1 + STEPS) return -1 // refuse rather than truncate
out[0] = FORMAT // version byte first
for (let step = 0; step < STEPS; step = step + 1) out[1 + step] = state.steps[step]
return 1 + STEPS
}
export function restoreState(state: PatternState, bytes: Uint8Array, length: number): boolean {
if (length !== 1 + STEPS || bytes[0] !== FORMAT) return false // not ours: refuse
for (let step = 0; step < STEPS; step = step + 1) state.steps[step] = bytes[1 + step]
return true
}
Three habits make this robust. Write fields one at a time, so the bytes mean the same thing in every build of your module. Put a version byte first and refuse anything you don't recognise, rather than restoring a different sound. And always write both functions: a module that can save but not restore writes its data perfectly and loses it on every reopen, silently.
Forgotten on purpose
Filter memory, a meter's history, an in-progress learning window, Guitarabin's loudness correction: some values should start fresh. Saving everything is as wrong as saving too little. The difference between a design and a bug is whether you chose it. "It relearns from the new input, because the old estimate may not fit" is a design. "We didn't get round to saving it" isn't.
Why the same-instance test lies
Here's the trap that catches almost everyone. You save, then restore into the same running instance, and everything looks perfect. Of course it does: the pattern was already in memory.
Which test would have caught the missing restore?
Save, close, reopen
Edit the pattern
You program a syncopated hi-hat pattern and set Swing to 60 percent.
- pattern
- edited
- Swing
- 60 percent
Only a fresh instance catches it. Maru's qualification has a check for exactly this: declare stateRoundTrip: true and it renders your test phrase, saves, restores into a brand-new instance, renders again, and requires the two to match. It's the only check that can see this failure, because one render on its own sounds fine.
Use a distinctive edit when you test. If your restore silently does nothing and your test pattern happens to be the default, it will pass.
A preset isn't just knob values
Two factory presets can share exactly the same values. A preset can take a moment to load, or fail. So "which preset is selected" is something the host tells the face, not something the face guesses by comparing knob positions. When you tweak a knob, the selection clears, because you've made something new. When the host itself adjusts a value (syncing a delay to tempo, say), it shouldn't, because you didn't.
A preset can also be partial: set only the delay controls and leave the rest as they are. That's useful and needs its own test, so you know the untouched controls really are untouched.
Your users' own presets live outside your plugin, in their data folder, so updating or uninstalling the plugin never deletes them.
Automation is a story, not a snapshot
A preset is a moment. Automation is a sequence of moments: Drive rising through a build, a filter opening on the drop. Both rely on the same parameter meanings, but they fail differently. A control can recall perfectly and still click when automated, or arrive a block late. Test both: reopen to check recall, play back automation to check motion.
Same idea, different place: Sequencers and arpeggiators makes the difference audible. The saved pattern and the moving playhead are different kinds of state, even when they're drawn on the same grid.
Make it yours
List every value your module uses and sort each into the four kinds: parameter, loaded content, saved state, or forgotten on purpose. Write one sentence for any choice a user might be surprised by, especially the forgotten ones; that sentence belongs in your module's documentation.
Then make a preset that's nothing like the defaults: unusual values, edited content, an edge case. Save it, close the instance completely, open a fresh one, restore, and play the same short phrase. Listen, and compare the output, not just the knob positions. Keep that round trip as a test, along with one failure case, such as missing content, and what the user should see when it happens. The next time someone reopens a session a year later, it will sound the way they left it.
Quick check
- Restoring into the same instance works perfectly. Have you tested recall? Not really. The data was already in memory, so a restore that does nothing still passes. Open a fresh instance and restore there.
- The bank's filename comes back after reopening, but the instrument sounds different. What would you check? Whether the host found the same content. A name isn't an identity when files can share names or change.
- After reopening, a pedal's level correction starts from scratch while its on switch is still on. Bug? Not necessarily. Some values are forgotten on purpose and relearned from fresh audio, like Guitarabin's loudness match. Check what the module promises, then decide.
Next steps
- Use it: Shipping to browser and plugins. It reopens in the browser. Will it reopen the same way as a plugin, on someone else's computer?
- Go deeper: Presets as product design. Decide what each saved starting point should help a musician make.













