Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
au_v2_entry.hpp
Go to the documentation of this file.
1#pragma once
2
3// Generic AU v2 entry point generator
4// Plugin developers include this and call PULP_AU_PLUGIN() with their factory
5// function and a class name. All AudioUnitSDK boilerplate is generated.
6//
7// Usage (in one .cpp file per AU plugin):
8// #include "my_processor.hpp"
9// #include <pulp/format/au_v2_entry.hpp>
10//
11// // The class name determines the factory function: MyPluginAUFactory
12// // This must match the factoryFunction in Info.plist.au
13// PULP_AU_PLUGIN(MyPluginAU, my_namespace::create_my_processor)
14//
15// This generates:
16// - A class MyPluginAU : PulpAUEffect (the AudioUnit implementation)
17// - A factory function MyPluginAUFactory (for the Info.plist factoryFunction)
18// - Plugin registration via PULP_REGISTER_PLUGIN
19
20// Apple-only: pulls in AudioUnitSDK. No-op on non-Apple so the header stays
21// self-contained on the Linux header-hygiene check.
22#if defined(__APPLE__)
23
26#include <AudioUnitSDK/AUPlugInDispatch.h>
27#include <AudioUnitSDK/ComponentBase.h>
28
29// Generate an AU v2 entry point for an audio-only effect (`aufx`).
30// ClassName determines the factory function name: ClassNameFactory
31// factory_fn is the ProcessorFactory function that creates the Processor
32//
33// Uses AUBaseFactory: the component exposes the base AU selectors only. An
34// `aufx` host never delivers MIDI, so no MusicDevice selectors are needed.
35// If the plugin's descriptor sets `accepts_midi = true` (packaged `aumf`),
36// use PULP_AU_MIDI_PLUGIN instead — see below.
37#define PULP_AU_PLUGIN(ClassName, factory_fn) \
38 PULP_REGISTER_PLUGIN(factory_fn) \
39 class ClassName : public pulp::format::au::PulpAUEffect { \
40 public: \
41 explicit ClassName(AudioComponentInstance ci) : PulpAUEffect(ci) {} \
42 }; \
43 AUSDK_COMPONENT_ENTRY(ausdk::AUBaseFactory, ClassName)
44
45// Generate an AU v2 entry point for a MIDI-receiving effect (`aumf`,
46// kAudioUnitType_MusicEffect). Identical to PULP_AU_PLUGIN except the
47// component is registered through AUMIDIEffectFactory, whose AUMIDILookup
48// dispatches the MusicDevice MIDIEvent + SysEx selectors to the base class.
49//
50// PulpAUEffect already derives ausdk::AUMIDIEffectBase and implements
51// HandleMIDIEvent / HandleSysEx, but with the plain AUBaseFactory those
52// selectors are never wired, so the host's MusicDeviceMIDIEvent call returns
53// -4 (unimpErr) and auval fails ("-4 IN CALL MusicDeviceMIDIEvent"). The
54// factory is the ONLY difference — the lookup table, not the C++ class, is
55// what carries the MIDI dispatch.
56//
57// Pairing rule (must match or the component is invalid): use this macro
58// exactly when the bundle is typed `aumf` and the descriptor sets
59// `accepts_midi = true`. Instruments (`aumu`) that also implement
60// StartNote/StopNote use the instrument entry (AUMusicDeviceFactory) instead.
61#define PULP_AU_MIDI_PLUGIN(ClassName, factory_fn) \
62 PULP_REGISTER_PLUGIN(factory_fn) \
63 class ClassName : public pulp::format::au::PulpAUEffect { \
64 public: \
65 explicit ClassName(AudioComponentInstance ci) : PulpAUEffect(ci) {} \
66 }; \
67 AUSDK_COMPONENT_ENTRY(ausdk::AUMIDIEffectFactory, ClassName)
68
69// ── Multi-plugin bundle variants ─────────────────────────────────────────
70// One binary hosts many plugins. Unlike the single-plugin macros above, these
71// bind each component to its OWN factory lexically (via the factory-taking
72// PulpAUEffect ctor), so instantiation never consults the legacy global slot,
73// and they register a KEYED entry (which never touches that global). Expand
74// once per component; N per binary is legal (AUSDK_COMPONENT_ENTRY generates a
75// distinct ClassNameFactory each time). Pass PluginRegistration metadata
76// (id + AU codes) so tools/enumeration and per-plugin editor assets resolve.
77// The registration braced-init is passed as trailing __VA_ARGS__ so its
78// internal commas (`{.id=…, .au_subtype=…}`) survive the preprocessor — parens
79// don't protect brace commas, and an extra paren pair would form a GNU
80// statement-expression. au_factory therefore comes BEFORE the init.
81//
82// `factory_fn` is the SINGLE source of truth: it binds the AU class ctor AND is
83// force-assigned onto the registration's `.factory`, overwriting whatever the
84// braced-init said. So the factory the host constructs (lexical ctor arg) and
85// the factory `find_plugin`/`registered_plugins` report are provably the same
86// function — a caller cannot desync them by passing a stray `.factory`. Callers
87// therefore MUST NOT set `.factory` in the braced-init (it is ignored).
88#define PULP_AU_BUNDLE_ENTRY_(ClassName, factory_fn, au_factory, ...) \
89 namespace { \
90 struct ClassName##_Registrar { \
91 ClassName##_Registrar() { \
92 ::pulp::format::PluginRegistration reg __VA_ARGS__; \
93 reg.factory = factory_fn; \
94 ::pulp::format::register_plugin(reg); \
95 } \
96 } ClassName##_registrar_; \
97 } \
98 class ClassName : public pulp::format::au::PulpAUEffect { \
99 public: \
100 explicit ClassName(AudioComponentInstance ci) \
101 : PulpAUEffect(ci, factory_fn) {} \
102 }; \
103 AUSDK_COMPONENT_ENTRY(au_factory, ClassName)
104
105// `aufx` component in a bundle. The trailing arg is a PluginRegistration
106// braced-init WITHOUT `.factory` (bound from factory_fn), e.g.
107// PULP_AU_BUNDLE_PLUGIN(FooAU, create_foo,
108// {.id="com.x.foo", .au_type='aufx',
109// .au_subtype='Foo1', .au_manufacturer='Mfr '})
110#define PULP_AU_BUNDLE_PLUGIN(ClassName, factory_fn, ...) \
111 PULP_AU_BUNDLE_ENTRY_(ClassName, factory_fn, ausdk::AUBaseFactory, __VA_ARGS__)
112
113// `aumf` (MusicEffect) component in a bundle.
114#define PULP_AU_BUNDLE_MIDI_PLUGIN(ClassName, factory_fn, ...) \
115 PULP_AU_BUNDLE_ENTRY_(ClassName, factory_fn, ausdk::AUMIDIEffectFactory, __VA_ARGS__)
116
117#endif // defined(__APPLE__)