Per-note expression → MIDI 1.0 synthesis shared by the format adapters. More...
#include <pulp/midi/message.hpp>#include <pulp/midi/mpe_voice_tracker.hpp>#include <algorithm>#include <cmath>#include <cstdint>Go to the source code of this file.
Namespaces | |
| namespace | pulp |
| namespace | pulp::format |
| What the product grants a device: the quota table indexed by capability tier and live thermal state. | |
| namespace | pulp::format::mpe |
Functions | |
| std::uint8_t | pulp::format::mpe::to_7bit (double norm) noexcept |
| Quantize a normalized [0,1] axis value to a 7-bit MIDI data byte. | |
| midi::MidiEvent | pulp::format::mpe::pitch_bend_from_semitones (std::uint8_t channel, double semitones) |
| Per-note pitch as a channel-wide 14-bit pitch bend. | |
| midi::MidiEvent | pulp::format::mpe::channel_pressure (std::uint8_t channel, double norm) |
| Per-note pressure as channel pressure (status 0xD0). | |
| midi::MidiEvent | pulp::format::mpe::timbre_cc (std::uint8_t channel, double norm) |
| Per-note timbre as CC 74. | |
Variables | |
| constexpr std::uint8_t | pulp::format::mpe::kTimbreController = 74 |
| MPE timbre controller. | |
Per-note expression → MIDI 1.0 synthesis shared by the format adapters.
Every host format carries per-note expression in its own currency — CLAP clap_event_note_expression_t, VST3 kNoteExpressionValueEvent, AU v3 AUParameterEvent ramps — but Pulp's per-note model is MPE: one note per member channel, fed by MpeVoiceTracker from a channel-wide MIDI 1.0 stream. So each adapter decodes its own event struct and then synthesizes the same three channel-wide messages for the same three MPE axes:
The per-format decode (which type ID means which axis, and how a raw host value maps onto the axis) stays in the adapter — the formats genuinely disagree there. What lives here is only the synthesis, which was copy-pasted per adapter with nothing that noticed when a copy drifted.
Scoping. Bridging noteId/key-targeted expression onto channel-wide messages is exact when each note lives on its own MPE member channel (the expressive-controller case MPE is built for); when several notes share one channel the expression collapses to channel-wide. That is a property of the MPE model, not of this mapping.
Real-time contract. Pure functions over scalars; allocation-, lock-, and branch-light. Callers set sample_offset on the returned event themselves, because each format reads the block offset from a different field.