Skip to content

Commit ead71f8

Browse files
committed
AudioUnit implementation of AUMessageChannel moved from example code to IPC library as dynamic ObjC class
1 parent 0746d17 commit ead71f8

File tree

4 files changed

+230
-39
lines changed

4 files changed

+230
-39
lines changed

IPC/ARAIPC.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,6 @@ typedef ARA_IPC_REF(ARAIPCProxyHostRef);
7272
//! C-compatible wrapper of ARA IPC ProxyPlugIn
7373
typedef ARA_IPC_REF(ARAIPCProxyPlugInRef);
7474

75-
//! C-compatible wrapper of ARA IPC MessageChannel
76-
typedef ARA_IPC_REF(ARAIPCMessageChannelRef);
77-
7875
//! to keep the IPC decoupled from the companion API in use, the IPC code uses
7976
//! an opaque encapsulation to represent a companion API plug-in instance
8077
typedef ARA_IPC_REF(ARAIPCPlugInInstanceRef);

IPC/ARAIPCAudioUnit_v3.h

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,8 @@ void ARA_CALL ARAIPCAUProxyPlugInUninitialize(ARAIPCProxyPlugInRef _Nonnull prox
7676

7777

7878

79-
//! plug-in side: implementation for AUMessageChannel<NSObject> -init...
80-
//! will initialize the proxy on demand - make sure to add all factories via ARAIPCProxyHostAddFactory()
81-
//! before the first call to this function and to call ARAIPCAUProxyHostUninitialize() if the call was made
82-
ARAIPCMessageChannelRef _Nullable ARA_CALL ARAIPCAUProxyHostInitializeMessageChannel(NSObject<AUMessageChannel> * _Nonnull audioUnitChannel,
83-
bool isMainThreadChannel);
84-
85-
//! plug-in side: implementation for AUMessageChannel<NSObject> -callAudioUnit:
86-
NSDictionary * _Nonnull ARA_CALL ARAIPCAUProxyHostCommandHandler(ARAIPCMessageChannelRef _Nonnull messageChannelRef, NSDictionary * _Nonnull message);
87-
88-
//! plug-in side: static cleanup upon shutdown
89-
void ARA_CALL ARAIPCAUProxyHostUninitialize(void);
79+
//! plug-in side: implementation for AUAudioUnit messageChannelFor:
80+
id<AUMessageChannel> _Nullable ARA_CALL ARAIPCAUProxyHostMessageChannelFor(NSString * _Nonnull channelName);
9081

9182

9283
API_AVAILABLE_END

IPC/ARAIPCAudioUnit_v3.mm

Lines changed: 228 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
#include <atomic>
3636

37+
#import <objc/runtime.h>
38+
3739

3840
// JUCE hotfix: because JUCE directly includes the .cpp/.mm files from this SDK instead of properly
3941
// compiling the ARA_IPC_Library, these switches allow for skipping the host- or the
@@ -46,6 +48,18 @@
4648
#endif
4749

4850

51+
// crude hack to use std::bit_width in C++17
52+
#if __cplusplus < 202002L
53+
namespace std
54+
{
55+
constexpr size_t bit_width (size_t n)
56+
{
57+
return (n <= 1) ? n : 1 + bit_width (n / 2);
58+
}
59+
}
60+
#endif
61+
62+
4963
namespace ARA {
5064
namespace IPC {
5165

@@ -158,7 +172,7 @@ void sendMessage (MessageID messageID, std::unique_ptr<MessageEncoder> && encode
158172
// remote side until the first message is sent. However, if another channel is assigned
159173
// its (same) callHostBlock, then the pending callHostBlock for the first channel is
160174
// lost for some reason. We need to send an empty dummy message to work around this bug,
161-
// which them must be filtered in ARAIPCAUProxyHostCommandHandler().
175+
// which them must be filtered before calling routeReceivedDictionary().
162176
[audioUnitChannel callAudioUnit:[NSDictionary dictionary]];
163177
}
164178

@@ -237,25 +251,13 @@ void sendMessage (MessageID messageID, std::unique_ptr<MessageEncoder> && encode
237251
#endif // !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY
238252

239253

240-
#if defined (__GNUC__)
241-
_Pragma ("GCC diagnostic push")
242-
_Pragma ("GCC diagnostic ignored \"-Wunguarded-availability\"")
243-
#endif
254+
// host side: proxy plug-in C adapter
255+
#if !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY
244256

245-
ARA_MAP_IPC_REF (AudioUnitMessageChannel, ARAIPCMessageChannelRef)
246257
ARA_MAP_IPC_REF (AUProxyPlugIn, ARAIPCProxyPlugInRef)
247258

248-
#if defined (__GNUC__)
249-
_Pragma ("GCC diagnostic pop")
250-
#endif
251-
252-
253259
extern "C" {
254260

255-
256-
// host side: proxy plug-in C adapter
257-
#if !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY
258-
259261
ARAIPCProxyPlugInRef ARA_CALL ARAIPCAUProxyPlugInInitialize (AUAudioUnit * _Nonnull audioUnit,
260262
ARAMainThreadWaitForMessageDelegate _Nullable waitForMessageDelegate,
261263
void * _Nullable delegateUserData)
@@ -285,6 +287,8 @@ void ARA_CALL ARAIPCAUProxyPlugInUninitialize (ARAIPCProxyPlugInRef _Nonnull pro
285287
delete fromIPCRef (proxyPlugInRef);
286288
}
287289

290+
} // extern "C"
291+
288292
#endif // !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY
289293

290294

@@ -314,9 +318,11 @@ void ARA_CALL ARAIPCAUProxyPlugInUninitialize (ARAIPCProxyPlugInRef _Nonnull pro
314318

315319
AUProxyHost* _proxyHost;
316320

321+
typedef ARA_IPC_REF(ARAIPCMessageChannelRef);
322+
ARA_MAP_IPC_REF (AudioUnitMessageChannel, ARAIPCMessageChannelRef)
317323

318-
ARAIPCMessageChannelRef _Nullable ARA_CALL ARAIPCAUProxyHostInitializeMessageChannel (NSObject<AUMessageChannel> * _Nonnull audioUnitChannel,
319-
bool isMainThreadChannel)
324+
static ARAIPCMessageChannelRef _Nullable initializeMessageChannel (NSObject<AUMessageChannel> * _Nonnull audioUnitChannel,
325+
bool isMainThreadChannel)
320326
{
321327
// \todo the connection currently stores the creation thread as main thread for Windows compatibility,
322328
// so we need to make sure the proxy is created on the main thread
@@ -343,28 +349,226 @@ ARAIPCMessageChannelRef _Nullable ARA_CALL ARAIPCAUProxyHostInitializeMessageCha
343349
return result;
344350
}
345351

346-
NSDictionary * _Nonnull ARA_CALL ARAIPCAUProxyHostCommandHandler (ARAIPCMessageChannelRef _Nonnull messageChannelRef, NSDictionary * _Nonnull message)
352+
#endif // !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY
353+
354+
355+
// plug-in side: NSObject<AUMessageChannel> implementation
356+
#if !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY
357+
358+
359+
/*
360+
The following code implements the below class at runtime to work around potential ObjC class name conflicts
361+
362+
@interface ARAIPCAUMessageChannelImpl : NSObject<AUMessageChannel>
363+
@end
364+
365+
@implementation ARAIPCAUMessageChannel {
366+
ARA::IPC::ARAIPCMessageChannelRef _messageChannelRef;
367+
}
368+
369+
@synthesize callHostBlock = _callHostBlock;
370+
371+
- (instancetype)initAsMainThreadChannel:(BOOL) isMainThreadChannel {
372+
self = [super init];
373+
374+
if (self == nil) { return nil; }
375+
376+
_callHostBlock = nil;
377+
_messageChannelRef = initializeMessageChannel (self, isMainThreadChannel);
378+
379+
return self;
380+
}
381+
382+
- (NSDictionary * _Nonnull)callAudioUnit:(NSDictionary *)message {
383+
if ([message count])
384+
static_cast<ProxyHostMessageChannel *> (fromIPCRef (_messageChannelRef))->routeReceivedDictionary (message);
385+
return [NSDictionary dictionary];
386+
}
387+
388+
@end
389+
*/
390+
391+
// helper to add ivars to an ObjC class
392+
template <typename T>
393+
void addObjCIVar (Class cls, const char * name)
394+
{
395+
constexpr auto size { sizeof (T) };
396+
constexpr auto alignment { static_cast<uint8_t> (std::bit_width (size)) };
397+
[[maybe_unused]] const auto success = class_addIvar (cls, name, size, alignment, @encode (T));
398+
ARA_INTERNAL_ASSERT (success);
399+
}
400+
401+
// helpers to get/set a scalar ivar of an ObjC object
402+
template <typename T>
403+
inline T getScalarIVar (id self, Ivar ivar)
404+
{
405+
const auto offset { ivar_getOffset (ivar) };
406+
const auto bytes = static_cast<const unsigned char *> ((__bridge void *) self);
407+
return *reinterpret_cast<const T *> (bytes + offset);
408+
}
409+
410+
template <typename T>
411+
inline void setScalarIVar (id self, Ivar ivar, T value)
347412
{
348-
if ([message count]) // \todo filter dummy message sent in ProxyPlugInMessageChannel(), see there
413+
const auto offset { ivar_getOffset (ivar) };
414+
auto bytes = static_cast<unsigned char *> ((__bridge void *) self);
415+
*reinterpret_cast<T *> (bytes + offset) = value;
416+
}
417+
418+
419+
// optimization of object_getInstanceVariable: cache the ivars of our class
420+
Ivar messageChannelRefIvar { nullptr };
421+
Ivar callHostBlockIvar { nullptr };
422+
423+
424+
// methods of our class
425+
extern "C" {
426+
427+
static CallHostBlock callHostBlock_getter (id self, SEL /*_cmd*/)
428+
{
429+
return object_getIvar (self, callHostBlockIvar);
430+
}
431+
432+
static void callHostBlock_setter (id self, SEL /*_cmd*/, CallHostBlock newBlock)
433+
{
434+
id oldBlock { object_getIvar (self, callHostBlockIvar) };
435+
if (oldBlock != newBlock)
436+
{
437+
if (oldBlock)
438+
Block_release ((__bridge void *)oldBlock);
439+
if (newBlock)
440+
newBlock = (__bridge CallHostBlock) Block_copy ((__bridge void *)newBlock);
441+
object_setIvar (self, callHostBlockIvar, newBlock);
442+
}
443+
}
444+
445+
static NSDictionary * _Nonnull callAudioUnit_impl (id self, SEL /*_cmd*/, NSDictionary * message)
446+
{
447+
const auto messageChannelRef { getScalarIVar<ARA::IPC::ARAIPCMessageChannelRef> (self, messageChannelRefIvar) };
448+
if ([message count]) // \todo filter dummy message sent in ProxyPlugInMessageChannel, see there
349449
static_cast<ProxyHostMessageChannel *> (fromIPCRef (messageChannelRef))->routeReceivedDictionary (message);
350450
return [NSDictionary dictionary]; // \todo it would yield better performance if -callAudioUnit: would allow nil as return value
351451
}
352452

353-
void ARA_CALL ARAIPCAUProxyHostUninitialize (void)
453+
} // extern "C"
454+
455+
456+
// creation of the ARAIPCAUMessageChannelImpl Class
457+
static Class createMessageChannelObjCClass ()
354458
{
355-
if (_proxyHost)
356-
delete _proxyHost;
459+
// create a unique class name by appending a unique address to the base name
460+
std::string className { "ARAIPCAUMessageChannelImpl" + std::to_string (reinterpret_cast<intptr_t> (&messageChannelRefIvar)) };
461+
ARA_INTERNAL_ASSERT (!objc_getClass (className.c_str ()));
462+
463+
// allocate class
464+
Class cls { objc_allocateClassPair ([NSObject class], className.c_str (), 0) };
465+
ARA_INTERNAL_ASSERT (cls);
466+
467+
// add iVars
468+
addObjCIVar<intptr_t> (cls, "_messageChannelRef");
469+
addObjCIVar<CallHostBlock> (cls, "_callHostBlock");
470+
471+
// add properties
472+
objc_property_attribute_t attr[] { { "T", "@?" }, // type block
473+
{ "C", "" }, // C = copy
474+
{ "V", "_callHostBlock" } }; // backing i-var
475+
[[maybe_unused]] auto success { class_addProperty (cls, "callHostBlock", attr, sizeof (attr) / sizeof (attr[0])) };
476+
477+
// add getters/setters
478+
success = class_addMethod (cls, @selector(callHostBlock), (IMP)callHostBlock_getter, "@@:");
479+
ARA_INTERNAL_ASSERT (success);
480+
class_addMethod (cls, @selector(setCallHostBlock:), (IMP)callHostBlock_setter, "v@:@");
481+
ARA_INTERNAL_ASSERT (success);
482+
483+
// add methods
484+
class_addMethod (cls, @selector(callAudioUnit:), (IMP)callAudioUnit_impl, "@@:@");
485+
ARA_INTERNAL_ASSERT (success);
486+
487+
// declare conformance to <AUMessageChannel>
488+
const auto messageChannelProtocol { objc_getProtocol ("AUMessageChannel") };
489+
ARA_INTERNAL_ASSERT (messageChannelProtocol);
490+
success = class_addProtocol (cls, (Protocol * _Nonnull)messageChannelProtocol);
491+
ARA_INTERNAL_ASSERT (success);
492+
493+
// activate class
494+
objc_registerClassPair (cls);
495+
496+
// update cached ivar ptrs after activation
497+
messageChannelRefIvar = class_getInstanceVariable (cls, "_messageChannelRef");
498+
ARA_INTERNAL_ASSERT (messageChannelRefIvar);
499+
callHostBlockIvar = class_getInstanceVariable (cls, "_callHostBlock");
500+
ARA_INTERNAL_ASSERT (callHostBlockIvar);
501+
502+
return cls;
357503
}
358504

359-
#endif // !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY
505+
// creation and designated initialisation of instances of ARAIPCAUMessageChannelImpl Class
506+
static NSObject<AUMessageChannel> * createAndInitMessageChannel (Class cls, bool isMainThreadChannel)
507+
{
508+
NSObject<AUMessageChannel> * obj { class_createInstance (cls, 0) };
509+
ARA_INTERNAL_ASSERT (obj);
510+
ARA_INTERNAL_ASSERT ([obj conformsToProtocol:@protocol(AUMessageChannel)]);
511+
obj = [obj init];
512+
ARA_INTERNAL_ASSERT (obj);
513+
514+
object_setIvar (obj, callHostBlockIvar, nil);
515+
516+
const auto messageChannelRef { initializeMessageChannel (obj, isMainThreadChannel) };
517+
setScalarIVar<ARA::IPC::ARAIPCMessageChannelRef> (obj, messageChannelRefIvar, messageChannelRef);
518+
519+
return obj;
520+
}
521+
522+
523+
NSObject<AUMessageChannel> * __strong _mainMessageChannel { nil };
524+
NSObject<AUMessageChannel> * __strong _otherMessageChannel { nil };
525+
526+
static void createSharedMessageChannels ()
527+
{
528+
ARA_INTERNAL_ASSERT(!_mainMessageChannel && !_otherMessageChannel);
529+
530+
const auto cls { createMessageChannelObjCClass () };
531+
_mainMessageChannel = createAndInitMessageChannel (cls, true);
532+
_otherMessageChannel = createAndInitMessageChannel (cls, false);
533+
}
534+
535+
extern "C" id<AUMessageChannel> _Nullable ARA_CALL ARAIPCAUProxyHostMessageChannelFor (NSString * _Nonnull channelName)
536+
{
537+
if ([channelName isEqualToString:ARA_AUDIOUNIT_MAIN_THREAD_MESSAGES_UTI])
538+
{
539+
if (!_mainMessageChannel)
540+
createSharedMessageChannels ();
541+
return _mainMessageChannel;
542+
}
543+
else if ([channelName isEqualToString:ARA_AUDIOUNIT_OTHER_THREADS_MESSAGES_UTI])
544+
{
545+
if (!_otherMessageChannel)
546+
createSharedMessageChannels ();
547+
return _otherMessageChannel;
548+
}
549+
return nil;
550+
}
360551

361552
API_AVAILABLE_END
362553

554+
__attribute__((destructor))
555+
static void destroySharedMessageChannelsIfNeeded ()
556+
{
557+
if (@available(macOS 13.0, iOS 16.0, *))
558+
{
559+
if (_proxyHost)
560+
delete _proxyHost;
561+
562+
_mainMessageChannel = nil;
563+
_otherMessageChannel = nil;
564+
}
565+
}
566+
567+
#endif // !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY
363568

364569
_Pragma ("GCC diagnostic pop")
365570

366571

367-
} // extern "C"
368572
} // namespace IPC
369573
} // namespace ARA
370574

IPC/ARAIPCEncoding.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAModelUpdateControllerHostRef)
150150
ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAPlaybackControllerHostRef)
151151
ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAIPCProxyHostRef)
152152
ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAIPCProxyPlugInRef)
153-
ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAIPCMessageChannelRef)
154153
ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAIPCPlugInInstanceRef)
155154
#undef ARA_IPC_SPECIALIZE_FOR_REF_TYPE
156155

0 commit comments

Comments
 (0)