Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
panic.hpp
Go to the documentation of this file.
1#pragma once
2
20
21#include <pulp/midi/buffer.hpp>
23#include <pulp/midi/panic.hpp>
24
25#include <array>
26#include <atomic>
27#include <cstddef>
28#include <cstdint>
29
30namespace pulp::format {
31
33enum class PanicMode : std::uint8_t {
40};
41
46 bool serviced = false;
50 bool midi_out = false;
51};
52
55 bool serviced = false;
58 std::size_t events_emitted = 0;
61 std::size_t events_deferred = 0;
65 bool complete = true;
67 bool carried = false;
68};
69
74 public:
77 void request(PanicMode mode) noexcept {
78 const auto wanted = static_cast<std::uint8_t>(mode);
79 auto current = pending_.load(std::memory_order_relaxed);
80 while (current < wanted &&
81 !pending_.compare_exchange_weak(current, wanted, std::memory_order_acq_rel,
82 std::memory_order_relaxed)) {
83 }
84 }
85
87 bool take(PanicMode& mode) noexcept {
88 const auto taken = pending_.exchange(0, std::memory_order_acq_rel);
89 if (taken == 0)
90 return false;
91 mode = static_cast<PanicMode>(taken);
92 return true;
93 }
94
96 bool carrying() const noexcept {
97 return carry_.load(std::memory_order_acquire) != 0;
98 }
99
105 std::uint64_t deferred_events() const noexcept {
106 return deferred_total_.load(std::memory_order_relaxed);
107 }
108
109 bool pending() const noexcept {
110 return pending_.load(std::memory_order_acquire) != 0;
111 }
112
115 void attach(bool midi_out) noexcept {
116 midi_out_.store(midi_out, std::memory_order_relaxed);
117 attached_.store(true, std::memory_order_release);
118 }
119 void detach() noexcept {
120 attached_.store(false, std::memory_order_release);
121 }
122
124 const bool attached = attached_.load(std::memory_order_acquire);
125 return {attached, attached && midi_out_.load(std::memory_order_relaxed)};
126 }
127
130 void observe_midi_out(const midi::MidiBuffer& midi_out) noexcept {
131 if (!midi_out_.load(std::memory_order_relaxed))
132 return;
133 for (const auto& event : midi_out)
134 emitted_.process(event);
135 }
136
138 const midi::MidiKeyboardState& emitted_notes() const noexcept {
139 return emitted_;
140 }
141
149 std::int32_t sample_offset = 0) noexcept {
150 PanicOutcome outcome;
151 outcome.serviced = true;
152 outcome.mode = mode;
153 bool full = false;
154 const auto put = [&](const midi::MidiEvent& event) {
155 if (!full && out.add(event)) {
156 ++outcome.events_emitted;
157 return true;
158 }
159 full = true;
160 ++outcome.events_deferred;
161 return false;
162 };
163 midi::for_each_held_note(emitted_, 0xFFFFu, [&](std::uint8_t ch, std::uint8_t note) {
164 auto event = midi::MidiEvent::note_off(ch, note);
165 event.sample_offset = sample_offset;
166 if (put(event))
167 emitted_.process(event); // ended: no longer held downstream
168 });
169 const auto scope =
171 std::array<midi::MidiEvent, midi::kMaxPanicMessages> messages;
172 const auto count = midi::panic_messages(scope, messages, sample_offset);
173 std::size_t next = message_cursor_;
174 for (; next < count; ++next) {
175 if (!put(messages[next]))
176 break;
177 }
178 // Everything after the first refused message is deferred too.
179 outcome.events_deferred += next < count ? count - next - 1 : 0;
180 message_cursor_ = static_cast<std::uint16_t>(next < count ? next : 0);
181 outcome.complete = !full;
182 if (full) {
183 const auto wanted = static_cast<std::uint8_t>(mode);
184 if (carry_.load(std::memory_order_relaxed) < wanted)
185 carry_.store(wanted, std::memory_order_release);
186 deferred_total_.fetch_add(outcome.events_deferred, std::memory_order_relaxed);
187 } else {
188 carry_.store(0, std::memory_order_release);
189 emitted_.reset();
190 }
191 return outcome;
192 }
193
195 bool take_carry(PanicMode& mode) noexcept {
196 const auto carried = carry_.load(std::memory_order_acquire);
197 if (carried == 0)
198 return false;
199 mode = static_cast<PanicMode>(carried);
200 return true;
201 }
202
204 void restart_messages() noexcept {
205 message_cursor_ = 0;
206 }
207
208 private:
209 std::atomic<std::uint8_t> pending_{0};
210 std::atomic<std::uint8_t> carry_{0};
211 std::atomic<std::uint64_t> deferred_total_{0};
212 std::uint16_t message_cursor_ = 0; // audio thread only
213 std::atomic<bool> attached_{false};
214 std::atomic<bool> midi_out_{false};
215 midi::MidiKeyboardState emitted_;
216};
217
224template <typename Proc>
225inline PanicOutcome service_panic(Proc& processor, PanicState& state, midi::MidiBuffer& midi_out,
226 std::int32_t sample_offset = 0) noexcept {
228 const bool fresh = state.take(mode);
229 bool carried = false;
230 if (!fresh) {
231 if (!state.take_carry(mode))
232 return {};
233 carried = true;
234 } else {
235 state.restart_messages();
236 // A new request while an earlier panic is still being sent keeps the
237 // harder of the two.
238 PanicMode carried_mode = PanicMode::release;
239 if (state.take_carry(carried_mode) &&
240 static_cast<std::uint8_t>(carried_mode) > static_cast<std::uint8_t>(mode))
241 mode = carried_mode;
242 processor.on_panic(mode);
243 }
244 if (!state.capabilities().midi_out) {
245 PanicOutcome outcome;
246 outcome.serviced = true;
247 outcome.mode = mode;
248 return outcome;
249 }
250 auto outcome = state.emit_midi(mode, midi_out, sample_offset);
251 outcome.carried = carried;
252 return outcome;
253}
254
255} // namespace pulp::format
Lock-free panic request and output-note state for one Processor.
Definition panic.hpp:73
PanicOutcome emit_midi(PanicMode mode, midi::MidiBuffer &out, std::int32_t sample_offset=0) noexcept
Write the panic into out (audio thread): note-offs for the held notes, then the channel messages for ...
Definition panic.hpp:148
void attach(bool midi_out) noexcept
Adapter wiring, off the audio thread.
Definition panic.hpp:115
void detach() noexcept
Definition panic.hpp:119
bool pending() const noexcept
Definition panic.hpp:109
bool take_carry(PanicMode &mode) noexcept
Mode of a panic still being sent (audio thread).
Definition panic.hpp:195
void observe_midi_out(const midi::MidiBuffer &midi_out) noexcept
Track the notes the plug-in sent downstream (audio thread, after process()).
Definition panic.hpp:130
void request(PanicMode mode) noexcept
Raise a panic request from any thread.
Definition panic.hpp:77
bool take(PanicMode &mode) noexcept
Consume the pending request (audio thread). False when none is pending.
Definition panic.hpp:87
const midi::MidiKeyboardState & emitted_notes() const noexcept
Notes currently believed held downstream.
Definition panic.hpp:138
std::uint64_t deferred_events() const noexcept
Total events that did not fit the MIDI output buffer in some block and were deferred to a later one.
Definition panic.hpp:105
PanicCapabilities capabilities() const noexcept
Definition panic.hpp:123
bool carrying() const noexcept
A panic whose MIDI did not fit in one block and is still being sent.
Definition panic.hpp:96
void restart_messages() noexcept
Start the message sequence from the top (a new request arrived).
Definition panic.hpp:204
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
bool add(const MidiEvent &event)
Append a MIDI event to the buffer.
Definition buffer.hpp:98
Tracks the state of all 128 MIDI keys across 16 channels.
Definition keyboard_state.hpp:35
Polyphonic key tracking with velocity and listener callbacks.
Pure MIDI 1.0 "panic" message generation and classification.
What the product grants a device: the quota table indexed by capability tier and live thermal state.
Definition aax_editor.hpp:31
PanicMode
How hard a panic stops the plug-in.
Definition panic.hpp:33
@ release
Release only: voices enter their normal release and tails ring out (MIDI CC 123 semantic).
Definition panic.hpp:39
@ immediate
Hard stop: voices are reset and release tails cut (MIDI CC 120 semantic).
Definition panic.hpp:36
PanicOutcome service_panic(Proc &processor, PanicState &state, midi::MidiBuffer &midi_out, std::int32_t sample_offset=0) noexcept
Service a pending panic request (audio thread, before process()).
Definition panic.hpp:225
std::size_t panic_messages(const PanicScope &scope, std::span< MidiEvent > out, std::int32_t sample_offset=0) noexcept
Write the panic messages for scope into out, channel by channel.
Definition panic.hpp:113
void for_each_held_note(const MidiKeyboardState &keys, std::uint16_t channel_mask, Fn &&fn) noexcept(noexcept(fn(std::uint8_t{}, std::uint8_t{})))
Call fn(channel, note) for every key keys reports as held on the channels in channel_mask,...
Definition panic.hpp:147
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
What a panic request can do in the current host.
Definition panic.hpp:43
bool serviced
A host adapter is servicing requests.
Definition panic.hpp:46
bool midi_out
The servicing adapter has MIDI output, so panic messages and note-offs reach downstream devices.
Definition panic.hpp:50
What service_panic() did.
Definition panic.hpp:54
std::size_t events_deferred
Events the buffer had no room for this block.
Definition panic.hpp:61
std::size_t events_emitted
Events written to the MIDI output buffer.
Definition panic.hpp:58
bool carried
This block continued a panic begun in an earlier block.
Definition panic.hpp:67
bool serviced
Definition panic.hpp:55
PanicMode mode
Definition panic.hpp:56
bool complete
True when the whole panic has been written.
Definition panic.hpp:65
Timestamped MIDI event combining a choc::midi::ShortMessage with timing info.
Definition message.hpp:32
static MidiEvent note_off(uint8_t channel, uint8_t note, uint8_t velocity=0)
Create a Note Off event.
Definition message.hpp:54
Which channels and which message kinds a panic covers.
Definition panic.hpp:48
static constexpr PanicScope release_only()
Panic that lets release tails ring out (no CC 120).
Definition panic.hpp:59