59#define DSPARK_PLUGIN_AU_INCLUDED 1
63#include "../DSParkPlugin.h"
65#include <AudioToolbox/AudioToolbox.h>
66#include <CoreFoundation/CoreFoundation.h>
68#if defined(DSPARK_PLUGIN_WEBVIEW)
69#include <AudioToolbox/AudioUnitUtilities.h>
71#include <objc/message.h>
72#include <objc/runtime.h>
75#include <dispatch/dispatch.h>
83namespace dspark::plugin::au {
88inline OSType fourCC(
const char* s)
noexcept
90 return (
static_cast<OSType
>(
static_cast<unsigned char>(s[0])) << 24)
91 | (
static_cast<OSType
>(
static_cast<unsigned char>(s[1])) << 16)
92 | (
static_cast<OSType
>(
static_cast<unsigned char>(s[2])) << 8)
93 |
static_cast<OSType
>(
static_cast<unsigned char>(s[3]));
96#if defined(DSPARK_PLUGIN_WEBVIEW)
111inline constexpr AudioUnitPropertyID kDsparkEditorHookProperty = 64537;
118 void* context =
nullptr;
119 void* (*createView)(
void* context,
double width,
double height) =
nullptr;
125 double width = 0, height = 0;
132 void (*onDealloc)(
void* context) =
nullptr;
133 void* context =
nullptr;
136inline void editorContainerDealloc(
void* self,
SEL)
noexcept
141 Class cls = objc_getClass(
"DSParkAUEditorContainer");
142 if (cls ==
nullptr)
return;
143 if (Ivar ivar = class_getInstanceVariable(cls,
"dsparkSink"))
145 auto* sink =
reinterpret_cast<EditorViewSink*
>(
146 object_getIvar(
static_cast<id>(self), ivar));
147 if (sink !=
nullptr && sink->onDealloc !=
nullptr)
148 sink->onDealloc(sink->context);
150 objc_super super {
static_cast<id>(self), class_getSuperclass(cls) };
151 reinterpret_cast<void (*)(objc_super*,
SEL)
>(&objc_msgSendSuper)(
152 &super, sel_registerName(
"dealloc"));
157inline Class editorContainerClass() noexcept
159 static Class registered = []() -> Class {
160 Class viewCls = objc_getClass(
"NSView");
161 if (viewCls ==
nullptr)
return nullptr;
162 Class c = objc_allocateClassPair(viewCls,
"DSParkAUEditorContainer", 0);
164 return objc_getClass(
"DSParkAUEditorContainer");
165 class_addIvar(c,
"dsparkSink",
sizeof(
void*),
alignof(
void*) == 8 ? 3 : 2,
"^v");
166 class_addMethod(c, sel_registerName(
"dealloc"),
167 reinterpret_cast<IMP
>(&editorContainerDealloc),
"v@:");
168 objc_registerClassPair(c);
174inline unsigned int editorFactoryInterfaceVersion(
void*,
SEL)
noexcept
179inline void* editorFactoryUiView(
void*,
SEL,
void* audioUnit, ViewSize size)
noexcept
182 UInt32 ioSize =
sizeof(hook);
183 if (audioUnit ==
nullptr
184 || AudioUnitGetProperty(
static_cast<AudioUnit
>(audioUnit),
185 kDsparkEditorHookProperty, kAudioUnitScope_Global,
186 0, &hook, &ioSize) != noErr
187 || hook.createView ==
nullptr)
189 return hook.createView(hook.context, size.width, size.height);
193inline Class editorFactoryClass() noexcept
195 static Class registered = []() -> Class {
196 Class c = objc_allocateClassPair(objc_getClass(
"NSObject"),
197 "DSParkAUCocoaViewFactory", 0);
199 return objc_getClass(
"DSParkAUCocoaViewFactory");
200 class_addMethod(c, sel_registerName(
"interfaceVersion"),
201 reinterpret_cast<IMP
>(&editorFactoryInterfaceVersion),
"I@:");
202 class_addMethod(c, sel_registerName(
"uiViewForAudioUnit:withSize:"),
203 reinterpret_cast<IMP
>(&editorFactoryUiView),
204 "@@:^{ComponentInstanceRecord=}{CGSize=dd}");
207 if (Protocol* proto = objc_getProtocol(
"AUCocoaUIBase"))
208 class_addProtocol(c, proto);
209 objc_registerClassPair(c);
218inline CFURLRef copyOwningBundleUrl() noexcept
220 static const int anchor = 0;
222 if (dladdr(&anchor, &info) == 0 || info.dli_fname ==
nullptr)
return nullptr;
223 const char* path = info.dli_fname;
224 const char* marker =
nullptr;
225 for (
const char* p = std::strstr(path,
"/Contents/MacOS/"); p !=
nullptr;
226 p = std::strstr(p + 1,
"/Contents/MacOS/"))
228 size_t length = marker !=
nullptr ?
static_cast<size_t>(marker - path) : 0;
230 if (
const char* slash = std::strrchr(path,
'/'))
231 length =
static_cast<size_t>(slash - path);
232 if (length == 0)
return nullptr;
233 return CFURLCreateFromFileSystemRepresentation(
234 kCFAllocatorDefault,
reinterpret_cast<const UInt8*
>(path),
235 static_cast<CFIndex
>(length),
true);
243 static constexpr size_t kNumParams = P::parameters.size();
244 static constexpr bool kIsInstrument =
245 P::descriptor.category == Category::Instrument;
246 static constexpr int kNumPresets = factoryPresetCountOf<P>();
247 static_assert(!(kIsInstrument && HasSidechain<P>),
248 "an Instrument has no audio inputs; remove the sidechain "
249 "processBlock or use Category::Fx");
250 static_assert(!kIsInstrument || HasMidi<P>,
251 "an Instrument needs handleMidiEvent (see HasMidi): it has "
252 "no audio input to process");
253 static_assert(paramIdsUnique<P>(),
254 "two parameter ids share a hash32 (or collide with the "
255 "reserved PRGM/BYPS state ids): automation and state would "
256 "cross-wire. Rename one id.");
259 static OSType expectedType() noexcept
261 if (kIsInstrument)
return fourCC(
"aumu");
262 if (HasMidi<P>)
return fourCC(
"aumf");
263 return fourCC(
"aufx");
266 AudioComponentInstance instance =
nullptr;
267 OSType subtype = 0, manufacturer = 0;
269 double sampleRate = 44100.0;
270 UInt32 maxFrames = 1156;
271 bool initialized =
false;
272 int currentChannels = defaultChannelCount<P>();
273 UInt32 offlineRender = 0;
276 std::atomic<double> shadow[kNumParams == 0 ? 1 : kNumParams] {};
277 std::atomic<bool> bypass {
false };
278 float bypassMix = 0.0f;
285 std::atomic<int> cachedLatency { 0 };
288 static constexpr UInt32 kNumInputElements =
289 kIsInstrument ? 0 : (HasSidechain<P> ? 2 : 1);
290 AURenderCallbackStruct inputCallback[2] {};
291 AudioUnit inputConnection[2] = {
nullptr,
nullptr };
292 UInt32 inputConnectionBus[2] = { 0, 0 };
294 std::vector<float> pullL, pullR, dryL, dryR, scL, scR;
298 HostCallbackInfo hostCallbacks {};
302 static constexpr uint32_t kMidiRingSize = 256;
303 MidiEvent midiRing[HasMidi<P> ? kMidiRingSize : 1] {};
304 std::atomic<uint32_t> midiHead { 0 }, midiTail { 0 };
309 int scheduledCount = 0;
313 std::vector<AUPreset> presetStorage;
314 CFArrayRef presetArray =
nullptr;
315 SInt32 currentPresetNumber = -1;
321 AudioUnitPropertyID id;
322 AudioUnitPropertyListenerProc proc;
325 std::vector<Listener> listeners;
326 CFStringRef presetName =
nullptr;
328#if defined(DSPARK_PLUGIN_WEBVIEW)
329 webview_ui::Editor<P> editor;
330 void* editorContainer =
nullptr;
331 EditorViewSink editorSink {};
332 bool editActive[kNumParams == 0 ? 1 : kNumParams] {};
337 stopLatencyTick(
false);
338#if defined(DSPARK_PLUGIN_WEBVIEW)
341 if (presetName !=
nullptr) CFRelease(presetName);
342 for (
auto& preset : presetStorage)
343 if (
preset.presetName != nullptr) CFRelease(
preset.presetName);
344 if (presetArray !=
nullptr) CFRelease(presetArray);
348 void applyFactoryPresetIdx(
int idx)
noexcept
350 if constexpr (kNumPresets > 0)
352 if (idx < 0 || idx >= kNumPresets)
return;
353 for (
size_t i = 0; i < kNumParams; ++i)
354 applyNormalized(
static_cast<int>(i), presetNormalized<P>(idx, i));
355 currentPresetNumber = idx;
366 void refreshLatency() noexcept
368 if constexpr (HasLatency<P>)
370 const int now = user.getLatency();
371 if (initialized && now != cachedLatency.load(std::memory_order_relaxed))
373 cachedLatency.store(now, std::memory_order_relaxed);
374 if (pthread_main_np() != 0
375 || !latencyTickRunning.load(std::memory_order_acquire))
376 notifyProperty(kAudioUnitProperty_Latency,
377 kAudioUnitScope_Global, 0);
379 latencyNotifyPending.store(
true, std::memory_order_release);
386 std::atomic<bool> latencyNotifyPending {
false };
387 std::atomic<bool> latencyTickRunning {
false };
388 dispatch_source_t latencyTick =
nullptr;
390 static void onLatencyTick(
void* self)
392 auto* plugin =
static_cast<Plugin*
>(self);
393 if (plugin->latencyNotifyPending.exchange(
false, std::memory_order_acq_rel))
394 plugin->notifyProperty(kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0);
397 void startLatencyTick() noexcept
399 if constexpr (HasLatency<P>)
401 if (latencyTick !=
nullptr)
return;
402 latencyTick = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
403 dispatch_get_main_queue());
404 if (latencyTick ==
nullptr)
return;
405 dispatch_set_context(latencyTick,
this);
406 dispatch_source_set_event_handler_f(latencyTick, &onLatencyTick);
407 dispatch_source_set_timer(latencyTick,
408 dispatch_time(DISPATCH_TIME_NOW, 33 * 1000000),
409 33u * 1000000u, 5u * 1000000u);
410 dispatch_resume(latencyTick);
411 latencyTickRunning.store(
true, std::memory_order_release);
417 void stopLatencyTick(
bool flush =
true) noexcept
419 if (latencyTick ==
nullptr)
return;
420 latencyTickRunning.store(
false, std::memory_order_release);
421 dispatch_source_cancel(latencyTick);
422 dispatch_release(latencyTick);
423 latencyTick =
nullptr;
424 if (latencyNotifyPending.exchange(
false, std::memory_order_acq_rel) && flush)
425 notifyProperty(kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0);
428 void notifyProperty(AudioUnitPropertyID
id, AudioUnitScope scope,
429 AudioUnitElement element)
noexcept
431 for (
const auto& l : listeners)
432 if (l.id == id && l.proc != nullptr)
433 l.proc(l.user, instance, id, scope, element);
438 for (
size_t i = 0; i < kNumParams; ++i)
439 shadow[i].store(
toNormalized(P::parameters[i], P::parameters[i].defValue),
440 std::memory_order_relaxed);
443 static int indexOfParamId(AudioUnitParameterID
id)
noexcept
445 for (
size_t i = 0; i < kNumParams; ++i)
446 if (
hash32(P::parameters[i].
id) ==
id)
return static_cast<int>(i);
450 void applyNormalized(
int index,
double normalized)
noexcept
454 if (normalized != normalized)
return;
455 const auto& spec = P::parameters[
static_cast<size_t>(index)];
456 shadow[
static_cast<size_t>(index)].store(normalized, std::memory_order_relaxed);
457 user.setParameter(index,
static_cast<float>(
toPlain(spec, normalized)));
460 void applyAllShadows() noexcept
462 for (
size_t i = 0; i < kNumParams; ++i)
463 user.setParameter(
static_cast<int>(i),
464 static_cast<float>(
toPlain(P::parameters[i],
465 shadow[i].load(std::memory_order_relaxed))));
468#if defined(DSPARK_PLUGIN_WEBVIEW)
472 static constexpr bool kHasWebEditor =
473 HasEditor<P> && webview_ui::Editor<P>::kAvailable;
475 void sendGesture(
int index, AudioUnitEventType type)
noexcept
477 AudioUnitEvent
event {};
478 event.mEventType = type;
479 event.mArgument.mParameter = AudioUnitParameter {
480 instance,
hash32(P::parameters[
static_cast<size_t>(index)].
id),
481 kAudioUnitScope_Global, 0 };
482 AUEventListenerNotify(
nullptr,
nullptr, &event);
488 static void cbSetParam(
void* context,
int index,
double plain)
noexcept
490 auto* self =
static_cast<Plugin*
>(context);
491 const AudioUnitParameter parameter {
492 self->instance,
hash32(P::parameters[
static_cast<size_t>(index)].
id),
493 kAudioUnitScope_Global, 0 };
494 const bool inGesture = self->editActive[
static_cast<size_t>(index)];
496 self->sendGesture(index, kAudioUnitEvent_BeginParameterChangeGesture);
497 AUParameterSet(
nullptr,
nullptr, ¶meter,
498 static_cast<AudioUnitParameterValue
>(plain), 0);
500 self->sendGesture(index, kAudioUnitEvent_EndParameterChangeGesture);
503 static void cbBeginEdit(
void* context,
int index)
noexcept
505 auto* self =
static_cast<Plugin*
>(context);
506 self->editActive[
static_cast<size_t>(index)] =
true;
507 self->sendGesture(index, kAudioUnitEvent_BeginParameterChangeGesture);
510 static void cbEndEdit(
void* context,
int index)
noexcept
512 auto* self =
static_cast<Plugin*
>(context);
513 self->editActive[
static_cast<size_t>(index)] =
false;
514 self->sendGesture(index, kAudioUnitEvent_EndParameterChangeGesture);
517 static void sOnContainerDealloc(
void* context)
noexcept
519 auto* self =
static_cast<Plugin*
>(context);
520 self->editorContainer =
nullptr;
521 self->editor.destroy();
526 void teardownEditor() noexcept
528 if (editorContainer !=
nullptr)
530 Class cls = editorContainerClass();
531 if (Ivar ivar = cls !=
nullptr
532 ? class_getInstanceVariable(cls,
"dsparkSink") : nullptr)
533 object_setIvar(static_cast<id>(editorContainer), ivar, nullptr);
534 editorContainer =
nullptr;
539 static void* sCreateEditorView(
void* context,
double width,
double height)
noexcept
541 return static_cast<Plugin*
>(context)->createEditorView(width, height);
549 void* createEditorView(
double,
double)
noexcept
551 namespace og = webview_ui::objc_glue;
553 Class cls = editorContainerClass();
554 if (cls ==
nullptr)
return nullptr;
555 const EditorSize logical = editorSizeOf<P>();
556 const og::Rect frame { 0.0, 0.0,
static_cast<double>(logical.width),
557 static_cast<double>(logical.height) };
558 og::ObjId view = og::call(og::call(
reinterpret_cast<og::ObjId
>(cls),
"alloc"),
559 "initWithFrame:", frame);
560 if (view ==
nullptr)
return nullptr;
561 const webview_ui::HostCallbacks callbacks {
562 this, &cbSetParam, &cbBeginEdit, &cbEndEdit
564 if (!editor.create(view, shadow, callbacks))
566 og::call<void>(view,
"release");
569 editorSink = { &sOnContainerDealloc,
this };
570 if (Ivar ivar = class_getInstanceVariable(cls,
"dsparkSink"))
571 object_setIvar(
static_cast<id>(view),
572 ivar,
reinterpret_cast<id>(&editorSink));
573 editorContainer = view;
574 editor.setBounds(logical.width, logical.height);
575 editor.setVisible(
true);
576 webview_ui::debugLog(
"au createEditorView %dx%d container=%p",
577 logical.width, logical.height, view);
578 return og::call(view,
"autorelease");
585 OSStatus initialize() noexcept
590 if constexpr (HasOfflineMode<P>)
591 user.setOfflineRendering(offlineRender != 0);
593 pullL.assign(maxFrames, 0.0f);
594 pullR.assign(maxFrames, 0.0f);
595 dryL.assign(maxFrames, 0.0f);
596 dryR.assign(maxFrames, 0.0f);
597 if constexpr (HasSidechain<P>)
599 scL.assign(maxFrames, 0.0f);
600 scR.assign(maxFrames, 0.0f);
602 bypassMix = bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
603 if constexpr (HasLatency<P>)
605 const int latency = user.getLatency();
606 cachedLatency.store(latency, std::memory_order_relaxed);
607 dryDelay.prepare(std::max(2 * latency, kDryDelayMinCapacity),
608 static_cast<int>(maxFrames));
616 OSStatus uninitialize() noexcept
623 OSStatus reset() noexcept
625 if constexpr (HasReset<P>)
628 bypassMix = bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
634 void fillStreamFormat(AudioStreamBasicDescription& f)
const noexcept
636 std::memset(&f, 0,
sizeof(f));
637 f.mSampleRate = sampleRate;
638 f.mFormatID = kAudioFormatLinearPCM;
639 f.mFormatFlags =
static_cast<AudioFormatFlags
>(kAudioFormatFlagsNativeFloatPacked)
640 |
static_cast<AudioFormatFlags
>(kAudioFormatFlagIsNonInterleaved);
641 f.mBytesPerPacket =
sizeof(float);
642 f.mFramesPerPacket = 1;
643 f.mBytesPerFrame =
sizeof(float);
644 f.mChannelsPerFrame =
static_cast<UInt32
>(currentChannels);
645 f.mBitsPerChannel = 32;
648 bool acceptableFormat(
const AudioStreamBasicDescription& f)
const noexcept
650 return f.mFormatID == kAudioFormatLinearPCM
651 && (f.mFormatFlags & kAudioFormatFlagIsFloat) != 0
652 && (f.mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0
653 && supportsChannelCount<P>(
static_cast<int>(f.mChannelsPerFrame))
654 && f.mBitsPerChannel == 32
655 && f.mSampleRate > 0.0;
660 static constexpr int numChannelConfigs() noexcept
662 return channelSupportOf<P>() == ChannelSupport::MonoAndStereo ? 2 : 1;
667 void buildPresetArray() noexcept
669 if constexpr (kNumPresets > 0)
671 if (presetArray !=
nullptr)
return;
672 presetStorage.resize(
static_cast<size_t>(kNumPresets));
673 const void* pointers[kNumPresets == 0 ? 1 : kNumPresets];
674 for (
int i = 0; i < kNumPresets; ++i)
676 presetStorage[
static_cast<size_t>(i)].presetNumber = i;
677 presetStorage[
static_cast<size_t>(i)].presetName =
678 CFStringCreateWithCString(
679 nullptr, P::factoryPresets[
static_cast<size_t>(i)].name,
680 kCFStringEncodingUTF8);
681 pointers[i] = &presetStorage[
static_cast<size_t>(i)];
683 presetArray = CFArrayCreate(
nullptr, pointers,
684 static_cast<CFIndex
>(kNumPresets),
nullptr);
688 OSStatus getPropertyInfo(AudioUnitPropertyID
id, AudioUnitScope scope,
689 AudioUnitElement element, UInt32* outSize,
690 Boolean* outWritable)
noexcept
693 Boolean writable =
false;
696 case kAudioUnitProperty_StreamFormat:
697 size =
sizeof(AudioStreamBasicDescription); writable =
true;
break;
698 case kAudioUnitProperty_SampleRate:
699 size =
sizeof(Float64); writable =
true;
break;
700 case kAudioUnitProperty_MaximumFramesPerSlice:
701 size =
sizeof(UInt32); writable =
true;
break;
702 case kAudioUnitProperty_ElementCount:
703 size =
sizeof(UInt32);
break;
704 case kAudioUnitProperty_SupportedNumChannels:
705 size =
static_cast<UInt32
>(numChannelConfigs() *
sizeof(AUChannelInfo));
707 case kAudioUnitProperty_HostCallbacks:
708 size =
sizeof(HostCallbackInfo); writable =
true;
break;
709 case kAudioUnitProperty_OfflineRender:
710 size =
sizeof(UInt32); writable =
true;
break;
711 case kAudioUnitProperty_FactoryPresets:
712 if (kNumPresets == 0)
return kAudioUnitErr_InvalidProperty;
713 size =
sizeof(CFArrayRef);
715 case kAudioUnitProperty_ParameterList:
716 size = scope == kAudioUnitScope_Global
717 ?
static_cast<UInt32
>((kNumParams + 1) *
sizeof(AudioUnitParameterID))
720 case kAudioUnitProperty_ParameterInfo:
721 size =
sizeof(AudioUnitParameterInfo);
break;
722 case kAudioUnitProperty_ParameterValueStrings:
724 const int idx = indexOfParamId(element);
725 if (idx < 0 || P::parameters[
static_cast<size_t>(idx)].labels ==
nullptr)
726 return kAudioUnitErr_InvalidProperty;
727 size =
sizeof(CFArrayRef);
730 case kAudioUnitProperty_ParameterStringFromValue:
731 size =
sizeof(AudioUnitParameterStringFromValue);
break;
732 case kAudioUnitProperty_ParameterValueFromString:
733 size =
sizeof(AudioUnitParameterValueFromString);
break;
734 case kAudioUnitProperty_Latency:
735 case kAudioUnitProperty_TailTime:
736 size =
sizeof(Float64);
break;
737 case kAudioUnitProperty_BypassEffect:
738 size =
sizeof(UInt32); writable =
true;
break;
739 case kAudioUnitProperty_ClassInfo:
740 size =
sizeof(CFPropertyListRef); writable =
true;
break;
741 case kAudioUnitProperty_SetRenderCallback:
742 size =
sizeof(AURenderCallbackStruct); writable =
true;
break;
743 case kAudioUnitProperty_MakeConnection:
744 size =
sizeof(AudioUnitConnection); writable =
true;
break;
745 case kAudioUnitProperty_InPlaceProcessing:
746 size =
sizeof(UInt32);
break;
747 case kAudioUnitProperty_PresentPreset:
748 case kAudioUnitProperty_CurrentPreset:
749 size =
sizeof(AUPreset); writable =
true;
break;
750#if defined(DSPARK_PLUGIN_WEBVIEW)
751 case kAudioUnitProperty_CocoaUI:
752 if constexpr (!kHasWebEditor)
return kAudioUnitErr_InvalidProperty;
753 size =
sizeof(AudioUnitCocoaViewInfo);
755 case kDsparkEditorHookProperty:
756 if constexpr (!kHasWebEditor)
return kAudioUnitErr_InvalidProperty;
757 size =
sizeof(EditorHook);
762 return kAudioUnitErr_InvalidProperty;
764 if (outSize) *outSize = size;
765 if (outWritable) *outWritable = writable;
769 OSStatus getProperty(AudioUnitPropertyID
id, AudioUnitScope scope,
770 AudioUnitElement element,
void* outData,
771 UInt32* ioSize)
noexcept
773 if (ioSize ==
nullptr)
return kAudio_ParamError;
774 if (outData ==
nullptr)
777 return getPropertyInfo(
id, scope, element, ioSize, &w);
781 case kAudioUnitProperty_StreamFormat:
783 if (*ioSize <
sizeof(AudioStreamBasicDescription))
784 return kAudioUnitErr_InvalidPropertyValue;
785 fillStreamFormat(*
static_cast<AudioStreamBasicDescription*
>(outData));
786 *ioSize =
sizeof(AudioStreamBasicDescription);
789 case kAudioUnitProperty_SampleRate:
790 if (*ioSize <
sizeof(Float64))
return kAudioUnitErr_InvalidPropertyValue;
791 *
static_cast<Float64*
>(outData) = sampleRate;
792 *ioSize =
sizeof(Float64);
794 case kAudioUnitProperty_MaximumFramesPerSlice:
795 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
796 *
static_cast<UInt32*
>(outData) = maxFrames;
797 *ioSize =
sizeof(UInt32);
799 case kAudioUnitProperty_ElementCount:
800 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
801 *
static_cast<UInt32*
>(outData) =
802 scope == kAudioUnitScope_Input ? kNumInputElements : 1;
803 *ioSize =
sizeof(UInt32);
805 case kAudioUnitProperty_SupportedNumChannels:
808 static_cast<UInt32
>(numChannelConfigs() *
sizeof(AUChannelInfo));
809 if (*ioSize < want)
return kAudioUnitErr_InvalidPropertyValue;
810 auto* info =
static_cast<AUChannelInfo*
>(outData);
813 if (supportsChannelCount<P>(1))
815 info[slot].inChannels = kIsInstrument ? 0 : 1;
816 info[slot].outChannels = 1;
819 if (supportsChannelCount<P>(2))
821 info[slot].inChannels = kIsInstrument ? 0 : 2;
822 info[slot].outChannels = 2;
828 case kAudioUnitProperty_HostCallbacks:
829 if (*ioSize <
sizeof(HostCallbackInfo))
830 return kAudioUnitErr_InvalidPropertyValue;
831 *
static_cast<HostCallbackInfo*
>(outData) = hostCallbacks;
832 *ioSize =
sizeof(HostCallbackInfo);
834 case kAudioUnitProperty_OfflineRender:
835 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
836 *
static_cast<UInt32*
>(outData) = offlineRender;
837 *ioSize =
sizeof(UInt32);
839 case kAudioUnitProperty_FactoryPresets:
841 if (kNumPresets == 0)
return kAudioUnitErr_InvalidProperty;
842 if (*ioSize <
sizeof(CFArrayRef))
843 return kAudioUnitErr_InvalidPropertyValue;
845 if (presetArray ==
nullptr)
return kAudioUnitErr_InvalidProperty;
846 *
static_cast<CFArrayRef*
>(outData) =
847 static_cast<CFArrayRef
>(CFRetain(presetArray));
848 *ioSize =
sizeof(CFArrayRef);
851 case kAudioUnitProperty_ParameterList:
853 if (scope != kAudioUnitScope_Global)
859 static_cast<UInt32
>((kNumParams + 1) *
sizeof(AudioUnitParameterID));
860 if (*ioSize < want)
return kAudioUnitErr_InvalidPropertyValue;
861 auto* ids =
static_cast<AudioUnitParameterID*
>(outData);
862 for (
size_t i = 0; i < kNumParams; ++i)
863 ids[i] =
hash32(P::parameters[i].
id);
868 case kAudioUnitProperty_ParameterInfo:
870 if (*ioSize <
sizeof(AudioUnitParameterInfo))
871 return kAudioUnitErr_InvalidPropertyValue;
872 auto* info =
static_cast<AudioUnitParameterInfo*
>(outData);
873 std::memset(info, 0,
sizeof(*info));
874 info->flags = kAudioUnitParameterFlag_IsReadable
875 | kAudioUnitParameterFlag_IsWritable
876 | kAudioUnitParameterFlag_HasCFNameString
877 | kAudioUnitParameterFlag_CFNameRelease;
878 if (element == kBypassParamId)
880 std::snprintf(info->name,
sizeof(info->name),
"Bypass");
881 info->cfNameString = CFStringCreateWithCString(
882 nullptr,
"Bypass", kCFStringEncodingUTF8);
883 info->unit = kAudioUnitParameterUnit_Boolean;
884 info->minValue = 0.0f;
885 info->maxValue = 1.0f;
886 info->defaultValue = 0.0f;
887 *ioSize =
sizeof(AudioUnitParameterInfo);
890 const int idx = indexOfParamId(element);
891 if (idx < 0)
return kAudioUnitErr_InvalidParameter;
892 const auto& spec = P::parameters[
static_cast<size_t>(idx)];
893 std::snprintf(info->name,
sizeof(info->name),
"%s", spec.name);
894 info->cfNameString = CFStringCreateWithCString(
895 nullptr, spec.name, kCFStringEncodingUTF8);
896 info->unit =
isToggle(spec) ? kAudioUnitParameterUnit_Boolean
897 : spec.steps > 0 && spec.minValue == 0.0f
898 && spec.maxValue ==
static_cast<float>(spec.steps)
899 ? kAudioUnitParameterUnit_Indexed
900 : (std::strcmp(spec.unit,
"dB") == 0
901 ? kAudioUnitParameterUnit_Decibels
902 : kAudioUnitParameterUnit_Generic);
903 if (spec.labels !=
nullptr)
904 info->flags |= kAudioUnitParameterFlag_ValuesHaveStrings;
905 info->minValue = spec.minValue;
906 info->maxValue = spec.maxValue;
907 info->defaultValue = spec.defValue;
908 *ioSize =
sizeof(AudioUnitParameterInfo);
911 case kAudioUnitProperty_ParameterValueStrings:
914 const int idx = indexOfParamId(element);
915 if (idx < 0)
return kAudioUnitErr_InvalidParameter;
916 const Param& spec = P::parameters[
static_cast<size_t>(idx)];
917 if (spec.labels ==
nullptr)
return kAudioUnitErr_InvalidProperty;
918 if (*ioSize <
sizeof(CFArrayRef))
return kAudioUnitErr_InvalidPropertyValue;
919 CFMutableArrayRef strings = CFArrayCreateMutable(
920 nullptr,
static_cast<CFIndex
>(spec.steps + 1), &kCFTypeArrayCallBacks);
921 if (strings ==
nullptr)
return kAudioUnitErr_InvalidProperty;
922 for (
int i = 0; i <= spec.steps; ++i)
924 const char* label = spec.labels[i] !=
nullptr ? spec.labels[i] :
"";
925 CFStringRef text = CFStringCreateWithCString(
nullptr, label,
926 kCFStringEncodingUTF8);
927 if (text ==
nullptr)
continue;
928 CFArrayAppendValue(strings, text);
931 *
static_cast<CFArrayRef*
>(outData) = strings;
932 *ioSize =
sizeof(CFArrayRef);
935 case kAudioUnitProperty_ParameterStringFromValue:
938 if (*ioSize <
sizeof(AudioUnitParameterStringFromValue))
939 return kAudioUnitErr_InvalidPropertyValue;
940 auto* request =
static_cast<AudioUnitParameterStringFromValue*
>(outData);
941 const int idx = indexOfParamId(request->inParamID);
942 if (idx < 0)
return kAudioUnitErr_InvalidParameter;
943 const Param& spec = P::parameters[
static_cast<size_t>(idx)];
944 const double plain = request->inValue !=
nullptr
945 ?
static_cast<double>(*request->inValue)
946 :
toPlain(spec, shadow[static_cast<size_t>(idx)].load(std::memory_order_relaxed));
948 formatValue(spec, plain, text,
static_cast<int>(
sizeof(text)));
949 request->outString = CFStringCreateWithCString(
nullptr, text,
950 kCFStringEncodingUTF8);
951 *ioSize =
sizeof(AudioUnitParameterStringFromValue);
954 case kAudioUnitProperty_ParameterValueFromString:
957 if (*ioSize <
sizeof(AudioUnitParameterValueFromString))
958 return kAudioUnitErr_InvalidPropertyValue;
959 auto* request =
static_cast<AudioUnitParameterValueFromString*
>(outData);
960 const int idx = indexOfParamId(request->inParamID);
961 if (idx < 0)
return kAudioUnitErr_InvalidParameter;
964 if (request->inString ==
nullptr
965 || !CFStringGetCString(request->inString, text,
966 static_cast<CFIndex
>(
sizeof(text)),
967 kCFStringEncodingUTF8)
968 || !
parseValue(P::parameters[
static_cast<size_t>(idx)], text, plain))
969 return kAudioUnitErr_InvalidPropertyValue;
970 request->outValue =
static_cast<AudioUnitParameterValue
>(plain);
971 *ioSize =
sizeof(AudioUnitParameterValueFromString);
974 case kAudioUnitProperty_Latency:
975 if (*ioSize <
sizeof(Float64))
return kAudioUnitErr_InvalidPropertyValue;
976 *
static_cast<Float64*
>(outData) =
978 ? cachedLatency.load(std::memory_order_relaxed) / sampleRate
980 *ioSize =
sizeof(Float64);
982 case kAudioUnitProperty_TailTime:
984 if (*ioSize <
sizeof(Float64))
return kAudioUnitErr_InvalidPropertyValue;
986 if constexpr (HasTail<P>)
987 tail = user.getTailSeconds();
988 *
static_cast<Float64*
>(outData) = tail;
989 *ioSize =
sizeof(Float64);
992 case kAudioUnitProperty_BypassEffect:
993 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
994 *
static_cast<UInt32*
>(outData) =
995 bypass.load(std::memory_order_relaxed) ? 1 : 0;
996 *ioSize =
sizeof(UInt32);
998 case kAudioUnitProperty_InPlaceProcessing:
999 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
1000 *
static_cast<UInt32*
>(outData) = 1;
1001 *ioSize =
sizeof(UInt32);
1003 case kAudioUnitProperty_PresentPreset:
1004 case kAudioUnitProperty_CurrentPreset:
1006 if (*ioSize <
sizeof(AUPreset))
return kAudioUnitErr_InvalidPropertyValue;
1007 auto*
preset =
static_cast<AUPreset*
>(outData);
1008 preset->presetNumber = currentPresetNumber;
1009 preset->presetName = presetName !=
nullptr
1010 ?
static_cast<CFStringRef
>(CFRetain(presetName))
1011 : CFStringCreateWithCString(nullptr,
"Untitled",
1012 kCFStringEncodingUTF8);
1013 *ioSize =
sizeof(AUPreset);
1016 case kAudioUnitProperty_ClassInfo:
1018 if (*ioSize <
sizeof(CFPropertyListRef))
1019 return kAudioUnitErr_InvalidPropertyValue;
1020 double norm[kNumParams == 0 ? 1 : kNumParams];
1021 for (
size_t i = 0; i < kNumParams; ++i)
1022 norm[i] = shadow[i].load(std::memory_order_relaxed);
1023 const std::vector<uint8_t> blob =
buildState(
1024 user, norm, kNumParams,
1025 kNumPresets > 0 ? currentPresetNumber : -1,
1026 bypass.load(std::memory_order_relaxed) ? 1 : 0);
1028 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
1029 nullptr, 0, &kCFTypeDictionaryKeyCallBacks,
1030 &kCFTypeDictionaryValueCallBacks);
1031 auto putInt = [&](
const char* key, SInt32 v) {
1032 CFNumberRef n = CFNumberCreate(
nullptr, kCFNumberSInt32Type, &v);
1033 CFStringRef k = CFStringCreateWithCString(
nullptr, key,
1034 kCFStringEncodingUTF8);
1035 CFDictionarySetValue(dict, k, n);
1039 putInt(
"version", 0);
1040 putInt(
"type",
static_cast<SInt32
>(expectedType()));
1041 putInt(
"subtype",
static_cast<SInt32
>(subtype));
1042 putInt(
"manufacturer",
static_cast<SInt32
>(manufacturer));
1043 CFStringRef nameKey = CFSTR(
"name");
1044 CFStringRef nameVal = CFStringCreateWithCString(
1045 nullptr, P::descriptor.name, kCFStringEncodingUTF8);
1046 CFDictionarySetValue(dict, nameKey, nameVal);
1048 CFDataRef data = CFDataCreate(
nullptr, blob.data(),
1049 static_cast<CFIndex
>(blob.size()));
1050 CFDictionarySetValue(dict, CFSTR(
"dspark-state"), data);
1053 *
static_cast<CFPropertyListRef*
>(outData) = dict;
1054 *ioSize =
sizeof(CFPropertyListRef);
1057#if defined(DSPARK_PLUGIN_WEBVIEW)
1058 case kAudioUnitProperty_CocoaUI:
1060 if constexpr (!kHasWebEditor)
return kAudioUnitErr_InvalidProperty;
1061 if (*ioSize <
sizeof(AudioUnitCocoaViewInfo))
1062 return kAudioUnitErr_InvalidPropertyValue;
1063 Class factory = editorFactoryClass();
1064 CFURLRef url = copyOwningBundleUrl();
1065 if (factory ==
nullptr || url ==
nullptr)
1067 if (url !=
nullptr) CFRelease(url);
1068 return kAudioUnitErr_InvalidProperty;
1070 auto* viewInfo =
static_cast<AudioUnitCocoaViewInfo*
>(outData);
1071 viewInfo->mCocoaAUViewBundleLocation = url;
1072 viewInfo->mCocoaAUViewClass[0] = CFStringCreateWithCString(
1073 nullptr, class_getName(factory), kCFStringEncodingUTF8);
1074 *ioSize =
sizeof(AudioUnitCocoaViewInfo);
1077 case kDsparkEditorHookProperty:
1079 if constexpr (!kHasWebEditor)
return kAudioUnitErr_InvalidProperty;
1080 if (*ioSize <
sizeof(EditorHook))
1081 return kAudioUnitErr_InvalidPropertyValue;
1082 *
static_cast<EditorHook*
>(outData) = EditorHook {
this, &sCreateEditorView };
1083 *ioSize =
sizeof(EditorHook);
1088 return kAudioUnitErr_InvalidProperty;
1092 OSStatus setProperty(AudioUnitPropertyID
id, AudioUnitScope scope,
1093 AudioUnitElement element,
const void* inData,
1094 UInt32 inSize)
noexcept
1098 case kAudioUnitProperty_StreamFormat:
1100 if (inData ==
nullptr || inSize <
sizeof(AudioStreamBasicDescription))
1101 return kAudioUnitErr_InvalidPropertyValue;
1102 const auto* f =
static_cast<const AudioStreamBasicDescription*
>(inData);
1103 if (!acceptableFormat(*f))
return kAudioUnitErr_FormatNotSupported;
1104 sampleRate = f->mSampleRate;
1107 currentChannels =
static_cast<int>(f->mChannelsPerFrame);
1110 case kAudioUnitProperty_SampleRate:
1111 if (inData ==
nullptr || inSize <
sizeof(Float64))
1112 return kAudioUnitErr_InvalidPropertyValue;
1113 sampleRate = *
static_cast<const Float64*
>(inData);
1115 case kAudioUnitProperty_MaximumFramesPerSlice:
1116 if (inData ==
nullptr || inSize <
sizeof(UInt32))
1117 return kAudioUnitErr_InvalidPropertyValue;
1118 maxFrames = *
static_cast<const UInt32*
>(inData);
1119 notifyProperty(
id, scope, element);
1121 case kAudioUnitProperty_PresentPreset:
1122 case kAudioUnitProperty_CurrentPreset:
1124 if (inData ==
nullptr || inSize <
sizeof(AUPreset))
1125 return kAudioUnitErr_InvalidPropertyValue;
1126 const auto*
preset =
static_cast<const AUPreset*
>(inData);
1127 if (
preset->presetNumber >= 0)
1130 if (
preset->presetNumber >= kNumPresets)
1131 return kAudioUnitErr_InvalidPropertyValue;
1132 applyFactoryPresetIdx(
preset->presetNumber);
1134 if (presetName !=
nullptr) CFRelease(presetName);
1135 presetName =
preset->presetName !=
nullptr
1136 ?
static_cast<CFStringRef
>(CFRetain(
preset->presetName))
1137 : CFStringCreateWithCString(
1139 [&]() -> const char* {
1140 if constexpr (kNumPresets > 0)
1141 return P::factoryPresets[
static_cast<size_t>(
1142 preset->presetNumber)].name;
1146 kCFStringEncodingUTF8);
1147 notifyProperty(
id, scope, element);
1150 currentPresetNumber = -1;
1151 if (presetName !=
nullptr) CFRelease(presetName);
1152 presetName =
preset->presetName !=
nullptr
1153 ?
static_cast<CFStringRef
>(CFRetain(
preset->presetName))
1155 notifyProperty(
id, scope, element);
1158 case kAudioUnitProperty_HostCallbacks:
1160 if (inData ==
nullptr)
return kAudioUnitErr_InvalidPropertyValue;
1162 std::memset(&hostCallbacks, 0,
sizeof(hostCallbacks));
1163 std::memcpy(&hostCallbacks, inData,
1164 inSize <
sizeof(hostCallbacks) ? inSize
1165 : sizeof(hostCallbacks));
1168 case kAudioUnitProperty_OfflineRender:
1169 if (inData ==
nullptr || inSize <
sizeof(UInt32))
1170 return kAudioUnitErr_InvalidPropertyValue;
1171 offlineRender = *
static_cast<const UInt32*
>(inData);
1172 if constexpr (HasOfflineMode<P>)
1173 user.setOfflineRendering(offlineRender != 0);
1175 case kAudioUnitProperty_BypassEffect:
1176 if (inData ==
nullptr || inSize <
sizeof(UInt32))
1177 return kAudioUnitErr_InvalidPropertyValue;
1178 bypass.store(*
static_cast<const UInt32*
>(inData) != 0,
1179 std::memory_order_relaxed);
1181 case kAudioUnitProperty_SetRenderCallback:
1186 constexpr UInt32 storable = kIsInstrument ? 2 : kNumInputElements;
1187 if (scope != kAudioUnitScope_Input || element >= storable
1188 || inData ==
nullptr || inSize <
sizeof(AURenderCallbackStruct))
1189 return kAudioUnitErr_InvalidPropertyValue;
1190 inputCallback[element] = *
static_cast<const AURenderCallbackStruct*
>(inData);
1191 inputConnection[element] =
nullptr;
1194 case kAudioUnitProperty_MakeConnection:
1196 if (inData ==
nullptr || inSize <
sizeof(AudioUnitConnection))
1197 return kAudioUnitErr_InvalidPropertyValue;
1198 const auto* c =
static_cast<const AudioUnitConnection*
>(inData);
1199 constexpr UInt32 storable = kIsInstrument ? 2 : kNumInputElements;
1200 if (c->destInputNumber >= storable)
1201 return kAudioUnitErr_InvalidPropertyValue;
1202 inputConnection[c->destInputNumber] = c->sourceAudioUnit;
1203 inputConnectionBus[c->destInputNumber] = c->sourceOutputNumber;
1204 inputCallback[c->destInputNumber] = {};
1207 case kAudioUnitProperty_ClassInfo:
1209 if (inData ==
nullptr || inSize <
sizeof(CFPropertyListRef))
1210 return kAudioUnitErr_InvalidPropertyValue;
1211 CFPropertyListRef plist = *
static_cast<const CFPropertyListRef*
>(inData);
1212 if (plist ==
nullptr || CFGetTypeID(plist) != CFDictionaryGetTypeID())
1213 return kAudioUnitErr_InvalidPropertyValue;
1214 auto dict =
static_cast<CFDictionaryRef
>(plist);
1215 auto data =
static_cast<CFDataRef
>(
1216 CFDictionaryGetValue(dict, CFSTR(
"dspark-state")));
1217 if (data ==
nullptr || CFGetTypeID(data) != CFDataGetTypeID())
1219 double norm[kNumParams == 0 ? 1 : kNumParams];
1220 for (
size_t i = 0; i < kNumParams; ++i)
1221 norm[i] = shadow[i].load(std::memory_order_relaxed);
1223 int bypassState = -1;
1225 CFDataGetBytePtr(data),
1226 static_cast<size_t>(CFDataGetLength(data)), norm,
1227 &program, &bypassState))
1229 for (
size_t i = 0; i < kNumParams; ++i)
1230 applyNormalized(
static_cast<int>(i), norm[i]);
1231 if (kNumPresets > 0 && program >= 0 && program < kNumPresets)
1232 currentPresetNumber = program;
1233 if (bypassState >= 0)
1234 bypass.store(bypassState != 0, std::memory_order_relaxed);
1240 return kAudioUnitErr_InvalidProperty;
1246 OSStatus getParameter(AudioUnitParameterID
id, AudioUnitScope scope,
1247 AudioUnitParameterValue* outValue)
noexcept
1249 if (scope != kAudioUnitScope_Global || outValue ==
nullptr)
1250 return kAudioUnitErr_InvalidParameter;
1251 if (
id == kBypassParamId)
1253 *outValue = bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
1256 const int idx = indexOfParamId(
id);
1257 if (idx < 0)
return kAudioUnitErr_InvalidParameter;
1258 *outValue =
static_cast<AudioUnitParameterValue
>(
1259 toPlain(P::parameters[
static_cast<size_t>(idx)],
1260 shadow[
static_cast<size_t>(idx)].load(std::memory_order_relaxed)));
1264 OSStatus setParameter(AudioUnitParameterID
id, AudioUnitScope scope,
1265 AudioUnitParameterValue value)
noexcept
1267 if (scope != kAudioUnitScope_Global)
return kAudioUnitErr_InvalidParameter;
1268 if (
id == kBypassParamId)
1270 bypass.store(value >= 0.5f, std::memory_order_relaxed);
1273 const int idx = indexOfParamId(
id);
1274 if (idx < 0)
return kAudioUnitErr_InvalidParameter;
1275 applyNormalized(idx,
1276 toNormalized(P::parameters[
static_cast<size_t>(idx)], value));
1284 OSStatus pushMidi(UInt32 status, UInt32 data1, UInt32 data2,
1285 UInt32 offsetFrame)
noexcept
1287 if constexpr (HasMidi<P>)
1290 ev.channel =
static_cast<uint8_t
>(status & 0x0Fu);
1291 ev.sampleOffset =
static_cast<int>(offsetFrame);
1292 const uint8_t d1 =
static_cast<uint8_t
>(data1 & 0x7Fu);
1293 const uint8_t d2 =
static_cast<uint8_t
>(data2 & 0x7Fu);
1294 switch (status & 0xF0u)
1297 ev.type = d2 > 0 ? MidiEvent::Type::NoteOn : MidiEvent::Type::NoteOff;
1299 ev.value =
static_cast<float>(d2) / 127.0f;
1302 ev.type = MidiEvent::Type::NoteOff;
1304 ev.value =
static_cast<float>(d2) / 127.0f;
1307 ev.type = MidiEvent::Type::PolyPressure;
1309 ev.value =
static_cast<float>(d2) / 127.0f;
1312 ev.type = MidiEvent::Type::ControlChange;
1314 ev.value =
static_cast<float>(d2) / 127.0f;
1317 ev.type = MidiEvent::Type::ChannelPressure;
1318 ev.value =
static_cast<float>(d1) / 127.0f;
1321 ev.type = MidiEvent::Type::PitchBend;
1322 ev.value = (
static_cast<float>((d2 << 7) | d1) - 8192.0f) / 8192.0f;
1327 const uint32_t tail = midiTail.load(std::memory_order_relaxed);
1328 const uint32_t next = (tail + 1) & (kMidiRingSize - 1);
1329 if (next == midiHead.load(std::memory_order_acquire))
1331 midiRing[tail] = ev;
1332 midiTail.store(next, std::memory_order_release);
1341 return kAudioUnitErr_InvalidProperty;
1346 void forwardTransport() noexcept
1348 if constexpr (HasTransport<P>)
1350 TransportInfo info {};
1352 if (hostCallbacks.beatAndTempoProc !=
nullptr)
1354 Float64 beat = 0.0, tempo = 0.0;
1355 if (hostCallbacks.beatAndTempoProc(hostCallbacks.hostUserData,
1356 &beat, &tempo) == noErr)
1360 info.tempoBpm = tempo;
1361 info.tempoValid =
true;
1363 info.ppqPosition = beat;
1364 info.positionValid =
true;
1368 if (hostCallbacks.musicalTimeLocationProc !=
nullptr)
1370 UInt32 deltaToNextBeat = 0;
1373 Float64 downbeat = 0.0;
1374 if (hostCallbacks.musicalTimeLocationProc(
1375 hostCallbacks.hostUserData, &deltaToNextBeat, &num, &den,
1376 &downbeat) == noErr)
1388 info.timeSigDenominator =
static_cast<int>(den);
1389 info.timeSigValid =
true;
1390 info.barStartPpq = downbeat;
1394 if (hostCallbacks.transportStateProc !=
nullptr)
1396 Boolean isPlaying =
false, changed =
false, isCycling =
false;
1397 Float64 samplePos = 0.0, cycleStart = 0.0, cycleEnd = 0.0;
1398 if (hostCallbacks.transportStateProc(
1399 hostCallbacks.hostUserData, &isPlaying, &changed,
1400 &samplePos, &isCycling, &cycleStart, &cycleEnd) == noErr)
1402 info.playing = isPlaying;
1403 info.looping = isCycling;
1404 info.loopStartPpq = cycleStart;
1405 info.loopEndPpq = cycleEnd;
1406 info.loopValid = isCycling;
1411 user.setTransport(info);
1420 OSStatus pullInput(UInt32 element,
float* dst[2],
1421 const AudioTimeStamp* timeStamp, UInt32 frames)
noexcept
1427 struct StereoBufferList
1429 UInt32 mNumberBuffers;
1430 ::AudioBuffer mBuffers[2];
1432 const UInt32 width =
static_cast<UInt32
>(currentChannels) < 2u
1433 ?
static_cast<UInt32
>(currentChannels) : 2u;
1434 list.mNumberBuffers = width;
1435 for (UInt32 ch = 0; ch < width; ++ch)
1437 list.mBuffers[ch].mNumberChannels = 1;
1438 list.mBuffers[ch].mDataByteSize = frames *
sizeof(float);
1439 list.mBuffers[ch].mData = dst[ch];
1441 auto* abl =
reinterpret_cast<AudioBufferList*
>(&list);
1442 OSStatus status = noErr;
1443 AudioUnitRenderActionFlags flags = 0;
1444 if (inputCallback[element].inputProc !=
nullptr)
1445 status = inputCallback[element].inputProc(
1446 inputCallback[element].inputProcRefCon, &flags, timeStamp,
1447 element, frames, abl);
1448 else if (inputConnection[element] !=
nullptr)
1449 status = AudioUnitRender(inputConnection[element], &flags, timeStamp,
1450 inputConnectionBus[element], frames, abl);
1452 for (UInt32 ch = 0; ch < width; ++ch)
1453 std::memset(dst[ch], 0, frames *
sizeof(
float));
1454 if (status != noErr)
return status;
1455 for (UInt32 ch = 0; ch < width && ch < list.mNumberBuffers; ++ch)
1456 if (list.mBuffers[ch].mData !=
nullptr)
1457 dst[ch] =
static_cast<float*
>(list.mBuffers[ch].mData);
1461 OSStatus render(AudioUnitRenderActionFlags* ioFlags,
1462 const AudioTimeStamp* timeStamp, UInt32 busNumber,
1463 UInt32 frames, AudioBufferList* ioData)
noexcept
1466 if (ioData ==
nullptr || frames == 0)
return noErr;
1467 if (!initialized)
return kAudioUnitErr_Uninitialized;
1468 if (frames > maxFrames)
return kAudioUnitErr_TooManyFramesToProcess;
1480 for (
int i = 0; i < scheduledCount; ++i)
1482 BlockEvent ev = scheduled[i];
1483 if (ev.offset >=
static_cast<int32_t
>(frames))
1484 ev.offset =
static_cast<int32_t
>(frames) - 1;
1485 if (ev.offset < 0) ev.offset = 0;
1486 events[eventCount++] = ev;
1489 if constexpr (HasMidi<P>)
1491 uint32_t head = midiHead.load(std::memory_order_relaxed);
1492 const uint32_t tail = midiTail.load(std::memory_order_acquire);
1493 while (head != tail)
1496 ev.kind = BlockEvent::Kind::Midi;
1497 ev.midi = midiRing[head];
1498 ev.offset = ev.midi.sampleOffset;
1499 if (ev.offset >=
static_cast<int32_t
>(frames))
1500 ev.offset =
static_cast<int32_t
>(frames) - 1;
1501 if (ev.offset < 0) ev.offset = 0;
1502 if (eventCount < kMaxBlockEvents) events[eventCount++] = ev;
1504 head = (head + 1) & (kMidiRingSize - 1);
1506 midiHead.store(head, std::memory_order_release);
1510 const UInt32 width =
static_cast<UInt32
>(currentChannels);
1511 const UInt32 outCh = ioData->mNumberBuffers < width
1512 ? ioData->mNumberBuffers : width;
1513 if (outCh < 1)
return noErr;
1518 float* pull[2] = { pullL.data(), pullR.data() };
1519 if constexpr (!kIsInstrument)
1521 const OSStatus pullStatus = pullInput(0, pull, timeStamp, frames);
1522 if (pullStatus != noErr)
return pullStatus;
1525 float* out[2] = {
nullptr,
nullptr };
1526 for (UInt32 ch = 0; ch < outCh; ++ch)
1528 if (ioData->mBuffers[ch].mData ==
nullptr)
1529 ioData->mBuffers[ch].mData = pull[ch];
1530 out[ch] =
static_cast<float*
>(ioData->mBuffers[ch].mData);
1531 ioData->mBuffers[ch].mDataByteSize = frames *
sizeof(float);
1536 float* dry[2] = { dryL.data(), dryR.data() };
1537 for (UInt32 ch = 0; ch < outCh; ++ch)
1541 std::memset(out[ch], 0, frames *
sizeof(
float));
1544 std::memcpy(dry[ch], pull[ch], frames *
sizeof(
float));
1545 if (out[ch] != pull[ch])
1546 std::memcpy(out[ch], pull[ch], frames *
sizeof(
float));
1548 if constexpr (HasLatency<P> && !kIsInstrument)
1549 dryDelay.process(dry,
static_cast<int>(outCh),
static_cast<int>(frames),
1550 cachedLatency.load(std::memory_order_relaxed));
1554 float* sc[2] = {
nullptr,
nullptr };
1555 if constexpr (HasSidechain<P>)
1559 if (pullInput(1, sc, timeStamp, frames) != noErr)
1563 std::memset(sc[0], 0, frames *
sizeof(
float));
1564 std::memset(sc[1], 0, frames *
sizeof(
float));
1570 auto applyEvent = [&](
const BlockEvent& ev,
int blockStart)
noexcept ->
bool {
1573 case BlockEvent::Kind::Midi:
1574 if constexpr (HasMidi<P>)
1576 MidiEvent midi = ev.midi;
1577 midi.sampleOffset = ev.offset - blockStart;
1578 if (midi.sampleOffset < 0) midi.sampleOffset = 0;
1579 user.handleMidiEvent(midi);
1582 case BlockEvent::Kind::Param:
1584 if (
const int idx = indexOfParamId(ev.paramId); idx >= 0)
1586 applyNormalized(idx, ev.value);
1592 auto processSegment = [&](
int start,
int length)
noexcept {
1593 float* sub[2] = { out[0] + start,
1594 outCh > 1 ? out[1] + start : out[0] + start };
1597 if constexpr (HasSidechain<P>)
1600 float* scSub[2] = { sc[0] + start, sc[1] + start };
1602 static_cast<int>(outCh), length);
1603 user.processBlock(view, scView);
1606 user.processBlock(view);
1609 bool paramsChanged =
false;
1610 const int total =
static_cast<int>(frames);
1612 if (!sampleAccurateOf<P>())
1614 for (; evIdx < eventCount; ++evIdx)
1615 paramsChanged |= applyEvent(events[evIdx], 0);
1616 processSegment(0, total);
1623 while (evIdx < eventCount
1624 && (events[evIdx].offset / kAutomationQuantum)
1625 * kAutomationQuantum <= pos)
1626 paramsChanged |= applyEvent(events[evIdx++], pos);
1628 if (evIdx < eventCount)
1631 * kAutomationQuantum;
1632 if (snapped < next) next = snapped;
1636 processSegment(pos, next - pos);
1639 for (; evIdx < eventCount; ++evIdx)
1640 paramsChanged |= applyEvent(events[evIdx], total);
1643 const float target = bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
1644 if (bypassMix != target || target > 0.0f)
1647 float mix = bypassMix;
1648 for (UInt32 i = 0; i < frames; ++i)
1650 mix += (target > mix) ? step : ((target < mix) ? -step : 0.0f);
1651 mix = mix < 0.0f ? 0.0f : (mix > 1.0f ? 1.0f : mix);
1652 for (UInt32 ch = 0; ch < outCh; ++ch)
1653 out[ch][i] += (dry[ch][i] - out[ch][i]) * mix;
1658 if (paramsChanged) refreshLatency();
1667template <
typename P>
1670 AudioComponentPlugInInterface iface {};
1671 Plugin<P>* state =
nullptr;
1673 static Component* fromSelf(
void* self)
noexcept
1675 return static_cast<Component*
>(self);
1678 static OSStatus sOpen(
void* self, AudioComponentInstance instance)
noexcept
1680 auto* c = fromSelf(self);
1681 c->state =
new (std::nothrow) Plugin<P>();
1682 if (c->state ==
nullptr)
return kAudio_MemFullError;
1683 c->state->instance = instance;
1684 c->state->subtype = gSubtype;
1685 c->state->manufacturer = gManufacturer;
1689 static OSStatus sClose(
void* self)
noexcept
1691 auto* c = fromSelf(self);
1699 static OSStatus sInitialize(
void* self)
noexcept
1700 {
return fromSelf(self)->state->initialize(); }
1702 static OSStatus sUninitialize(
void* self)
noexcept
1703 {
return fromSelf(self)->state->uninitialize(); }
1705 static OSStatus sGetPropertyInfo(
void* self, AudioUnitPropertyID
id,
1706 AudioUnitScope scope, AudioUnitElement element,
1707 UInt32* outSize, Boolean* outWritable)
noexcept
1708 {
return fromSelf(self)->state->getPropertyInfo(
id, scope, element, outSize, outWritable); }
1710 static OSStatus sGetProperty(
void* self, AudioUnitPropertyID
id,
1711 AudioUnitScope scope, AudioUnitElement element,
1712 void* outData, UInt32* ioSize)
noexcept
1713 {
return fromSelf(self)->state->getProperty(
id, scope, element, outData, ioSize); }
1715 static OSStatus sSetProperty(
void* self, AudioUnitPropertyID
id,
1716 AudioUnitScope scope, AudioUnitElement element,
1717 const void* inData, UInt32 inSize)
noexcept
1718 {
return fromSelf(self)->state->setProperty(
id, scope, element, inData, inSize); }
1720 static OSStatus sGetParameter(
void* self, AudioUnitParameterID
id,
1721 AudioUnitScope scope, AudioUnitElement element,
1722 AudioUnitParameterValue* outValue)
noexcept
1725 return fromSelf(self)->state->getParameter(
id, scope, outValue);
1728 static OSStatus sSetParameter(
void* self, AudioUnitParameterID
id,
1729 AudioUnitScope scope, AudioUnitElement element,
1730 AudioUnitParameterValue value,
1731 UInt32 bufferOffset)
noexcept
1734 (void) bufferOffset;
1735 return fromSelf(self)->state->setParameter(
id, scope, value);
1738 static OSStatus sReset(
void* self, AudioUnitScope scope,
1739 AudioUnitElement element)
noexcept
1743 return fromSelf(self)->state->reset();
1746 static OSStatus sRender(
void* self, AudioUnitRenderActionFlags* ioFlags,
1747 const AudioTimeStamp* timeStamp, UInt32 busNumber,
1748 UInt32 frames, AudioBufferList* ioData)
noexcept
1749 {
return fromSelf(self)->state->render(ioFlags, timeStamp, busNumber, frames, ioData); }
1751 static OSStatus sAddPropertyListener(
void* self, AudioUnitPropertyID
id,
1752 AudioUnitPropertyListenerProc proc,
1753 void* user)
noexcept
1755 auto* state = fromSelf(self)->state;
1756 state->listeners.push_back({ id, proc, user });
1760 static OSStatus sRemovePropertyListener(
void* self, AudioUnitPropertyID
id,
1761 AudioUnitPropertyListenerProc proc)
noexcept
1763 auto& ls = fromSelf(self)->state->listeners;
1764 for (
size_t i = ls.size(); i > 0; --i)
1765 if (ls[i - 1].
id ==
id && ls[i - 1].proc == proc)
1766 ls.erase(ls.begin() +
static_cast<ptrdiff_t
>(i - 1));
1770 static OSStatus sRemovePropertyListenerWithUserData(
void* self, AudioUnitPropertyID
id,
1771 AudioUnitPropertyListenerProc proc,
1772 void* user)
noexcept
1774 auto& ls = fromSelf(self)->state->listeners;
1775 for (
size_t i = ls.size(); i > 0; --i)
1776 if (ls[i - 1].
id ==
id && ls[i - 1].proc == proc && ls[i - 1].user == user)
1777 ls.erase(ls.begin() +
static_cast<ptrdiff_t
>(i - 1));
1781 static OSStatus sAddRenderNotify(
void*, AURenderCallback,
void*)
noexcept
1784 static OSStatus sRemoveRenderNotify(
void*, AURenderCallback,
void*)
noexcept
1791 static OSStatus sScheduleParameters(
void* self,
1792 const AudioUnitParameterEvent* events,
1793 UInt32 numEvents)
noexcept
1795 auto* state = fromSelf(self)->state;
1796 auto push = [&](AudioUnitParameterID id, AudioUnitScope scope,
1797 float plain, SInt32 offset) {
1798 if (scope != kAudioUnitScope_Global)
return;
1799 if (
id == kBypassParamId)
1801 state->bypass.store(plain >= 0.5f, std::memory_order_relaxed);
1804 const int idx = Plugin<P>::indexOfParamId(
id);
1805 if (idx < 0 || state->scheduledCount >= kMaxBlockEvents)
return;
1807 ev.offset = offset < 0 ? 0 : offset;
1808 ev.kind = BlockEvent::Kind::Param;
1810 ev.value =
toNormalized(P::parameters[
static_cast<size_t>(idx)], plain);
1811 state->scheduled[state->scheduledCount++] = ev;
1813 for (UInt32 i = 0; events !=
nullptr && i < numEvents; ++i)
1815 const auto& ev = events[i];
1816 if (ev.eventType == kParameterEvent_Immediate)
1817 push(ev.parameter, ev.scope, ev.eventValues.immediate.value,
1818 static_cast<SInt32
>(ev.eventValues.immediate.bufferOffset));
1819 else if (ev.eventType == kParameterEvent_Ramped)
1821 push(ev.parameter, ev.scope, ev.eventValues.ramp.startValue,
1822 ev.eventValues.ramp.startBufferOffset);
1823 push(ev.parameter, ev.scope, ev.eventValues.ramp.endValue,
1824 ev.eventValues.ramp.startBufferOffset
1825 +
static_cast<SInt32
>(ev.eventValues.ramp.durationInFrames));
1833 static OSStatus sMidiEvent(
void* self, UInt32 status, UInt32 data1,
1834 UInt32 data2, UInt32 offsetSampleFrame)
noexcept
1836 return fromSelf(self)->state->pushMidi(status, data1, data2,
1840 static OSStatus sSysEx(
void* self,
const UInt8*, UInt32)
noexcept
1846 static AudioComponentMethod sLookup(SInt16 selector)
noexcept
1848 if constexpr (HasMidi<P>)
1850 if (selector == kMusicDeviceMIDIEventSelect)
1851 return reinterpret_cast<AudioComponentMethod
>(&sMidiEvent);
1852 if (selector == kMusicDeviceSysExSelect)
1853 return reinterpret_cast<AudioComponentMethod
>(&sSysEx);
1857 case kAudioUnitInitializeSelect:
1858 return reinterpret_cast<AudioComponentMethod
>(&sInitialize);
1859 case kAudioUnitUninitializeSelect:
1860 return reinterpret_cast<AudioComponentMethod
>(&sUninitialize);
1861 case kAudioUnitGetPropertyInfoSelect:
1862 return reinterpret_cast<AudioComponentMethod
>(&sGetPropertyInfo);
1863 case kAudioUnitGetPropertySelect:
1864 return reinterpret_cast<AudioComponentMethod
>(&sGetProperty);
1865 case kAudioUnitSetPropertySelect:
1866 return reinterpret_cast<AudioComponentMethod
>(&sSetProperty);
1867 case kAudioUnitGetParameterSelect:
1868 return reinterpret_cast<AudioComponentMethod
>(&sGetParameter);
1869 case kAudioUnitSetParameterSelect:
1870 return reinterpret_cast<AudioComponentMethod
>(&sSetParameter);
1871 case kAudioUnitResetSelect:
1872 return reinterpret_cast<AudioComponentMethod
>(&sReset);
1873 case kAudioUnitRenderSelect:
1874 return reinterpret_cast<AudioComponentMethod
>(&sRender);
1875 case kAudioUnitAddPropertyListenerSelect:
1876 return reinterpret_cast<AudioComponentMethod
>(&sAddPropertyListener);
1877 case kAudioUnitRemovePropertyListenerSelect:
1878 return reinterpret_cast<AudioComponentMethod
>(&sRemovePropertyListener);
1879 case kAudioUnitRemovePropertyListenerWithUserDataSelect:
1880 return reinterpret_cast<AudioComponentMethod
>(&sRemovePropertyListenerWithUserData);
1881 case kAudioUnitAddRenderNotifySelect:
1882 return reinterpret_cast<AudioComponentMethod
>(&sAddRenderNotify);
1883 case kAudioUnitRemoveRenderNotifySelect:
1884 return reinterpret_cast<AudioComponentMethod
>(&sRemoveRenderNotify);
1885 case kAudioUnitScheduleParametersSelect:
1886 return reinterpret_cast<AudioComponentMethod
>(&sScheduleParameters);
1892 inline static OSType gSubtype = 0;
1893 inline static OSType gManufacturer = 0;
1895 static void* factory(
const AudioComponentDescription* desc)
noexcept
1899 if (desc !=
nullptr && desc->componentType != Plugin<P>::expectedType())
1901 auto* c =
new (std::nothrow) Component();
1902 if (c ==
nullptr)
return nullptr;
1903 c->iface.Open = &sOpen;
1904 c->iface.Close = &sClose;
1905 c->iface.Lookup = &sLookup;
1906 c->iface.reserved =
nullptr;
1919#define DSPARK_AU_PLUGIN(PluginClass, subtype4, manufacturer4) \
1920 extern "C" __attribute__((visibility("default"))) \
1921 void* DSParkAuFactory(const AudioComponentDescription* desc) \
1923 using Comp = dspark::plugin::au::Component<PluginClass>; \
1924 Comp::gSubtype = dspark::plugin::au::fourCC(subtype4); \
1925 Comp::gManufacturer = dspark::plugin::au::fourCC(manufacturer4); \
1926 return Comp::factory(desc); \
1932#define DSPARK_AU_PLUGIN(PluginClass, subtype4, manufacturer4)
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
constexpr int kBypassRampSamples
constexpr uint32_t kBypassParamId
std::vector< uint8_t > buildState(const P &user, const double *normalized, size_t numParams, int programIndex=-1, int bypassState=-1)
constexpr double toNormalized(const Param &p, double plain) noexcept
Plain [min, max] -> normalized [0, 1] (steps snap on the way back).
constexpr double toPlain(const Param &p, double normalized) noexcept
Normalized [0, 1] -> plain [min, max], snapped for stepped params.
constexpr int kAutomationQuantum
Sub-block grain: automation/event splits snap to this many frames (bounds the per-call overhead; ~0....
constexpr uint32_t hash32(const char *s) noexcept
FNV-1a 32-bit over a C string - the per-parameter host id.
constexpr int kMaxBlockEvents
Hard cap of timestamped events handled per block; the (very unlikely) excess applies at the position ...
constexpr bool isToggle(const Param &p) noexcept
True for an on/off parameter (two unnamed positions).
constexpr PresetDef< sizeof...(Vs)> preset(const char *name, Vs... values) noexcept
Builds one factory preset: preset("Warm", 6.0f, 0.8f, ...) - one PLAIN value per parameter,...
void formatValue(const Param &p, double plain, char *out, int outSize) noexcept
Formats a plain value for host display ("3.5 dB", "On", "Saw", "4").
void sortBlockEvents(BlockEvent *events, int count) noexcept
Stable insertion sort by offset (tiny N, allocation-free - audio-thread safe; equal offsets keep arri...
constexpr int toBoundedInt(double value, int lo, int hi) noexcept
A floating value from outside converted to an integer, with the range settled BEFORE the conversion.
bool applyState(P &user, const uint8_t *data, size_t size, double *normalized, int *programIndex=nullptr, int *bypassState=nullptr)
Parses a state blob; fills normalized (defaults pre-loaded by the caller), reports a persisted progra...
bool parseValue(const Param &p, const char *text, double &plain) noexcept
Host text -> plain value, the inverse of formatValue(): a choice's label (any case),...
Describes the audio environment for a DSP processor.