Map incoming MIDI Control Change messages to plugin parameters, with a "learn" mode — the equivalent of a host's MIDI-mapping panel, owned by the plugin so it works in every host. More...
#include <algorithm>#include <array>#include <cmath>#include <cstddef>#include <cstdint>#include <pulp/runtime/spsc_queue.hpp>#include <pulp/state/store.hpp>Go to the source code of this file.
Classes | |
| struct | pulp::state::MidiMapScale |
| Normalized [0, 1] output window a CC sweep is mapped onto. More... | |
| class | pulp::state::MidiParameterMap |
Namespaces | |
| namespace | pulp |
| namespace | pulp::state |
| SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the bulk, observable state a step sequencer / mod-matrix / pattern engine holds outside its automatable parameter set (e.g. | |
Map incoming MIDI Control Change messages to plugin parameters, with a "learn" mode — the equivalent of a host's MIDI-mapping panel, owned by the plugin so it works in every host.
Thread model: mappings are added / armed from the UI thread and applied from the audio thread. UI calls go through a lock-free command queue; the audio thread drains it once per block via pump() and then routes each incoming CC through handle_cc(), which writes the parameter with set_normalized_rt. The value change is picked up by the format adapter's post-process diff and pushed to the host, so a CC-driven move is recorded as parameter automation.
Value scaling: a controller need not drive its parameter across the whole range. Each mapping carries a MidiMapScale — the normalized [0, 1] window the CC sweep is mapped onto. CC 0..127 spans min..max; the endpoints are clamped into [0, 1], and min > max inverts the response (a knob turned up lowers the parameter). The default window is the full range, so a mapping created without a scale behaves exactly as an unscaled 0..127 → 0..1 sweep. Shaping (log/skew) is a property of the target parameter's own ParamRange, not of the map — the map only supplies the normalized window.
Usage (in the processor): midi_map_.pump(); // top of process() for (auto& e : midi_in) if (e.is_cc()) midi_map_.handle_cc(state(), e.channel(), e.cc_number(), e.cc_value()); And from the UI: midi_map_.arm_learn(kCutoff); // next CC binds to kCutoff midi_map_.set_mapping(0, 74, kCutoff); // or map explicitly midi_map_.set_mapping(0, 1, kMix, {0.25f, 0.75f}); // sweep only the middle midi_map_.set_mapping(0, 7, kGain, {1.0f, 0.0f}); // inverted