Skip to content

feat(state): scale a mapped MIDI CC onto a normalized parameter window#6476

Open
shipyard-local[bot] wants to merge 2 commits into
mainfrom
feature/timeline-midi-map
Open

feat(state): scale a mapped MIDI CC onto a normalized parameter window#6476
shipyard-local[bot] wants to merge 2 commits into
mainfrom
feature/timeline-midi-map

Conversation

@shipyard-local

@shipyard-local shipyard-local Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

Phase-3 item #6 of the Creative Timeline Engine — "a hardware MIDI controller maps to any parameter with zero driver code."

Verified against code first: the routing foundation already existed. pulp::state::MidiParameterMap already routes an incoming MIDI Control Change to any parameter by id, with learn mode, an omni/channel filter, a lock-free UI→audio command queue, and an RT-safe apply via StateStore::set_normalized_rt. The engine's only automatable parameter surface — pulp::timeline::AutomationTarget = variant<DeviceParameterTarget> — addresses a hosted device parameter by a 32-bit param_id, the same pulp::state::ParamID space the map already drives. So the DoD's mapping capability is satisfied by absorption; there is no separate engine parameter identity to bridge.

The one genuine gap was value scaling: the map always drove the parameter across its full range. This PR adds a per-mapping MidiMapScale — the normalized [0, 1] window a 0..127 CC sweep maps onto. Endpoints clamp into [0, 1] (on the UI thread), and min > max inverts. The default window is the full range, so existing mappings are unchanged and the 5 prior MidiParameterMap tests pass bit-identically. Shaping stays a property of the target ParamRange, not the map.

DoD proof

A headless Catch2 case sources the parameter id from the engine's own DeviceParameterTarget, registers it in a device StateStore, then learns a CC onto it and drives it through one generic learn→map→apply loop (full / middle-half / inverted windows) — proving reach into an engine-addressed parameter with zero per-controller/per-parameter code, using the engine type directly rather than asserting the equivalence.

Tests

test/test_midi_parameter_map_scaling.cpp (suite pulp-test-midi-parameter-map-scaling, registered in test/cmake/timeline_tests.cmake): 9 cases / 32 assertions, green Release. Covers scaled-window math, non-unit param range, inversion, out-of-range endpoint clamping, learn-carries-scale, unmapped/channel/remove edges, the engine-identity DoD proof, and an allocation-free apply-path assertion via RtAllocationProbe (negative-control-verified: an escaping allocation makes the probe fail, so the assertion has teeth). The suite links pulp::timeline for the DoD proof — test-only.

Scope

Out of scope (not required by the DoD): recording CC moves as timeline automation / transport arbitration, MIDI-learn UI, 14-bit CC, per-map response curves, MPE per-note expression, OSC.

Version-at-land assigns the SDK minor post-merge (no chore: bump versions commit; version_bump_check --mode=report exits 0).

🤖 Generated with Claude Code

https://claude.ai/code/session_01TVQHcnbg3t6TvXsdv6gXhS


🔎 Provenance

Agent claude
Machine m3
Tab Timeline v3
Directory /private/tmp/claude-501/cte-midi-map
Session c66f4a64-25ea-42e5-b184-d48611a066a2

Resume

claude --resume c66f4a64-25ea-42e5-b184-d48611a066a2

Jump to this tab

cmux surface focus C0B3DA54-39AF-4DAA-8089-AA08DAB71968

Relaunch (any agent)

cmux surface resume get --surface C0B3DA54-39AF-4DAA-8089-AA08DAB71968

Restore URLhttps://claude.ai/code/session_01TVQHcnbg3t6TvXsdv6gXhS

stamped 2026-07-22 22:41 UTC

danielraffel and others added 2 commits July 22, 2026 13:35
MidiParameterMap already routed a MIDI Control Change to any parameter by
id, with learn mode, an omni/channel filter, and an RT-safe lock-free
apply. It always drove the parameter across its full range, though — a
controller could not be scaled to a sub-range or inverted.

Give each mapping a MidiMapScale: the normalized [0, 1] window a 0..127 CC
sweep is mapped onto. Endpoints clamp into [0, 1]; min > max inverts. The
default window is the full range, so existing mappings are unchanged and
the prior tests pass untouched. Shaping stays a property of the target
ParamRange, not the map.

The apply path stays allocation-free (asserted with RtAllocationProbe).
Deferred, not required by the mapping foundation: MIDI-learn UI, 14-bit
(MSB/LSB) CC pairing, per-map response curves, MPE per-note expression
routing, and OSC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TVQHcnbg3t6TvXsdv6gXhS
…river code

The Creative Timeline Engine's only automatable parameter surface is a
hosted device parameter, addressed by timeline::DeviceParameterTarget whose
param_id is the device's stable 32-bit host-facing ID — the same
state::ParamID a StateStore uses and MidiParameterMap drives. There is no
separate engine parameter identity to bridge, so the map already reaches
every engine-automatable parameter with no per-controller or per-parameter
driver code.

Prove it against the engine type directly: source the id the engine would
automate from a DeviceParameterTarget, register it in the device store, then
learn a CC onto it and drive it through the generic learn/apply loop (full,
scaled, and inverted windows). Links pulp::timeline so the proof uses the
engine's own parameter identity rather than asserting the equivalence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TVQHcnbg3t6TvXsdv6gXhS
@shipyard-local
shipyard-local Bot enabled auto-merge July 22, 2026 22:42
@shipyard-local
shipyard-local Bot added this pull request to the merge queue Jul 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 23, 2026
@shipyard-local
shipyard-local Bot added this pull request to the merge queue Jul 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 23, 2026
@shipyard-local
shipyard-local Bot added this pull request to the merge queue Jul 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 23, 2026
@shipyard-local
shipyard-local Bot added this pull request to the merge queue Jul 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 23, 2026
@shipyard-local
shipyard-local Bot added this pull request to the merge queue Jul 23, 2026
@shipyard-local
shipyard-local Bot removed this pull request from the merge queue due to a manual request Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant