Skip to content

midi_cc can name an AMY parameter directly (#1175, part 1) - #1191

Merged
dpwe merged 1 commit into
mainfrom
midi-cc-param
Sep 28, 2026
Merged

dpwe merged 1 commit into
mainfrom
midi-cc-param

Conversation

@dpwe

@dpwe dpwe commented Sep 26, 2026

Copy link
Copy Markdown
Collaborator

Part 1 of #1175: the direct-parameter form of midi_cc, which midi_cc_output will mirror in a follow-up.

A mapping payload that starts with a digit is a parameter list instead of a wire command template (the forms can't collide, since no wire command starts with a digit):

ic<C>,<L>,<N>,<X>,<O>,<P>[,<OSC>][,<P>,<OSC>...]
amy.send(synth=1, midi_cc='74,1,100,6400,0,%d' % amy.PARAM_FILTER_FREQ)   # osc 0 of every voice
amy.send(synth=1, midi_cc='71,0,0.7,8,0,%d,0,%d,1' % (amy.PARAM_RESONANCE, amy.PARAM_RESONANCE))  # oscs 0 and 1

How it's done

  • amy.PARAM_* constants for every enum params entry. scripts/gen_param_constants.py (run from the existing constants.py rule) compiles a tiny program against amy.h, so the computed enum values are the compiler's rather than parsed from comments, which are stale (NO_PARAM // 210 is really 234). The PARAM_ prefix keeps FREQ, BUS, MODE etc. from shadowing anything in amy.. It uses $(HOSTCC) (default cc), not $(CC), so a cross build still works. amy_api.generated.js is regenerated to match.
  • A direct mapping doesn't build deltas itself. It sets the matching amy_event field (a param_fields table in midi_mappings.c) and sends the event to the synth down the ordinary path. So:
    • values are in amy.send() units (Hz for filter_freq, linear drive, …), with no second copy of the logfreq/log2/logdrive conversions;
    • they reach every voice, at the voice-relative OSC;
    • bus params (PARAM_VOLUME, PARAM_REVERB_LEVEL, PARAM_BUS_DIST_*, …) go to the synth's bus.
  • Only params with a plain event field can be mapped. MIDI_NOTE/VELOCITY, osc references (CHAINED_OSC, MOD_SOURCE_*, ALGO_SOURCE_*), breakpoints, resets, LATENCY and BUS are refused. So is MODE, because event_addresses_oscs() doesn't count mode, so a synth-addressed mode change is dropped today (template form too; left alone here).
  • Integer params are rounded, not truncated, so a CC scaled to 0..6 steps through filter types cleanly.
  • A refused payload no longer deletes the mapping already on that CC: it's parsed before the old one is freed.
  • The payload text is kept as the template string, so the state dump and restore round-trip unchanged.
  • Also fixed: midi_mapping_init didn't check its malloc_caps.

Tests

  • New tests/test_midi_cc_param.c, added to make ctest. It covers:
    • linear and log transforms
    • every voice, and only the named osc
    • multiple P,OSC targets
    • integer rounding
    • a refused payload leaving the existing mapping intact
    • a state dump fed back in as a restore
    • template and direct forms side by side
  • make ctest: all pass.
  • make test: 90 pass / 43 "fail" at ~-99 dB error. That result is identical on unmodified main in this environment, so it's float noise, not this change.

Follow-up (part 2): midi_cc_output (iC) with the same C,L,N,X,O,P[,OSC] form, hooked beside note_output_handle_event in patches_event_has_voices. Decisions:

  • channel follows the synth's note_output MIDI channel, otherwise the synth number;
  • timing is event time, like note_output;
  • stacking several mappings on one CC is deferred.

It will only send when the target queue is the live delta queue. note_output looks like it has the same issue when events are stored into a patch; that's worth a separate small fix.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PykXkeeWw2aRpC6PLfpqTQ


Generated by Claude Code

A MIDI mapping payload that starts with a digit is now a parameter list
instead of a wire command template:

    ic<C>,<L>,<N>,<X>,<O>,<P>[,<OSC>][,<P>,<OSC>...]

P is an enum params id, exported to Python/JS as amy.PARAM_* (generated
from amy.h by scripts/gen_param_constants.py, which compiles against the
header so the computed enum values are the real ones). OSC is
voice-relative; a lone P means osc 0. Up to 4 P,OSC pairs share a value.

A direct mapping sets the matching amy_event field and sends the event
to the synth by the ordinary path, so values are in amy.send() units,
reach every voice, and bus params go to the synth's bus. Only params
with a plain event field are mappable; note-shaped params, osc refs,
breakpoints and resets are refused, and a refused payload no longer
deletes the mapping already on that CC. Integer params are rounded.

This is the groundwork for midi_cc_output, which will use the same
P,OSC form to echo parameter changes out as MIDI CCs.

Also: midi_mapping_init now checks its allocation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PykXkeeWw2aRpC6PLfpqTQ
@github-actions

Copy link
Copy Markdown
Contributor

🎛️ AMY HW CI (AMYboard bench)

Flashed this PR's AMY (LoadTestChord: 6-voice Juno patch=1, one held note every 2 s) onto the physical AMYboard and measured the smoothed render load as the chord grows — back-to-back with the same sketch built at the PR's merge base, so Δ is this PR's own cost.

✅ PASS — the bench ran the test to completion.

notes held main @ 44bf70d this PR Δ
1 981 991 +10
2 1136 1152 +16
3 1706 1723 +17
4 1875 1896 +21
5 2465 2498 +33
6 2600 2631 +31

Full chord settled render μs: 2633 (was 2600, Δ +1.3%) (peak 2640, 39 samples)

⬇️ Artifacts: serial log · load trace · report

Self-hosted bench (amyboardci). FAIL means only that the test could not run — the load values are informational, with no threshold and no audio compare. See tools/arduino_loadsweep/.

@dpwe
dpwe merged commit d3f8e98 into main Sep 28, 2026
12 checks passed
@bwhitman

Copy link
Copy Markdown
Collaborator

⛓️ tulipcc integration PR opened

This merge was pinned into tulipcc for full-system CI: shorepine/tulipcc#1379

Test it there and merge that PR to move tulipcc onto this AMY.

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.

3 participants