Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
midi_voice_modulation_adapter.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <algorithm>
8#include <array>
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12#include <limits>
13
14namespace pulp::audio {
15
17 bool active = false;
18 std::uint8_t channel = 0;
19 std::uint8_t note = 0;
20 std::uint8_t velocity = 0;
22 float pitch_cents = 0.0f;
23 float pressure = 0.0f;
24 float timbre = 0.0f;
25};
26
27template <std::size_t MaximumVoices> class MidiVoiceModulationAdapter {
28 public:
29 static_assert(MaximumVoices > 0);
30 static constexpr std::size_t capacity() noexcept {
31 return MaximumVoices;
32 }
33 static constexpr std::size_t maximum_event_amplification() noexcept {
34 return 0;
35 }
36
37 bool note_event(std::size_t voice_index, const midi::MidiEvent& event,
38 midi::MpeNoteGeneration note_id) noexcept {
39 if (voice_index >= MaximumVoices || note_id == 0 ||
40 (!event.is_note_on() && !event.is_note_off()))
41 return false;
42 auto& voice = voices_[voice_index];
43 const bool is_attack = event.is_note_on() && event.velocity() != 0;
44 if (is_attack) {
45 // note_id is also the slot's monotonic watermark while inactive;
46 // ordinary release must not allow an older delayed attack to
47 // resurrect after the voice has been reused.
48 if (note_id <= voice.note_id)
49 return false;
50 voice = {true, event.channel(), event.note(), event.velocity(), note_id, 0.0f, 0.0f,
51 0.0f};
52 return true;
53 }
54 if (!voice.active || voice.channel != event.channel() || voice.note != event.note() ||
55 voice.note_id != note_id)
56 return false;
57 const auto watermark = voice.note_id;
58 voice = {};
59 voice.note_id = watermark;
60 return true;
61 }
62
63 bool mpe_expression(std::size_t voice_index, const midi::MpeNoteState& expression) noexcept {
64 if (voice_index >= MaximumVoices || !expression.active || expression.note_id == 0)
65 return false;
66 if (!std::isfinite(expression.pitch_bend_semitones) ||
67 !std::isfinite(expression.pressure) || !std::isfinite(expression.timbre))
68 return false;
69 auto& voice = voices_[voice_index];
70 if (!voice.active || voice.channel != expression.channel || voice.note != expression.note ||
71 voice.note_id != expression.note_id)
72 return false;
73 const double pitch_cents = static_cast<double>(expression.pitch_bend_semitones) * 100.0;
74 if (!std::isfinite(pitch_cents) ||
75 std::abs(pitch_cents) > static_cast<double>(std::numeric_limits<float>::max()))
76 return false;
77 voice.pitch_cents = static_cast<float>(pitch_cents);
78 voice.pressure = std::clamp(expression.pressure, 0.0f, 1.0f);
79 voice.timbre = std::clamp(expression.timbre, 0.0f, 1.0f);
80 return true;
81 }
82
83 VoiceModulationResult write_voice(std::size_t voice_index, VoiceModulationBuffer& destination,
84 std::uint32_t frame_count) const noexcept {
85 if (voice_index >= MaximumVoices)
87 if (!destination.prepared())
89 if (frame_count > destination.max_frames())
91 if (destination.max_lanes() < 4)
93 const auto& voice = voices_[voice_index];
94 if (!std::isfinite(voice.pitch_cents) || !std::isfinite(voice.pressure) ||
95 !std::isfinite(voice.timbre))
97 auto result = destination.begin_block(frame_count);
98 if (!result.ok)
99 return result;
100 if (!(result = destination.add_constant(
102 voice.active ? static_cast<float>(voice.velocity) / 127.0f : 0.0f))
103 .ok)
104 return result;
105 if (!(result =
106 destination.add_constant(VoiceModulationTarget::PitchCents, voice.pitch_cents))
107 .ok)
108 return result;
109 if (!(result = destination.add_constant(VoiceModulationTarget::Pressure, voice.pressure))
110 .ok)
111 return result;
112 return destination.add_constant(VoiceModulationTarget::Timbre, voice.timbre);
113 }
114
115 const MidiVoiceModulationState* state(std::size_t voice_index) const noexcept {
116 return voice_index < MaximumVoices ? &voices_[voice_index] : nullptr;
117 }
118
119 bool release_voice(std::size_t voice_index, midi::MpeNoteGeneration note_id) noexcept {
120 if (voice_index >= MaximumVoices || note_id == 0 || !voices_[voice_index].active ||
121 voices_[voice_index].note_id != note_id)
122 return false;
123 const auto watermark = voices_[voice_index].note_id;
124 voices_[voice_index] = {};
125 voices_[voice_index].note_id = watermark;
126 return true;
127 }
128
129 void flush() noexcept {
130 voices_.fill({});
131 }
132 void reset() noexcept {
133 flush();
134 }
135 void hot_swap_reset() noexcept {
136 flush();
137 }
138
139 private:
140 std::array<MidiVoiceModulationState, MaximumVoices> voices_{};
141};
142
143} // namespace pulp::audio
Definition midi_voice_modulation_adapter.hpp:27
VoiceModulationResult write_voice(std::size_t voice_index, VoiceModulationBuffer &destination, std::uint32_t frame_count) const noexcept
Definition midi_voice_modulation_adapter.hpp:83
static constexpr std::size_t maximum_event_amplification() noexcept
Definition midi_voice_modulation_adapter.hpp:33
static constexpr std::size_t capacity() noexcept
Definition midi_voice_modulation_adapter.hpp:30
void hot_swap_reset() noexcept
Definition midi_voice_modulation_adapter.hpp:135
bool note_event(std::size_t voice_index, const midi::MidiEvent &event, midi::MpeNoteGeneration note_id) noexcept
Definition midi_voice_modulation_adapter.hpp:37
bool release_voice(std::size_t voice_index, midi::MpeNoteGeneration note_id) noexcept
Definition midi_voice_modulation_adapter.hpp:119
void reset() noexcept
Definition midi_voice_modulation_adapter.hpp:132
const MidiVoiceModulationState * state(std::size_t voice_index) const noexcept
Definition midi_voice_modulation_adapter.hpp:115
void flush() noexcept
Definition midi_voice_modulation_adapter.hpp:129
bool mpe_expression(std::size_t voice_index, const midi::MpeNoteState &expression) noexcept
Definition midi_voice_modulation_adapter.hpp:63
Definition voice_modulation_buffer.hpp:78
MPE (MIDI Polyphonic Expression) voice tracker.
Definition analyzer_provider.hpp:12
@ PitchCents
Definition voice_modulation_buffer.hpp:11
@ Gain
Definition voice_modulation_buffer.hpp:10
@ Pressure
Definition voice_modulation_buffer.hpp:13
@ Timbre
Definition voice_modulation_buffer.hpp:14
@ NonFiniteValue
Definition voice_modulation_buffer.hpp:35
@ NotPrepared
Definition voice_modulation_buffer.hpp:32
@ InvalidTarget
Definition voice_modulation_buffer.hpp:34
@ LaneOverflow
Definition voice_modulation_buffer.hpp:37
@ InvalidFrameCount
Definition voice_modulation_buffer.hpp:33
std::uint64_t MpeNoteGeneration
Stable identity assigned to one accepted MPE note-on generation.
Definition mpe_voice_tracker.hpp:36
Definition midi_voice_modulation_adapter.hpp:16
float pressure
Definition midi_voice_modulation_adapter.hpp:23
bool active
Definition midi_voice_modulation_adapter.hpp:17
std::uint8_t note
Definition midi_voice_modulation_adapter.hpp:19
float pitch_cents
Definition midi_voice_modulation_adapter.hpp:22
float timbre
Definition midi_voice_modulation_adapter.hpp:24
std::uint8_t channel
Definition midi_voice_modulation_adapter.hpp:18
std::uint8_t velocity
Definition midi_voice_modulation_adapter.hpp:20
midi::MpeNoteGeneration note_id
Definition midi_voice_modulation_adapter.hpp:21
Definition voice_modulation_buffer.hpp:40
Timestamped MIDI event combining a choc::midi::ShortMessage with timing info.
Definition message.hpp:32
Per-note state maintained by MpeVoiceTracker.
Definition mpe_voice_tracker.hpp:44