Skip to content

Declare the five momentary actions as write-only triggers - #1

Open
charlesvestal wants to merge 1 commit into
filliformes:mainfrom
charlesvestal:momentary-actions
Open

charlesvestal wants to merge 1 commit into
filliformes:mainfrom
charlesvestal:momentary-actions

Conversation

@charlesvestal

Copy link
Copy Markdown

Hi — I hit this while poking at knob behaviour across a few modules on my Move, and Essaim turned out to have a clean one-line-per-param fix. Five params behave as one-shot actions but are declared as ordinary readable floats, so the host has no choice but to treat them as knobs.

What's happening

Rnd Patch, Init Freq, Rnd Mod, Rnd Pan and Rnd Voice are momentary. In essaim.c:

// fires on any non-zero, stores nothing
if (strcmp(key,"rnd_patch")==0) { if (atof(val)!=0) randomize_patch(inst); return; }

// always answers "0"
if (strcmp(key,"rnd_patch")==0||...) return snprintf(buf,buf_len,"0");

But they're declared as values:

{"key":"rnd_patch","name":"Rnd Patch","type":"float","min":0,"max":1,"step":1}

Three symptoms on device, all from that single declaration:

  1. ~200 detents to cross 0..1. A float's declared step isn't used for detent size — the host derives it from the range (perDetentStep in knob_engine.mjs honours step only for int). So step: 1 has no effect and each detent moves 0.005.
  2. The value snaps back after turning. The host writes optimistically, then its read cursor re-reads — and get_param always answers "0", so the display reverts.
  3. They draw as value knobs, not as buttons.

The change

Declaration only — one line per param:

-{"key":"rnd_patch","name":"Rnd Patch","type":"float","min":0,"max":1,"step":1},
+{"key":"rnd_patch","name":"Rnd Patch","type":"enum","options":["Idle","Trigger"],"access":"write"},

access: "write" is the host's existing declaration for a momentary action. It gives you:

  • fires on a single jog-click instead of a turn
  • no read-back, so nothing snaps back
  • the host's bang widget with its flash animation, instead of a value cell
  • isTurnable false, so it stops occupying a knob as a value

No engine change needed

Worth being explicit, since it's the part that could have bitten: for a write-only param the host sends the option index, i.e. the string "1". I checked this through the host's own code rather than assuming —

isTrigger isTurnable wire value atof(val)!=0 fires?
before false true "1.000" yes
after true false "1" yes

So your existing if (atof(val)!=0) handler fires unchanged. get_param returning "0" is now simply never called for these, and I left it alone.

One thing to avoid if you tweak this: don't add options_as_string. That switches the wire to the option label ("Trigger"), atof gives 0, and the actions would silently stop firing.

The "Idle"/"Trigger" strings are deliberately plain ASCII — the bang widget draws no text, but the labels still surface in the list editor and to the screen reader, and the 5x7 font atlas can't render an em-dash.

Testing

Verified the metadata resolution and wire format against the host's param_meta / param_format modules, and confirmed the chain_params JSON still parses (34 entries). I have not rebuilt the module or run it on hardware — I don't have your build set up — so a quick on-device check that the five still fire would be worth doing before merging.


Thanks for Essaim, it's a lovely thing to have on the Move.

Disclosure: I used Claude to help investigate and draft this. The diff is five declaration lines and I verified the wire-format claim above myself.

Rnd Patch, Init Freq, Rnd Mod, Rnd Pan and Rnd Voice are one-shot actions:
set_param fires on any non-zero write and stores nothing, and get_param
returns a hardcoded "0". They were declared as readable floats 0..1 step 1,
so the host had to treat them as ordinary knobs.

Three consequences on device, all from that one declaration:

  - ~200 detents to cross 0..1, because a float's declared step is not used
    for the detent size (the host derives it from the range)
  - the value visibly snaps back, because the read cursor re-reads and
    get_param always answers "0"
  - they draw as value knobs rather than as buttons

Declaring them enum + access:"write" gives the behaviour they were always
meant to have: fire on a single jog-click, no read-back, and the host's
existing bang widget with its flash animation.

No engine change. The host sends "1" for a write-only trigger, which the
existing `if (atof(val)!=0)` already fires on.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant