Tracks MPE voice state across channels. More...
#include <mpe_voice_tracker.hpp>
Classes | |
| struct | ZoneState |
Public Member Functions | |
| MpeVoiceTracker (MpeConfig config=MpeConfig::standard_lower()) | |
| void | set_config (const MpeConfig &cfg) |
| const MpeConfig & | config () const |
| void | set_member_bend_range (float semitones) |
| Set the member-channel pitch-bend range in semitones (per-note bend) for every channel. | |
| void | set_manager_bend_range (float semitones) |
| Set the manager-channel pitch-bend range in semitones (zone-wide bend) for both zones. | |
| float | member_bend_range () const |
| Member-channel range for channel 1 (the first lower-zone member). | |
| float | member_bend_range (uint8_t channel) const |
| Member-channel range for one specific channel (0-15). | |
| float | manager_bend_range () const |
| Manager range of the lower zone. | |
| float | manager_bend_range (bool upper_zone) const |
| Manager range of one zone. | |
| void | set_follow_configuration_messages (bool enabled) |
| When enabled (the default), process(MidiEvent) follows two Registered Parameter Number messages as they arrive: | |
| bool | follows_configuration_messages () const |
| void | set_assignable_timbre_index (std::optional< uint8_t > cc_index) |
| Bind a MIDI 2.0 Assignable Per-Note Controller index (status 0x10) to per-note timbre. | |
| std::optional< uint8_t > | assignable_timbre_index () const |
| bool | process (const MidiEvent &event) |
| Process a single MIDI 1.0 event. | |
| bool | process (const UmpPacket &p) |
| Process a MIDI 2.0 UMP Channel Voice packet. | |
| std::size_t | active_count () const |
| const MpeNoteState * | find (uint8_t channel, uint8_t note) const |
| Find the newest active note for channel/note, or nullptr. | |
| std::size_t | snapshot (MpeNoteState *out, std::size_t max) const |
| Iterate all active notes. | |
| bool | note_generation_exhausted () const noexcept |
| True once every representable nonzero note generation has been issued. | |
| std::uint64_t | refused_note_on_count () const noexcept |
| Number of note-ons refused because the generation space was exhausted. | |
| bool | advance_note_generation (MpeNoteGeneration next) noexcept |
| Advance the next generation without issuing it. | |
| std::size_t | pending_note_off_count () const noexcept |
| Number of accepted physical note-offs waiting for a bounded lifecycle sink to make room. | |
| bool | flush_pending_note_offs () |
| Retry deferred note-offs in FIFO order. | |
| const std::array< MpeNoteState, kMaxNotes > & | slots () const |
| Direct access to the underlying slots (active flag identifies live notes). | |
| const ZoneState & | lower_zone_state () const |
| const ZoneState & | upper_zone_state () const |
| void | reset () noexcept |
Public Attributes | |
| std::function< void(const MpeConfig &)> | on_config_change |
| Invoked after an incoming MPE Configuration Message changed config(). | |
| std::function< bool(const MpeNoteState *note_off, const MpeNoteState *note_on)> | on_note_lifecycle |
| Transactional note-lifecycle sink. | |
| std::function< void(const MpeNoteState &)> | on_note_on |
| std::function< void(const MpeNoteState &)> | on_note_off |
| std::function< void(const MpeNoteState &)> | on_pitch_bend |
| std::function< void(const MpeNoteState &)> | on_pressure |
| std::function< void(const MpeNoteState &)> | on_timbre |
Static Public Attributes | |
| static constexpr std::size_t | kMaxNotes = 128 |
| Maximum concurrently active notes the tracker can hold. | |
| static constexpr float | kDefaultMemberBendSemitones = 48.0f |
| Default member-channel pitch-bend range (MPE spec). | |
| static constexpr float | kDefaultManagerBendSemitones = 2.0f |
| Default manager-channel pitch-bend range (MPE spec). | |
Tracks MPE voice state across channels.
Not thread-safe; intended to be owned by a single consumer (usually the plugin's process() path or its format-adapter preprocessor).
|
inlineexplicit |
| std::size_t pulp::midi::MpeVoiceTracker::active_count | ( | ) | const |
|
noexcept |
Advance the next generation without issuing it.
This control-thread operation succeeds only while no notes are active and only moves the cursor forward; zero, rewind, and exhausted-tracker requests fail. Primarily useful for deterministic lifecycle tests and state handoff.
|
inline |
|
inline |
| const MpeNoteState * pulp::midi::MpeVoiceTracker::find | ( | uint8_t | channel, |
| uint8_t | note ) const |
Find the newest active note for channel/note, or nullptr.
| bool pulp::midi::MpeVoiceTracker::flush_pending_note_offs | ( | ) |
Retry deferred note-offs in FIFO order.
Returns true when the queue is empty. A sidecar calls this once, at sample offset zero, before ingesting each new block. Accepted releases invoke on_note_off exactly once.
|
inline |
|
inline |
|
inline |
Manager range of the lower zone.
|
inline |
Manager range of one zone.
|
inline |
Member-channel range for channel 1 (the first lower-zone member).
|
inline |
Member-channel range for one specific channel (0-15).
|
inlinenoexcept |
True once every representable nonzero note generation has been issued.
Further note-ons are consumed by process() but refused without changing note state or invoking on_note_on. Exhaustion is permanent for this tracker instance, including across reset() and set_config().
|
inlinenoexcept |
Number of accepted physical note-offs waiting for a bounded lifecycle sink to make room.
While nonzero, fresh note-ons and retriggers are consumed without mutation so the fixed queue cannot be overrun.
| bool pulp::midi::MpeVoiceTracker::process | ( | const MidiEvent & | event | ) |
Process a single MIDI 1.0 event.
Returns true if the event belonged to a known MPE zone (manager or member), false otherwise. A false return indicates the host should treat the event as a plain MIDI message (no MPE routing).
| bool pulp::midi::MpeVoiceTracker::process | ( | const UmpPacket & | p | ) |
Process a MIDI 2.0 UMP Channel Voice packet.
Returns true when the packet belongs to a known MPE zone (and was consumed), false otherwise. MIDI 2.0 per-note pitch bend (status 0x60) is routed directly to the matching note rather than going via member-channel state, preserving the full per-note resolution UMP carries.
Non-Channel-Voice UMP types (Utility, System, SysEx, Data128) are ignored and return false.
|
inlinenoexcept |
Number of note-ons refused because the generation space was exhausted.
Saturates at the maximum representable value.
|
noexcept |
|
inline |
Bind a MIDI 2.0 Assignable Per-Note Controller index (status 0x10) to per-note timbre.
The assignable PNC space is host- configured per the UMP spec; this hook lets a Pulp plugin advertise which assignable index it accepts as timbre. Pass std::nullopt to disable assignable-PNC routing entirely (registered PNC 74, status 0x00, continues to route to timbre regardless). Range: 0-127.
| void pulp::midi::MpeVoiceTracker::set_config | ( | const MpeConfig & | cfg | ) |
|
inline |
When enabled (the default), process(MidiEvent) follows two Registered Parameter Number messages as they arrive:
| void pulp::midi::MpeVoiceTracker::set_manager_bend_range | ( | float | semitones | ) |
Set the manager-channel pitch-bend range in semitones (zone-wide bend) for both zones.
Non-positive values are ignored.
| void pulp::midi::MpeVoiceTracker::set_member_bend_range | ( | float | semitones | ) |
Set the member-channel pitch-bend range in semitones (per-note bend) for every channel.
Non-positive values are ignored.
|
inline |
Direct access to the underlying slots (active flag identifies live notes).
Stable indices across calls.
| std::size_t pulp::midi::MpeVoiceTracker::snapshot | ( | MpeNoteState * | out, |
| std::size_t | max ) const |
Iterate all active notes.
Writes up to max records to out and returns the count written.
|
inline |
|
staticconstexpr |
Default manager-channel pitch-bend range (MPE spec).
|
staticconstexpr |
Default member-channel pitch-bend range (MPE spec).
|
staticconstexpr |
Maximum concurrently active notes the tracker can hold.
| std::function<void(const MpeConfig&)> pulp::midi::MpeVoiceTracker::on_config_change |
Invoked after an incoming MPE Configuration Message changed config().
| std::function<bool(const MpeNoteState* note_off, const MpeNoteState* note_on)> pulp::midi::MpeVoiceTracker::on_note_lifecycle |
Transactional note-lifecycle sink.
note_off and note_on are null when that half is absent; a retrigger supplies both, in retirement-then- start order. Return false when the complete lifecycle cannot be represented. Rejection leaves a retrigger slot and generation cursor unchanged and suppresses the observer callbacks below.
The pointers are valid only for the duration of the call. This hook is intended for bounded realtime sinks such as MpeBuffer; ordinary observers should continue to use on_note_on / on_note_off.
| std::function<void(const MpeNoteState&)> pulp::midi::MpeVoiceTracker::on_note_off |
| std::function<void(const MpeNoteState&)> pulp::midi::MpeVoiceTracker::on_note_on |
| std::function<void(const MpeNoteState&)> pulp::midi::MpeVoiceTracker::on_pitch_bend |
| std::function<void(const MpeNoteState&)> pulp::midi::MpeVoiceTracker::on_pressure |
| std::function<void(const MpeNoteState&)> pulp::midi::MpeVoiceTracker::on_timbre |