Skip to content

midi_cc_output: echo parameter changes out as MIDI CCs (#1175, part 2) - #1194

Merged
dpwe merged 2 commits into
mainfrom
midi-cc-output
Sep 29, 2026
Merged

dpwe merged 2 commits into
mainfrom
midi-cc-output

Conversation

@dpwe

@dpwe dpwe commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Part 2 of #1175, following #1191: midi_cc_output, the converse of midi_cc's direct-parameter form.

iC<C>,<L>,<N>,<X>,<O>,<P>[,<OSC>][,<P>,<OSC>...]
amy.send(synth=1, midi_cc_output='74,1,100,6400,0,%d' % amy.PARAM_FILTER_FREQ)

When parameter P of the synth changes at voice-relative OSC, the new value is sent out as CC C, with the L,N,X,O mapping inverted. The same arguments on ic and iC round-trip every 0..127 value unchanged; the test asserts this for linear and log (with offset) maps. The synth's own voices still get the change.

Behaviour, as agreed on the issue

  • Channel: the synth's note_output MIDI channel if it has one, otherwise the synth number. A synth outside 1..16 with no MIDI note output sends nothing, and making such a mapping prints a warning rather than guessing a channel.
  • Timing: the CC goes out when the event is added, the same moment note_output sends notes. Sequenced changes go out on their step; an event scheduled for later with time= sends early.
  • Stacking several mappings on one CC is deferred.

Details

  • Where it hooks: at the top of amy_event_to_deltas_queue for events with a synth. It has to be there because:
    • bus-only events never reach patches_event_has_voices;
    • bus distortion fields are cleared once they've been turned into deltas.
  • Stored events: only the live delta queue counts, so an event being stored into a patch sends nothing.
  • Reading the value: amy_event_get_param reads the value back from the same param_fields table midi_cc uses to write it, so values are compared in amy.send() units. Matching rules:
    • an event naming no osc counts for every osc;
    • the distortion fields shared between osc and bus scope are matched by whether an osc is named, so PARAM_DIST_MIX and PARAM_BUS_DIST_MIX are told apart.
  • Feedback guard: a change that arrived over MIDI isn't echoed unless the note output sets forward_midi_in. So ic and iC on the same CC don't form a loop.
  • Sent on change only, per mapping. With several P,OSC pairs, one event sends at most one CC.
  • Separate list: output mappings live in their own list. They never answer an incoming CC or mark a MIDI channel as active.
  • Template form refused: iC with a wire-command template is refused, since there's nothing for an output to run.
  • Clearing and state: iC<C> and iC255 clear mappings. yield_synth_commands emits them (codes 256..383) so they survive a state dump.
  • Generated files: Python kwarg midi_cc_output; godot/amy.gd and amy_api.generated.js regenerated.

Tests

  • New tests/test_midi_cc_output.c, 29 checks. It covers:
    • basic sends and clamping
    • dedupe
    • the full round trip
    • osc matching
    • multiple targets
    • bus params and the shared distortion scope
    • all three channel cases
    • the feedback guard, with and without forward_midi_in
    • output mappings ignoring incoming CCs
    • the template form being refused
    • clearing
    • state dump and restore
  • make ctest: all pass.
  • make test: 90 / 43 at ~-99 dB, identical to unchanged main in this environment.

Not in this PR: the note_output counterpart of the stored-patch guard (it sends notes from events being stored into a patch). That's a separate one-line fix.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PykXkeeWw2aRpC6PLfpqTQ


Generated by Claude Code

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

The converse of midi_cc's direct-parameter form: when synth parameter P
changes at voice-relative OSC, its value goes out as CC C with the
L,N,X,O mapping inverted, so the same arguments on ic and iC round-trip
every 0..127 value unchanged.

- Hooked at the top of amy_event_to_deltas_queue for synth events, before
  bus-only events return and bus distortion fields are spent, and only
  for the live delta queue (not events stored into a patch).
- Channel: the synth's MIDI note_output channel, else the synth number;
  a synth outside 1..16 with neither sends nothing, with a warning when
  the mapping is made.
- A change that arrived over MIDI isn't echoed unless the note output
  sets forward_midi_in, so ic + iC on one CC is not a feedback loop.
- Sent on change only. An event naming no osc counts for every osc; the
  shared distortion fields are matched by scope (osc named or not).
- Output mappings live in their own list: they never answer an incoming
  CC or make a MIDI channel active. iC<C> / iC255 clear; the state dump
  emits them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PykXkeeWw2aRpC6PLfpqTQ
@dpwe
dpwe merged commit f20caf7 into main Sep 29, 2026
12 checks passed
@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 @ 19bd93f this PR Δ
1 996 991 -5
2 1147 1149 +2
3 1727 1717 -10
4 1885 1880 -5
5 2504 2489 -15
6 2631 2627 -4

Full chord settled render μs: 2628 (was 2626, Δ +0.1%) (peak 2630, 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/.

@bwhitman

Copy link
Copy Markdown
Collaborator

⛓️ tulipcc integration PR opened

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

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