Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
mpe_voice_tracker.hpp
Go to the documentation of this file.
1#pragma once
2
24
25#include <pulp/midi/message.hpp>
26#include <pulp/midi/ump.hpp>
27#include <array>
28#include <cstdint>
29#include <functional>
30#include <optional>
31
32namespace pulp::midi {
33
36using MpeNoteGeneration = std::uint64_t;
37
45 bool active = false;
46 uint8_t channel = 0;
47 uint8_t note = 0;
48 uint8_t velocity = 0;
49 float pitch_bend_semitones = 0.0f;
50 float pressure = 0.0f;
51 float timbre = 0.0f;
53 bool is_upper_zone = false;
60 bool detached = false;
61};
62
68public:
70 static constexpr std::size_t kMaxNotes = 128;
71
73 static constexpr float kDefaultMemberBendSemitones = 48.0f;
75 static constexpr float kDefaultManagerBendSemitones = 2.0f;
76
79
80 // ── Configuration ──────────────────────────────────────────────────────
81
82 void set_config(const MpeConfig& cfg);
83 const MpeConfig& config() const { return config_; }
84
87 void set_member_bend_range(float semitones);
90 void set_manager_bend_range(float semitones);
92 float member_bend_range() const {
93 return member_bend_semi_[1];
94 }
95
96 float member_bend_range(uint8_t channel) const {
97 return member_bend_semi_[channel & 0x0F];
98 }
99
100 float manager_bend_range() const {
101 return manager_bend_semi_[0];
102 }
103
104 float manager_bend_range(bool upper_zone) const {
105 return manager_bend_semi_[upper_zone ? 1 : 0];
106 }
107
108 // ── Configuration from incoming MIDI ──────────────────────────────────
109
128 follow_config_messages_ = enabled;
129 }
131 return follow_config_messages_;
132 }
133
135 std::function<void(const MpeConfig&)> on_config_change;
136
144 void set_assignable_timbre_index(std::optional<uint8_t> cc_index) {
145 if (cc_index) assignable_timbre_index_ = static_cast<uint8_t>(*cc_index & 0x7F);
146 else assignable_timbre_index_.reset();
147 }
148 std::optional<uint8_t> assignable_timbre_index() const {
149 return assignable_timbre_index_;
150 }
151
152 // ── Event ingestion ────────────────────────────────────────────────────
153
158 bool process(const MidiEvent& event);
159
168 bool process(const UmpPacket& p);
169
170 // ── Queries ────────────────────────────────────────────────────────────
171
172 std::size_t active_count() const;
173
175 const MpeNoteState* find(uint8_t channel, uint8_t note) const;
176
179 std::size_t snapshot(MpeNoteState* out, std::size_t max) const;
180
185 bool note_generation_exhausted() const noexcept { return next_note_generation_ == 0; }
186
189 std::uint64_t refused_note_on_count() const noexcept { return refused_note_on_count_; }
190
196
200 std::size_t pending_note_off_count() const noexcept { return pending_note_off_count_; }
201
206
209 const std::array<MpeNoteState, kMaxNotes>& slots() const { return notes_; }
210
211 // ── Zone-wide (manager) state ─────────────────────────────────────────
212
213 struct ZoneState {
214 float pitch_bend_semitones = 0.0f;
215 float pressure = 0.0f;
216 float timbre = 0.0f;
217 };
218 const ZoneState& lower_zone_state() const { return lower_zone_state_; }
219 const ZoneState& upper_zone_state() const { return upper_zone_state_; }
220
221 // ── Callbacks (host-thread setup; invoked inline during process()) ────
222
232 std::function<bool(const MpeNoteState* note_off,
234
235 std::function<void(const MpeNoteState&)> on_note_on;
236 std::function<void(const MpeNoteState&)> on_note_off;
237 std::function<void(const MpeNoteState&)> on_pitch_bend;
238 std::function<void(const MpeNoteState&)> on_pressure;
239 std::function<void(const MpeNoteState&)> on_timbre;
240
241 // ── Lifecycle ──────────────────────────────────────────────────────────
242
243 void reset() noexcept;
244
245private:
246 void add_note(uint8_t ch, uint8_t note, uint8_t velocity, bool upper);
247 void remove_note(uint8_t ch, uint8_t note);
248 bool accept_note_lifecycle(const MpeNoteState* note_off,
249 const MpeNoteState* note_on) const;
250 bool defer_note_off(const MpeNoteState& note_off) noexcept;
251 std::optional<MpeNoteGeneration> take_note_generation() noexcept;
252 void record_generation_refusal() noexcept;
253
254 void update_channel_pitch_bend(uint8_t ch, float semitones, bool upper);
255 void update_channel_pressure(uint8_t ch, float pressure, bool upper);
256 void update_channel_timbre(uint8_t ch, float timbre, bool upper);
257
258 void track_registered_parameter(const MidiEvent& event);
259 void apply_registered_parameter(uint8_t ch, uint16_t param, uint8_t data_msb, uint8_t data_lsb);
260 void apply_configuration_message(uint8_t manager_channel, uint8_t member_count);
261 void release_notes_outside_zones();
262
263 void apply_manager_message(const MidiEvent& event, bool upper);
264 void apply_manager_ump(const UmpPacket& p, bool upper);
265
266 // Per-note helpers — MIDI 2.0 UMP per-note pitch bend / per-note CC
267 // target a specific (channel, note) pair rather than going via the
268 // member-channel cache, so they only update that one note.
269 void apply_per_note_pitch_bend(uint8_t ch, uint8_t note, float semitones);
270 void apply_per_note_timbre(uint8_t ch, uint8_t note, float timbre);
271 // UMP Per-Note Management (status 0xF0): `flags` bit-0 resets per-note
272 // controllers to their defaults, bit-1 detaches channel-level
273 // controllers from the note (`MpeNoteState::detached`).
274 void apply_per_note_management(uint8_t ch, uint8_t note, uint8_t flags);
275
276 static float ump_bend_normalised(uint32_t v32);
277 static std::array<float, 16> make_member_ranges() {
278 std::array<float, 16> r{};
280 return r;
281 }
282
283 MpeConfig config_;
284 std::array<MpeNoteState, kMaxNotes> notes_{};
285 std::array<float, 16> channel_pitch_bend_{};
286 std::array<float, 16> channel_pressure_{};
287 std::array<float, 16> channel_timbre_{};
288 ZoneState lower_zone_state_{};
289 ZoneState upper_zone_state_{};
290 std::array<float, 16> member_bend_semi_ = make_member_ranges();
291 std::array<float, 2> manager_bend_semi_{kDefaultManagerBendSemitones,
293
295 struct ParameterSelection {
296 uint8_t msb = 127;
297 uint8_t lsb = 127;
298 bool is_rpn = true;
299 uint8_t data_msb = 0;
300 uint8_t data_lsb = 0;
301 };
302 std::array<ParameterSelection, 16> selection_{};
303 bool follow_config_messages_ = true;
304 MpeNoteGeneration next_note_generation_ = 1;
305 std::uint64_t refused_note_on_count_ = 0;
306 std::array<MpeNoteState, kMaxNotes> pending_note_offs_{};
307 std::size_t pending_note_off_head_ = 0;
308 std::size_t pending_note_off_count_ = 0;
309 std::optional<uint8_t> assignable_timbre_index_;
310};
311
312} // namespace pulp::midi
std::uint64_t refused_note_on_count() const noexcept
Number of note-ons refused because the generation space was exhausted.
Definition mpe_voice_tracker.hpp:189
const MpeNoteState * find(uint8_t channel, uint8_t note) const
Find the newest active note for channel/note, or nullptr.
bool flush_pending_note_offs()
Retry deferred note-offs in FIFO order.
std::function< void(const MpeNoteState &)> on_pressure
Definition mpe_voice_tracker.hpp:238
static constexpr std::size_t kMaxNotes
Maximum concurrently active notes the tracker can hold.
Definition mpe_voice_tracker.hpp:70
std::optional< uint8_t > assignable_timbre_index() const
Definition mpe_voice_tracker.hpp:148
void set_follow_configuration_messages(bool enabled)
When enabled (the default), process(MidiEvent) follows two Registered Parameter Number messages as th...
Definition mpe_voice_tracker.hpp:127
std::function< void(const MpeNoteState &)> on_pitch_bend
Definition mpe_voice_tracker.hpp:237
bool follows_configuration_messages() const
Definition mpe_voice_tracker.hpp:130
const MpeConfig & config() const
Definition mpe_voice_tracker.hpp:83
std::size_t pending_note_off_count() const noexcept
Number of accepted physical note-offs waiting for a bounded lifecycle sink to make room.
Definition mpe_voice_tracker.hpp:200
void set_manager_bend_range(float semitones)
Set the manager-channel pitch-bend range in semitones (zone-wide bend) for both zones.
const ZoneState & upper_zone_state() const
Definition mpe_voice_tracker.hpp:219
std::function< void(const MpeNoteState &)> on_note_on
Definition mpe_voice_tracker.hpp:235
std::function< void(const MpeNoteState &)> on_note_off
Definition mpe_voice_tracker.hpp:236
std::function< void(const MpeConfig &)> on_config_change
Invoked after an incoming MPE Configuration Message changed config().
Definition mpe_voice_tracker.hpp:135
float manager_bend_range() const
Manager range of the lower zone.
Definition mpe_voice_tracker.hpp:100
std::size_t active_count() const
void set_member_bend_range(float semitones)
Set the member-channel pitch-bend range in semitones (per-note bend) for every channel.
MpeVoiceTracker(MpeConfig config=MpeConfig::standard_lower())
Definition mpe_voice_tracker.hpp:77
bool process(const MidiEvent &event)
Process a single MIDI 1.0 event.
const std::array< MpeNoteState, kMaxNotes > & slots() const
Direct access to the underlying slots (active flag identifies live notes).
Definition mpe_voice_tracker.hpp:209
void set_config(const MpeConfig &cfg)
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.
Definition mpe_voice_tracker.hpp:144
const ZoneState & lower_zone_state() const
Definition mpe_voice_tracker.hpp:218
std::function< void(const MpeNoteState &)> on_timbre
Definition mpe_voice_tracker.hpp:239
float member_bend_range() const
Member-channel range for channel 1 (the first lower-zone member).
Definition mpe_voice_tracker.hpp:92
std::size_t snapshot(MpeNoteState *out, std::size_t max) const
Iterate all active notes.
float member_bend_range(uint8_t channel) const
Member-channel range for one specific channel (0-15).
Definition mpe_voice_tracker.hpp:96
bool process(const UmpPacket &p)
Process a MIDI 2.0 UMP Channel Voice packet.
static constexpr float kDefaultManagerBendSemitones
Default manager-channel pitch-bend range (MPE spec).
Definition mpe_voice_tracker.hpp:75
static constexpr float kDefaultMemberBendSemitones
Default member-channel pitch-bend range (MPE spec).
Definition mpe_voice_tracker.hpp:73
float manager_bend_range(bool upper_zone) const
Manager range of one zone.
Definition mpe_voice_tracker.hpp:104
bool note_generation_exhausted() const noexcept
True once every representable nonzero note generation has been issued.
Definition mpe_voice_tracker.hpp:185
bool advance_note_generation(MpeNoteGeneration next) noexcept
Advance the next generation without issuing it.
std::function< bool(const MpeNoteState *note_off, const MpeNoteState *note_on)> on_note_lifecycle
Transactional note-lifecycle sink.
Definition mpe_voice_tracker.hpp:233
Definition custom_node_events.hpp:26
std::uint64_t MpeNoteGeneration
Stable identity assigned to one accepted MPE note-on generation.
Definition mpe_voice_tracker.hpp:36
STL namespace.
Timestamped MIDI event combining a choc::midi::ShortMessage with timing info.
Definition message.hpp:32
Definition ump.hpp:374
static MpeConfig standard_lower(uint8_t members=15)
Definition ump.hpp:379
Per-note state maintained by MpeVoiceTracker.
Definition mpe_voice_tracker.hpp:44
bool detached
True after a UMP Per-Note Management message with the "detachcontrollers" flag landed on this note: s...
Definition mpe_voice_tracker.hpp:60
float timbre
CC 74 (0..1); same value as slide.
Definition mpe_voice_tracker.hpp:51
float pitch_bend_semitones
Current per-note pitch bend in semitones.
Definition mpe_voice_tracker.hpp:49
float pressure
Channel pressure (0..1).
Definition mpe_voice_tracker.hpp:50
uint8_t channel
MPE member channel the note lives on.
Definition mpe_voice_tracker.hpp:46
MpeNoteGeneration note_id
Monotonic generation assigned on note-on (never 0).
Definition mpe_voice_tracker.hpp:52
bool active
Definition mpe_voice_tracker.hpp:45
uint8_t note
MIDI note number (0-127).
Definition mpe_voice_tracker.hpp:47
uint8_t velocity
Note-on velocity (0-127).
Definition mpe_voice_tracker.hpp:48
bool is_upper_zone
true if this note lives in the upper zone
Definition mpe_voice_tracker.hpp:53
Definition mpe_voice_tracker.hpp:213
float timbre
Zone-wide timbre (CC 74).
Definition mpe_voice_tracker.hpp:216
float pitch_bend_semitones
Zone-wide pitch bend.
Definition mpe_voice_tracker.hpp:214
float pressure
Zone-wide pressure.
Definition mpe_voice_tracker.hpp:215
Definition ump.hpp:84