Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
au_v2_midi_processor.hpp
Go to the documentation of this file.
1#pragma once
2
4
5// The AU v2 MIDI-processor adapter wraps Apple's AudioUnitSDK (Apple-only,
6// developer-supplied). The whole header is gated on __APPLE__ so it stays
7// self-contained — an empty no-op — on the Linux header-hygiene check and any
8// non-Apple TU.
9#if defined(__APPLE__)
10
11#include <AudioUnitSDK/MusicDeviceBase.h>
12
17#include <pulp/midi/buffer.hpp>
20
21#include <array>
22#include <cstdint>
23#include <memory>
24
25namespace pulp::format::au {
26
61class PulpAUMidiProcessor : public ausdk::MusicDeviceBase {
62public:
63 explicit PulpAUMidiProcessor(AudioComponentInstance ci);
64 // Multi-plugin-bundle ctor: the component is bound to its own factory
65 // lexically (no global registry lookup). The single-arg ctor delegates here
66 // with the legacy `registered_factory()` for single-plugin bundles.
67 PulpAUMidiProcessor(AudioComponentInstance ci, ProcessorFactory factory);
68
69 OSStatus GetParameterList(AudioUnitScope inScope,
70 AudioUnitParameterID* outParameterList,
71 UInt32& outNumParameters) override;
72 OSStatus GetParameterInfo(AudioUnitScope inScope,
73 AudioUnitParameterID inParameterID,
74 AudioUnitParameterInfo& outParameterInfo) override;
75 OSStatus GetParameterValueStrings(AudioUnitScope inScope,
76 AudioUnitParameterID inParameterID,
77 CFArrayRef* outStrings) override;
78
79 // Single source of truth: the host reads/writes the plugin's StateStore
80 // directly — no separate Globals copy to reconcile per block. See the auv2
81 // skill's "Parameters are single-source-of-truth" section.
82 OSStatus GetParameter(AudioUnitParameterID inID, AudioUnitScope inScope,
83 AudioUnitElement inElement, Float32& outValue) override;
84 OSStatus SetParameter(AudioUnitParameterID inID, AudioUnitScope inScope,
85 AudioUnitElement inElement, Float32 inValue,
86 UInt32 inBufferOffsetInFrames) override;
87
88 OSStatus GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope,
89 AudioUnitElement inElement, UInt32& outDataSize,
90 bool& outWritable) override;
91 OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope,
92 AudioUnitElement inElement, void* outData) override;
93 OSStatus SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope,
94 AudioUnitElement inElement, const void* inData,
95 UInt32 inDataSize) override;
96
97 OSStatus Initialize() override;
98 void Cleanup() override;
99
100 bool StreamFormatWritable(AudioUnitScope scope,
101 AudioUnitElement element) override;
102 bool CanScheduleParameters() const noexcept override;
103
104 OSStatus Render(AudioUnitRenderActionFlags& ioActionFlags,
105 const AudioTimeStamp& inTimeStamp,
106 UInt32 inNumberFrames) override;
107
112 OSStatus GetPresets(CFArrayRef* outData) const override;
113
118 OSStatus NewFactoryPresetSet(const AUPreset& inNewFactoryPreset) override;
119
124 return factory_presets_;
125 }
126
127 OSStatus SaveState(CFPropertyListRef* outData) override;
128 OSStatus RestoreState(CFPropertyListRef plist) override;
129
130 Float64 GetLatency() override;
131
132protected:
135
142 OSStatus HandleMIDIEvent(UInt8 inStatus, UInt8 inChannel,
143 UInt8 inData1, UInt8 inData2,
144 UInt32 inStartFrame) override;
145
149 OSStatus HandleSysEx(const UInt8* inData, UInt32 inLength) override;
150
151private:
156 bool bypass_engaged() const noexcept;
157
158 // The store is declared before the Processor so it is destroyed after it.
159 // `Processor::state()` dereferences a pointer to this store, and a Processor
160 // may read it from its destructor or from a worker thread that destructor is
161 // about to join. Reversing these two lines hands that thread a freed store.
162 state::StateStore store_;
163 std::unique_ptr<Processor> processor_;
164 // Panic requests (Processor::request_panic), serviced before each block.
165 std::shared_ptr<PanicState> panic_state_;
166 // Factory presets, bound to store_ in the constructor. Declared after
167 // store_ so reverse member destruction retires the PresetManager while the
168 // store it holds a reference to is still alive.
169 FactoryPresetTable factory_presets_;
170 // kAudioUnitProperty_RenderContextObserver; bound once the processor
171 // exists, advertised only for an AudioWorkgroupClient processor.
172 RenderContextObserverProperty render_context_;
173 // Keeps a host class-info restore off the processor while Render() is
174 // inside it. Declared after processor_ so it outlives every render that
175 // consults it.
176 StateRestoreGate state_restore_gate_;
177 // Declared after processor_ so reverse member destruction retires retained
178 // editor handles before either referenced object is released.
179 runtime::AliveToken owner_alive_;
180 // Contract editors the host still holds. Declared after processor_ so its
181 // destruction invalidates them while the processor and store_ exist.
182 ContractSessionRegistry contract_sessions_;
183
184 // Immutable plugin metadata, cached once in the constructor so the render
185 // path never copies the descriptor (which allocates std::string members) on
186 // the audio thread.
187 PluginDescriptor descriptor_{};
188
189 // Host accommodations, resolved once in the constructor via the runtime
190 // policy. Gates the non-negative latency clamp.
191 HostQuirks host_quirks_{};
192
193 // Cached ParamID of a plugin-declared Bypass parameter, 0 when there is
194 // none. This adapter does NOT synthesize one: an `aumi` has no
195 // kAudioUnitProperty_BypassEffect (that lives on AUEffectBase), so a
196 // synthesized control would appear as an ordinary parameter with no host
197 // bypass semantics behind it.
198 state::ParamID bypass_param_id_ = 0;
199
200 // Main-thread listener that pushes editor parameter edits to the host, kept
201 // alive for the adapter's lifetime so host param notifications never run on
202 // the render thread.
203 state::ListenerToken ui_push_listener_;
204 ParameterDisplayNamePublisher parameter_display_names_;
205
206 // Parameter-event sidecar, set on the Processor each block so the
207 // param-events contract is uniform across formats. AU v2 has no scheduled /
208 // ramped parameter event source, so this queue stays empty and host
209 // parameter changes reach the Processor through `store_`.
210 state::ParameterEventQueue param_events_;
211
212 // Per-block MIDI I/O. Members (not per-render locals) with reserved,
213 // realtime-capacity-limited storage so the drain and the Processor's
214 // appends never grow a vector on the audio thread. Capacities match the
215 // effect adapter so behavior is uniform across formats.
216 static constexpr std::size_t kMaxEventsPerBlock = kMaxMidiEventsPerBlock;
217 static constexpr std::size_t kMaxSysexPerBlock = 64;
218 static constexpr std::size_t kMaxSysexPayloadBytes = 512;
219 midi::MidiBuffer midi_in_;
220 midi::MidiBuffer midi_out_;
221
222 // Lock-free MIDI input. Single producer (the host's MIDI delivery thread,
223 // via HandleMIDIEvent / HandleSysEx), single consumer (Render). No mutex on
224 // the audio thread; under flood, excess events are dropped rather than
225 // blocking the render.
227 struct SysexChunk {
228 std::array<uint8_t, kMaxSysexPayloadBytes> bytes{};
229 uint16_t length = 0;
230 };
231 runtime::SpscQueue<SysexChunk, 32> sysex_in_queue_;
232
233 // MIDI output: the host installs a callback via
234 // kAudioUnitProperty_MIDIOutputCallback (main thread) that the render path
235 // reads every block. The publisher owns that cross-thread handoff.
236 MidiOutputCallbackPublisher midi_output_callback_;
237 // Pre-reserved packet-list storage so the render-path build never allocates.
238 MidiOutputPacketBuilder midi_out_packet_builder_;
239
240 // Previous-block transport snapshot used to derive playhead change flags.
241 // Default-constructed so the first Render() after Initialize() reports no
242 // changes.
243 detail::PlayheadSnapshot playhead_prev_{};
244
245 // Host offline-render intent (kAudioUnitProperty_OfflineRender), written by
246 // the host before a faster-than-realtime bounce and read each render block.
247 OfflineRenderProperty offline_render_;
248};
249
250} // namespace pulp::format::au
251
252#endif // defined(__APPLE__)
The contract sessions one adapter instance opened that a host still holds.
Definition contract_editor_session.hpp:73
Lock-free panic request and output-note state for one Processor.
Definition panic.hpp:73
Definition processor.hpp:174
Definition state_restore_gate.hpp:87
The host-facing factory-preset table for one plug-in instance.
Definition au_factory_presets.hpp:56
bool StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element) override
OSStatus SaveState(CFPropertyListRef *outData) override
OSStatus NewFactoryPresetSet(const AUPreset &inNewFactoryPreset) override
kAudioUnitProperty_PresentPreset carrying a non-negative preset number.
FactoryPresetTable & factory_preset_table() noexcept
The factory-preset table this instance serves.
Definition au_v2_midi_processor.hpp:123
OSStatus GetParameterList(AudioUnitScope inScope, AudioUnitParameterID *outParameterList, UInt32 &outNumParameters) override
PulpAUMidiProcessor(AudioComponentInstance ci)
OSStatus HandleMIDIEvent(UInt8 inStatus, UInt8 inChannel, UInt8 inData1, UInt8 inData2, UInt32 inStartFrame) override
Every short MIDI message the host delivers (status already split into inStatus / inChannel by AUMIDIB...
OSStatus SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void *inData, UInt32 inDataSize) override
OSStatus GetParameterValueStrings(AudioUnitScope inScope, AudioUnitParameterID inParameterID, CFArrayRef *outStrings) override
OSStatus GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32 &outDataSize, bool &outWritable) override
bool CanScheduleParameters() const noexcept override
OSStatus RestoreState(CFPropertyListRef plist) override
OSStatus GetPresets(CFArrayRef *outData) const override
kAudioUnitProperty_FactoryPresets — the presets bundled with the plug-in, discovered from its Resourc...
OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void *outData) override
OSStatus GetParameterInfo(AudioUnitScope inScope, AudioUnitParameterID inParameterID, AudioUnitParameterInfo &outParameterInfo) override
OSStatus SetParameter(AudioUnitParameterID inID, AudioUnitScope inScope, AudioUnitElement inElement, Float32 inValue, UInt32 inBufferOffsetInFrames) override
OSStatus HandleSysEx(const UInt8 *inData, UInt32 inLength) override
System-exclusive payload (F0 … F7).
PulpAUMidiProcessor(AudioComponentInstance ci, ProcessorFactory factory)
OSStatus GetParameter(AudioUnitParameterID inID, AudioUnitScope inScope, AudioUnitElement inElement, Float32 &outValue) override
OSStatus Render(AudioUnitRenderActionFlags &ioActionFlags, const AudioTimeStamp &inTimeStamp, UInt32 inNumberFrames) override
void publish_parameter_display_changes()
Drain host-facing name changes on the caller's control/main thread.
kAudioUnitProperty_RenderContextObserver, the v2 form of AUv3's renderContextObserver.
Definition au_v2_common.hpp:492
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
Definition spsc_queue.hpp:23
RAII handle for a registered parameter-change listener.
Definition listener_token.hpp:47
Definition parameter_event_queue.hpp:82
Per-host accommodation flags consumed by format adapters.
Apple-only: the whole header is gated on APPLE so it stays an empty no-op on the Linux header-hygiene...
Definition au_factory_presets.hpp:33
constexpr std::size_t kMaxMidiEventsPerBlock
RT-safe builder for the MIDIPacketList an AU v2 plugin hands the host via kAudioUnitProperty_MIDIOutp...
Definition au_v2_common.hpp:310
std::unique_ptr< Processor >(*)() ProcessorFactory
Factory function type — plugins provide this to create processor instances.
Definition processor.hpp:1416
Definition generated_event_source.hpp:33
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
uint32_t ParamID
Unique parameter identifier, stable across plugin versions.
Definition parameter.hpp:55
STL namespace.
Always-on / host-gated defensive behaviors that the adapters consult.
Definition host_quirks.hpp:66
Plugin metadata — declared once, immutable.
Definition plugin_descriptor.hpp:245