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>
80namespace dspark::plugin::au {
82inline constexpr uint32_t kBypassParamId = 0x42595053u;
83inline constexpr int kBypassRampSamples = 256;
85inline OSType fourCC(
const char* s)
noexcept
87 return (
static_cast<OSType
>(
static_cast<unsigned char>(s[0])) << 24)
88 | (
static_cast<OSType
>(
static_cast<unsigned char>(s[1])) << 16)
89 | (
static_cast<OSType
>(
static_cast<unsigned char>(s[2])) << 8)
90 |
static_cast<OSType
>(
static_cast<unsigned char>(s[3]));
93#if defined(DSPARK_PLUGIN_WEBVIEW)
108inline constexpr AudioUnitPropertyID kDsparkEditorHookProperty = 64537;
115 void* context =
nullptr;
116 void* (*createView)(
void* context,
double width,
double height) =
nullptr;
122 double width = 0, height = 0;
129 void (*onDealloc)(
void* context) =
nullptr;
130 void* context =
nullptr;
133inline void editorContainerDealloc(
void* self,
SEL)
noexcept
138 Class cls = objc_getClass(
"DSParkAUEditorContainer");
139 if (cls ==
nullptr)
return;
140 if (Ivar ivar = class_getInstanceVariable(cls,
"dsparkSink"))
142 auto* sink =
reinterpret_cast<EditorViewSink*
>(
143 object_getIvar(
static_cast<id>(self), ivar));
144 if (sink !=
nullptr && sink->onDealloc !=
nullptr)
145 sink->onDealloc(sink->context);
147 objc_super super {
static_cast<id>(self), class_getSuperclass(cls) };
148 reinterpret_cast<void (*)(objc_super*,
SEL)
>(&objc_msgSendSuper)(
149 &super, sel_registerName(
"dealloc"));
154inline Class editorContainerClass()
noexcept
156 static Class registered = []() -> Class {
157 Class viewCls = objc_getClass(
"NSView");
158 if (viewCls ==
nullptr)
return nullptr;
159 Class c = objc_allocateClassPair(viewCls,
"DSParkAUEditorContainer", 0);
161 return objc_getClass(
"DSParkAUEditorContainer");
162 class_addIvar(c,
"dsparkSink",
sizeof(
void*),
alignof(
void*) == 8 ? 3 : 2,
"^v");
163 class_addMethod(c, sel_registerName(
"dealloc"),
164 reinterpret_cast<IMP
>(&editorContainerDealloc),
"v@:");
165 objc_registerClassPair(c);
171inline unsigned int editorFactoryInterfaceVersion(
void*,
SEL)
noexcept
176inline void* editorFactoryUiView(
void*,
SEL,
void* audioUnit, ViewSize size)
noexcept
179 UInt32 ioSize =
sizeof(hook);
180 if (audioUnit ==
nullptr
181 || AudioUnitGetProperty(
static_cast<AudioUnit
>(audioUnit),
182 kDsparkEditorHookProperty, kAudioUnitScope_Global,
183 0, &hook, &ioSize) != noErr
184 || hook.createView ==
nullptr)
186 return hook.createView(hook.context, size.width, size.height);
190inline Class editorFactoryClass()
noexcept
192 static Class registered = []() -> Class {
193 Class c = objc_allocateClassPair(objc_getClass(
"NSObject"),
194 "DSParkAUCocoaViewFactory", 0);
196 return objc_getClass(
"DSParkAUCocoaViewFactory");
197 class_addMethod(c, sel_registerName(
"interfaceVersion"),
198 reinterpret_cast<IMP
>(&editorFactoryInterfaceVersion),
"I@:");
199 class_addMethod(c, sel_registerName(
"uiViewForAudioUnit:withSize:"),
200 reinterpret_cast<IMP
>(&editorFactoryUiView),
201 "@@:^{ComponentInstanceRecord=}{CGSize=dd}");
204 if (Protocol* proto = objc_getProtocol(
"AUCocoaUIBase"))
205 class_addProtocol(c, proto);
206 objc_registerClassPair(c);
215inline CFURLRef copyOwningBundleUrl()
noexcept
217 static const int anchor = 0;
219 if (dladdr(&anchor, &info) == 0 || info.dli_fname ==
nullptr)
return nullptr;
220 const char* path = info.dli_fname;
221 const char* marker =
nullptr;
222 for (
const char* p = std::strstr(path,
"/Contents/MacOS/"); p !=
nullptr;
223 p = std::strstr(p + 1,
"/Contents/MacOS/"))
225 size_t length = marker !=
nullptr ?
static_cast<size_t>(marker - path) : 0;
227 if (
const char* slash = std::strrchr(path,
'/'))
228 length =
static_cast<size_t>(slash - path);
229 if (length == 0)
return nullptr;
230 return CFURLCreateFromFileSystemRepresentation(
231 kCFAllocatorDefault,
reinterpret_cast<const UInt8*
>(path),
232 static_cast<CFIndex
>(length),
true);
240 static constexpr size_t kNumParams = P::parameters.size();
241 static constexpr bool kIsInstrument =
242 P::descriptor.category == Category::Instrument;
243 static constexpr int kNumPresets = factoryPresetCountOf<P>();
244 static_assert(!(kIsInstrument && HasSidechain<P>),
245 "an Instrument has no audio inputs; remove the sidechain "
246 "processBlock or use Category::Fx");
247 static_assert(!kIsInstrument || HasMidi<P>,
248 "an Instrument needs handleMidiEvent (see HasMidi): it has "
249 "no audio input to process");
250 static_assert(paramIdsUnique<P>(),
251 "two parameter ids share a hash32 (or collide with the "
252 "reserved PRGM/BYPS state ids): automation and state would "
253 "cross-wire. Rename one id.");
256 static OSType expectedType()
noexcept
258 if (kIsInstrument)
return fourCC(
"aumu");
259 if (HasMidi<P>)
return fourCC(
"aumf");
260 return fourCC(
"aufx");
263 AudioComponentInstance instance =
nullptr;
264 OSType subtype = 0, manufacturer = 0;
266 double sampleRate = 44100.0;
267 UInt32 maxFrames = 1156;
268 bool initialized =
false;
269 int currentChannels = defaultChannelCount<P>();
270 UInt32 offlineRender = 0;
273 std::atomic<double> shadow[kNumParams == 0 ? 1 : kNumParams] {};
274 std::atomic<bool> bypass {
false };
275 float bypassMix = 0.0f;
276 int cachedLatency = 0;
279 static constexpr UInt32 kNumInputElements =
280 kIsInstrument ? 0 : (HasSidechain<P> ? 2 : 1);
281 AURenderCallbackStruct inputCallback[2] {};
282 AudioUnit inputConnection[2] = {
nullptr,
nullptr };
283 UInt32 inputConnectionBus[2] = { 0, 0 };
285 std::vector<float> pullL, pullR, dryL, dryR, scL, scR;
288 HostCallbackInfo hostCallbacks {};
292 static constexpr uint32_t kMidiRingSize = 256;
293 MidiEvent midiRing[HasMidi<P> ? kMidiRingSize : 1] {};
294 std::atomic<uint32_t> midiHead { 0 }, midiTail { 0 };
298 BlockEvent scheduled[kMaxBlockEvents];
299 int scheduledCount = 0;
303 std::vector<AUPreset> presetStorage;
304 CFArrayRef presetArray =
nullptr;
305 SInt32 currentPresetNumber = -1;
311 AudioUnitPropertyID id;
312 AudioUnitPropertyListenerProc proc;
315 std::vector<Listener> listeners;
316 CFStringRef presetName =
nullptr;
318#if defined(DSPARK_PLUGIN_WEBVIEW)
319 webview_ui::Editor<P> editor;
320 void* editorContainer =
nullptr;
321 EditorViewSink editorSink {};
322 bool editActive[kNumParams == 0 ? 1 : kNumParams] {};
327#if defined(DSPARK_PLUGIN_WEBVIEW)
330 if (presetName !=
nullptr) CFRelease(presetName);
331 for (
auto& preset : presetStorage)
332 if (preset.presetName !=
nullptr) CFRelease(preset.presetName);
333 if (presetArray !=
nullptr) CFRelease(presetArray);
337 void applyFactoryPresetIdx(
int idx)
noexcept
339 if constexpr (kNumPresets > 0)
341 if (idx < 0 || idx >= kNumPresets)
return;
342 for (
size_t i = 0; i < kNumParams; ++i)
343 applyNormalized(
static_cast<int>(i), presetNormalized<P>(idx, i));
344 currentPresetNumber = idx;
352 void refreshLatency()
noexcept
354 if constexpr (HasLatency<P>)
356 const int now = user.getLatency();
357 if (initialized && now != cachedLatency)
360 notifyProperty(kAudioUnitProperty_Latency,
361 kAudioUnitScope_Global, 0);
366 void notifyProperty(AudioUnitPropertyID
id, AudioUnitScope scope,
367 AudioUnitElement element)
noexcept
369 for (
const auto& l : listeners)
370 if (l.id ==
id && l.proc !=
nullptr)
371 l.proc(l.user, instance,
id, scope, element);
376 for (
size_t i = 0; i < kNumParams; ++i)
377 shadow[i].store(toNormalized(P::parameters[i], P::parameters[i].defValue),
378 std::memory_order_relaxed);
381 static int indexOfParamId(AudioUnitParameterID
id)
noexcept
383 for (
size_t i = 0; i < kNumParams; ++i)
384 if (hash32(P::parameters[i].
id) ==
id)
return static_cast<int>(i);
388 void applyNormalized(
int index,
double normalized)
noexcept
392 if (normalized != normalized)
return;
393 const auto& spec = P::parameters[
static_cast<size_t>(index)];
394 shadow[
static_cast<size_t>(index)].store(normalized, std::memory_order_relaxed);
395 user.setParameter(index,
static_cast<float>(toPlain(spec, normalized)));
398 void applyAllShadows()
noexcept
400 for (
size_t i = 0; i < kNumParams; ++i)
401 user.setParameter(
static_cast<int>(i),
402 static_cast<float>(toPlain(P::parameters[i],
403 shadow[i].load(std::memory_order_relaxed))));
406#if defined(DSPARK_PLUGIN_WEBVIEW)
410 static constexpr bool kHasWebEditor =
411 HasEditor<P> && webview_ui::Editor<P>::kAvailable;
413 void sendGesture(
int index, AudioUnitEventType type)
noexcept
415 AudioUnitEvent
event {};
416 event.mEventType = type;
417 event.mArgument.mParameter = AudioUnitParameter {
418 instance, hash32(P::parameters[
static_cast<size_t>(index)].
id),
419 kAudioUnitScope_Global, 0 };
420 AUEventListenerNotify(
nullptr,
nullptr, &event);
426 static void cbSetParam(
void* context,
int index,
double plain)
noexcept
428 auto* self =
static_cast<Plugin*
>(context);
429 const AudioUnitParameter parameter {
430 self->instance, hash32(P::parameters[
static_cast<size_t>(index)].
id),
431 kAudioUnitScope_Global, 0 };
432 const bool inGesture = self->editActive[
static_cast<size_t>(index)];
434 self->sendGesture(index, kAudioUnitEvent_BeginParameterChangeGesture);
435 AUParameterSet(
nullptr,
nullptr, ¶meter,
436 static_cast<AudioUnitParameterValue
>(plain), 0);
438 self->sendGesture(index, kAudioUnitEvent_EndParameterChangeGesture);
441 static void cbBeginEdit(
void* context,
int index)
noexcept
443 auto* self =
static_cast<Plugin*
>(context);
444 self->editActive[
static_cast<size_t>(index)] =
true;
445 self->sendGesture(index, kAudioUnitEvent_BeginParameterChangeGesture);
448 static void cbEndEdit(
void* context,
int index)
noexcept
450 auto* self =
static_cast<Plugin*
>(context);
451 self->editActive[
static_cast<size_t>(index)] =
false;
452 self->sendGesture(index, kAudioUnitEvent_EndParameterChangeGesture);
455 static void sOnContainerDealloc(
void* context)
noexcept
457 auto* self =
static_cast<Plugin*
>(context);
458 self->editorContainer =
nullptr;
459 self->editor.destroy();
464 void teardownEditor()
noexcept
466 if (editorContainer !=
nullptr)
468 Class cls = editorContainerClass();
469 if (Ivar ivar = cls !=
nullptr
470 ? class_getInstanceVariable(cls,
"dsparkSink") :
nullptr)
471 object_setIvar(
static_cast<id>(editorContainer), ivar,
nullptr);
472 editorContainer =
nullptr;
477 static void* sCreateEditorView(
void* context,
double width,
double height)
noexcept
479 return static_cast<Plugin*
>(context)->createEditorView(width, height);
487 void* createEditorView(
double,
double)
noexcept
489 namespace og = webview_ui::objc_glue;
491 Class cls = editorContainerClass();
492 if (cls ==
nullptr)
return nullptr;
493 const EditorSize logical = editorSizeOf<P>();
494 const og::Rect frame { 0.0, 0.0,
static_cast<double>(logical.width),
495 static_cast<double>(logical.height) };
496 og::ObjId view = og::call(og::call(
reinterpret_cast<og::ObjId
>(cls),
"alloc"),
497 "initWithFrame:", frame);
498 if (view ==
nullptr)
return nullptr;
499 const webview_ui::HostCallbacks callbacks {
500 this, &cbSetParam, &cbBeginEdit, &cbEndEdit
502 if (!editor.create(view, shadow, callbacks))
504 og::call<void>(view,
"release");
507 editorSink = { &sOnContainerDealloc,
this };
508 if (Ivar ivar = class_getInstanceVariable(cls,
"dsparkSink"))
509 object_setIvar(
static_cast<id>(view),
510 ivar,
reinterpret_cast<id>(&editorSink));
511 editorContainer = view;
512 editor.setBounds(logical.width, logical.height);
513 editor.setVisible(
true);
514 webview_ui::debugLog(
"au createEditorView %dx%d container=%p",
515 logical.width, logical.height, view);
516 return og::call(view,
"autorelease");
523 OSStatus initialize()
noexcept
528 if constexpr (HasOfflineMode<P>)
529 user.setOfflineRendering(offlineRender != 0);
531 pullL.assign(maxFrames, 0.0f);
532 pullR.assign(maxFrames, 0.0f);
533 dryL.assign(maxFrames, 0.0f);
534 dryR.assign(maxFrames, 0.0f);
535 if constexpr (HasSidechain<P>)
537 scL.assign(maxFrames, 0.0f);
538 scR.assign(maxFrames, 0.0f);
540 bypassMix = bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
541 if constexpr (HasLatency<P>)
542 cachedLatency = user.getLatency();
548 OSStatus uninitialize()
noexcept
554 OSStatus reset()
noexcept
556 if constexpr (HasReset<P>)
558 bypassMix = bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
564 void fillStreamFormat(AudioStreamBasicDescription& f)
const noexcept
566 std::memset(&f, 0,
sizeof(f));
567 f.mSampleRate = sampleRate;
568 f.mFormatID = kAudioFormatLinearPCM;
569 f.mFormatFlags =
static_cast<AudioFormatFlags
>(kAudioFormatFlagsNativeFloatPacked)
570 |
static_cast<AudioFormatFlags
>(kAudioFormatFlagIsNonInterleaved);
571 f.mBytesPerPacket =
sizeof(float);
572 f.mFramesPerPacket = 1;
573 f.mBytesPerFrame =
sizeof(float);
574 f.mChannelsPerFrame =
static_cast<UInt32
>(currentChannels);
575 f.mBitsPerChannel = 32;
578 bool acceptableFormat(
const AudioStreamBasicDescription& f)
const noexcept
580 return f.mFormatID == kAudioFormatLinearPCM
581 && (f.mFormatFlags & kAudioFormatFlagIsFloat) != 0
582 && (f.mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0
583 && supportsChannelCount<P>(
static_cast<int>(f.mChannelsPerFrame))
584 && f.mBitsPerChannel == 32
585 && f.mSampleRate > 0.0;
590 static constexpr int numChannelConfigs()
noexcept
592 return channelSupportOf<P>() == ChannelSupport::MonoAndStereo ? 2 : 1;
597 void buildPresetArray()
noexcept
599 if constexpr (kNumPresets > 0)
601 if (presetArray !=
nullptr)
return;
602 presetStorage.resize(
static_cast<size_t>(kNumPresets));
603 const void* pointers[kNumPresets == 0 ? 1 : kNumPresets];
604 for (
int i = 0; i < kNumPresets; ++i)
606 presetStorage[
static_cast<size_t>(i)].presetNumber = i;
607 presetStorage[
static_cast<size_t>(i)].presetName =
608 CFStringCreateWithCString(
609 nullptr, P::factoryPresets[
static_cast<size_t>(i)].name,
610 kCFStringEncodingUTF8);
611 pointers[i] = &presetStorage[
static_cast<size_t>(i)];
613 presetArray = CFArrayCreate(
nullptr, pointers,
614 static_cast<CFIndex
>(kNumPresets),
nullptr);
618 OSStatus getPropertyInfo(AudioUnitPropertyID
id, AudioUnitScope scope,
619 AudioUnitElement element, UInt32* outSize,
620 Boolean* outWritable)
noexcept
623 Boolean writable =
false;
626 case kAudioUnitProperty_StreamFormat:
627 size =
sizeof(AudioStreamBasicDescription); writable =
true;
break;
628 case kAudioUnitProperty_SampleRate:
629 size =
sizeof(Float64); writable =
true;
break;
630 case kAudioUnitProperty_MaximumFramesPerSlice:
631 size =
sizeof(UInt32); writable =
true;
break;
632 case kAudioUnitProperty_ElementCount:
633 size =
sizeof(UInt32);
break;
634 case kAudioUnitProperty_SupportedNumChannels:
635 size =
static_cast<UInt32
>(numChannelConfigs() *
sizeof(AUChannelInfo));
637 case kAudioUnitProperty_HostCallbacks:
638 size =
sizeof(HostCallbackInfo); writable =
true;
break;
639 case kAudioUnitProperty_OfflineRender:
640 size =
sizeof(UInt32); writable =
true;
break;
641 case kAudioUnitProperty_FactoryPresets:
642 if (kNumPresets == 0)
return kAudioUnitErr_InvalidProperty;
643 size =
sizeof(CFArrayRef);
645 case kAudioUnitProperty_ParameterList:
646 size = scope == kAudioUnitScope_Global
647 ?
static_cast<UInt32
>((kNumParams + 1) *
sizeof(AudioUnitParameterID))
650 case kAudioUnitProperty_ParameterInfo:
651 size =
sizeof(AudioUnitParameterInfo);
break;
652 case kAudioUnitProperty_Latency:
653 case kAudioUnitProperty_TailTime:
654 size =
sizeof(Float64);
break;
655 case kAudioUnitProperty_BypassEffect:
656 size =
sizeof(UInt32); writable =
true;
break;
657 case kAudioUnitProperty_ClassInfo:
658 size =
sizeof(CFPropertyListRef); writable =
true;
break;
659 case kAudioUnitProperty_SetRenderCallback:
660 size =
sizeof(AURenderCallbackStruct); writable =
true;
break;
661 case kAudioUnitProperty_MakeConnection:
662 size =
sizeof(AudioUnitConnection); writable =
true;
break;
663 case kAudioUnitProperty_InPlaceProcessing:
664 size =
sizeof(UInt32);
break;
665 case kAudioUnitProperty_PresentPreset:
666 case kAudioUnitProperty_CurrentPreset:
667 size =
sizeof(AUPreset); writable =
true;
break;
668#if defined(DSPARK_PLUGIN_WEBVIEW)
669 case kAudioUnitProperty_CocoaUI:
670 if constexpr (!kHasWebEditor)
return kAudioUnitErr_InvalidProperty;
671 size =
sizeof(AudioUnitCocoaViewInfo);
673 case kDsparkEditorHookProperty:
674 if constexpr (!kHasWebEditor)
return kAudioUnitErr_InvalidProperty;
675 size =
sizeof(EditorHook);
680 return kAudioUnitErr_InvalidProperty;
682 if (outSize) *outSize = size;
683 if (outWritable) *outWritable = writable;
687 OSStatus getProperty(AudioUnitPropertyID
id, AudioUnitScope scope,
688 AudioUnitElement element,
void* outData,
689 UInt32* ioSize)
noexcept
691 if (ioSize ==
nullptr)
return kAudio_ParamError;
692 if (outData ==
nullptr)
695 return getPropertyInfo(
id, scope, element, ioSize, &w);
699 case kAudioUnitProperty_StreamFormat:
701 if (*ioSize <
sizeof(AudioStreamBasicDescription))
702 return kAudioUnitErr_InvalidPropertyValue;
703 fillStreamFormat(*
static_cast<AudioStreamBasicDescription*
>(outData));
704 *ioSize =
sizeof(AudioStreamBasicDescription);
707 case kAudioUnitProperty_SampleRate:
708 if (*ioSize <
sizeof(Float64))
return kAudioUnitErr_InvalidPropertyValue;
709 *
static_cast<Float64*
>(outData) = sampleRate;
710 *ioSize =
sizeof(Float64);
712 case kAudioUnitProperty_MaximumFramesPerSlice:
713 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
714 *
static_cast<UInt32*
>(outData) = maxFrames;
715 *ioSize =
sizeof(UInt32);
717 case kAudioUnitProperty_ElementCount:
718 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
719 *
static_cast<UInt32*
>(outData) =
720 scope == kAudioUnitScope_Input ? kNumInputElements : 1;
721 *ioSize =
sizeof(UInt32);
723 case kAudioUnitProperty_SupportedNumChannels:
726 static_cast<UInt32
>(numChannelConfigs() *
sizeof(AUChannelInfo));
727 if (*ioSize < want)
return kAudioUnitErr_InvalidPropertyValue;
728 auto* info =
static_cast<AUChannelInfo*
>(outData);
731 if (supportsChannelCount<P>(1))
733 info[slot].inChannels = kIsInstrument ? 0 : 1;
734 info[slot].outChannels = 1;
737 if (supportsChannelCount<P>(2))
739 info[slot].inChannels = kIsInstrument ? 0 : 2;
740 info[slot].outChannels = 2;
746 case kAudioUnitProperty_HostCallbacks:
747 if (*ioSize <
sizeof(HostCallbackInfo))
748 return kAudioUnitErr_InvalidPropertyValue;
749 *
static_cast<HostCallbackInfo*
>(outData) = hostCallbacks;
750 *ioSize =
sizeof(HostCallbackInfo);
752 case kAudioUnitProperty_OfflineRender:
753 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
754 *
static_cast<UInt32*
>(outData) = offlineRender;
755 *ioSize =
sizeof(UInt32);
757 case kAudioUnitProperty_FactoryPresets:
759 if (kNumPresets == 0)
return kAudioUnitErr_InvalidProperty;
760 if (*ioSize <
sizeof(CFArrayRef))
761 return kAudioUnitErr_InvalidPropertyValue;
763 if (presetArray ==
nullptr)
return kAudioUnitErr_InvalidProperty;
764 *
static_cast<CFArrayRef*
>(outData) =
765 static_cast<CFArrayRef
>(CFRetain(presetArray));
766 *ioSize =
sizeof(CFArrayRef);
769 case kAudioUnitProperty_ParameterList:
771 if (scope != kAudioUnitScope_Global)
777 static_cast<UInt32
>((kNumParams + 1) *
sizeof(AudioUnitParameterID));
778 if (*ioSize < want)
return kAudioUnitErr_InvalidPropertyValue;
779 auto* ids =
static_cast<AudioUnitParameterID*
>(outData);
780 for (
size_t i = 0; i < kNumParams; ++i)
781 ids[i] = hash32(P::parameters[i].
id);
782 ids[kNumParams] = kBypassParamId;
786 case kAudioUnitProperty_ParameterInfo:
788 if (*ioSize <
sizeof(AudioUnitParameterInfo))
789 return kAudioUnitErr_InvalidPropertyValue;
790 auto* info =
static_cast<AudioUnitParameterInfo*
>(outData);
791 std::memset(info, 0,
sizeof(*info));
792 info->flags = kAudioUnitParameterFlag_IsReadable
793 | kAudioUnitParameterFlag_IsWritable
794 | kAudioUnitParameterFlag_HasCFNameString
795 | kAudioUnitParameterFlag_CFNameRelease;
796 if (element == kBypassParamId)
798 std::snprintf(info->name,
sizeof(info->name),
"Bypass");
799 info->cfNameString = CFStringCreateWithCString(
800 nullptr,
"Bypass", kCFStringEncodingUTF8);
801 info->unit = kAudioUnitParameterUnit_Boolean;
802 info->minValue = 0.0f;
803 info->maxValue = 1.0f;
804 info->defaultValue = 0.0f;
805 *ioSize =
sizeof(AudioUnitParameterInfo);
808 const int idx = indexOfParamId(element);
809 if (idx < 0)
return kAudioUnitErr_InvalidParameter;
810 const auto& spec = P::parameters[
static_cast<size_t>(idx)];
811 std::snprintf(info->name,
sizeof(info->name),
"%s", spec.name);
812 info->cfNameString = CFStringCreateWithCString(
813 nullptr, spec.name, kCFStringEncodingUTF8);
814 info->unit = spec.steps == 1 ? kAudioUnitParameterUnit_Boolean
815 : (std::strcmp(spec.unit,
"dB") == 0
816 ? kAudioUnitParameterUnit_Decibels
817 : kAudioUnitParameterUnit_Generic);
818 info->minValue = spec.minValue;
819 info->maxValue = spec.maxValue;
820 info->defaultValue = spec.defValue;
821 *ioSize =
sizeof(AudioUnitParameterInfo);
824 case kAudioUnitProperty_Latency:
825 if (*ioSize <
sizeof(Float64))
return kAudioUnitErr_InvalidPropertyValue;
826 *
static_cast<Float64*
>(outData) =
827 sampleRate > 0.0 ? cachedLatency / sampleRate : 0.0;
828 *ioSize =
sizeof(Float64);
830 case kAudioUnitProperty_TailTime:
832 if (*ioSize <
sizeof(Float64))
return kAudioUnitErr_InvalidPropertyValue;
834 if constexpr (HasTail<P>)
835 tail = user.getTailSeconds();
836 *
static_cast<Float64*
>(outData) = tail;
837 *ioSize =
sizeof(Float64);
840 case kAudioUnitProperty_BypassEffect:
841 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
842 *
static_cast<UInt32*
>(outData) =
843 bypass.load(std::memory_order_relaxed) ? 1 : 0;
844 *ioSize =
sizeof(UInt32);
846 case kAudioUnitProperty_InPlaceProcessing:
847 if (*ioSize <
sizeof(UInt32))
return kAudioUnitErr_InvalidPropertyValue;
848 *
static_cast<UInt32*
>(outData) = 1;
849 *ioSize =
sizeof(UInt32);
851 case kAudioUnitProperty_PresentPreset:
852 case kAudioUnitProperty_CurrentPreset:
854 if (*ioSize <
sizeof(AUPreset))
return kAudioUnitErr_InvalidPropertyValue;
855 auto* preset =
static_cast<AUPreset*
>(outData);
856 preset->presetNumber = currentPresetNumber;
857 preset->presetName = presetName !=
nullptr
858 ?
static_cast<CFStringRef
>(CFRetain(presetName))
859 : CFStringCreateWithCString(
nullptr,
"Untitled",
860 kCFStringEncodingUTF8);
861 *ioSize =
sizeof(AUPreset);
864 case kAudioUnitProperty_ClassInfo:
866 if (*ioSize <
sizeof(CFPropertyListRef))
867 return kAudioUnitErr_InvalidPropertyValue;
868 double norm[kNumParams == 0 ? 1 : kNumParams];
869 for (
size_t i = 0; i < kNumParams; ++i)
870 norm[i] = shadow[i].load(std::memory_order_relaxed);
871 const std::vector<uint8_t> blob = buildState(
872 user, norm, kNumParams,
873 kNumPresets > 0 ? currentPresetNumber : -1,
874 bypass.load(std::memory_order_relaxed) ? 1 : 0);
876 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
877 nullptr, 0, &kCFTypeDictionaryKeyCallBacks,
878 &kCFTypeDictionaryValueCallBacks);
879 auto putInt = [&](
const char* key, SInt32 v) {
880 CFNumberRef n = CFNumberCreate(
nullptr, kCFNumberSInt32Type, &v);
881 CFStringRef k = CFStringCreateWithCString(
nullptr, key,
882 kCFStringEncodingUTF8);
883 CFDictionarySetValue(dict, k, n);
887 putInt(
"version", 0);
888 putInt(
"type",
static_cast<SInt32
>(expectedType()));
889 putInt(
"subtype",
static_cast<SInt32
>(subtype));
890 putInt(
"manufacturer",
static_cast<SInt32
>(manufacturer));
891 CFStringRef nameKey = CFSTR(
"name");
892 CFStringRef nameVal = CFStringCreateWithCString(
893 nullptr, P::descriptor.name, kCFStringEncodingUTF8);
894 CFDictionarySetValue(dict, nameKey, nameVal);
896 CFDataRef data = CFDataCreate(
nullptr, blob.data(),
897 static_cast<CFIndex
>(blob.size()));
898 CFDictionarySetValue(dict, CFSTR(
"dspark-state"), data);
901 *
static_cast<CFPropertyListRef*
>(outData) = dict;
902 *ioSize =
sizeof(CFPropertyListRef);
905#if defined(DSPARK_PLUGIN_WEBVIEW)
906 case kAudioUnitProperty_CocoaUI:
908 if constexpr (!kHasWebEditor)
return kAudioUnitErr_InvalidProperty;
909 if (*ioSize <
sizeof(AudioUnitCocoaViewInfo))
910 return kAudioUnitErr_InvalidPropertyValue;
911 Class factory = editorFactoryClass();
912 CFURLRef url = copyOwningBundleUrl();
913 if (factory ==
nullptr || url ==
nullptr)
915 if (url !=
nullptr) CFRelease(url);
916 return kAudioUnitErr_InvalidProperty;
918 auto* viewInfo =
static_cast<AudioUnitCocoaViewInfo*
>(outData);
919 viewInfo->mCocoaAUViewBundleLocation = url;
920 viewInfo->mCocoaAUViewClass[0] = CFStringCreateWithCString(
921 nullptr, class_getName(factory), kCFStringEncodingUTF8);
922 *ioSize =
sizeof(AudioUnitCocoaViewInfo);
925 case kDsparkEditorHookProperty:
927 if constexpr (!kHasWebEditor)
return kAudioUnitErr_InvalidProperty;
928 if (*ioSize <
sizeof(EditorHook))
929 return kAudioUnitErr_InvalidPropertyValue;
930 *
static_cast<EditorHook*
>(outData) = EditorHook {
this, &sCreateEditorView };
931 *ioSize =
sizeof(EditorHook);
936 return kAudioUnitErr_InvalidProperty;
940 OSStatus setProperty(AudioUnitPropertyID
id, AudioUnitScope scope,
941 AudioUnitElement element,
const void* inData,
942 UInt32 inSize)
noexcept
946 case kAudioUnitProperty_StreamFormat:
948 if (inData ==
nullptr || inSize <
sizeof(AudioStreamBasicDescription))
949 return kAudioUnitErr_InvalidPropertyValue;
950 const auto* f =
static_cast<const AudioStreamBasicDescription*
>(inData);
951 if (!acceptableFormat(*f))
return kAudioUnitErr_FormatNotSupported;
952 sampleRate = f->mSampleRate;
955 currentChannels =
static_cast<int>(f->mChannelsPerFrame);
958 case kAudioUnitProperty_SampleRate:
959 if (inData ==
nullptr || inSize <
sizeof(Float64))
960 return kAudioUnitErr_InvalidPropertyValue;
961 sampleRate = *
static_cast<const Float64*
>(inData);
963 case kAudioUnitProperty_MaximumFramesPerSlice:
964 if (inData ==
nullptr || inSize <
sizeof(UInt32))
965 return kAudioUnitErr_InvalidPropertyValue;
966 maxFrames = *
static_cast<const UInt32*
>(inData);
967 notifyProperty(
id, scope, element);
969 case kAudioUnitProperty_PresentPreset:
970 case kAudioUnitProperty_CurrentPreset:
972 if (inData ==
nullptr || inSize <
sizeof(AUPreset))
973 return kAudioUnitErr_InvalidPropertyValue;
974 const auto* preset =
static_cast<const AUPreset*
>(inData);
975 if (preset->presetNumber >= 0)
978 if (preset->presetNumber >= kNumPresets)
979 return kAudioUnitErr_InvalidPropertyValue;
980 applyFactoryPresetIdx(preset->presetNumber);
982 if (presetName !=
nullptr) CFRelease(presetName);
983 presetName = preset->presetName !=
nullptr
984 ?
static_cast<CFStringRef
>(CFRetain(preset->presetName))
985 : CFStringCreateWithCString(
987 [&]() ->
const char* {
988 if constexpr (kNumPresets > 0)
989 return P::factoryPresets[
static_cast<size_t>(
990 preset->presetNumber)].name;
994 kCFStringEncodingUTF8);
995 notifyProperty(
id, scope, element);
998 currentPresetNumber = -1;
999 if (presetName !=
nullptr) CFRelease(presetName);
1000 presetName = preset->presetName !=
nullptr
1001 ?
static_cast<CFStringRef
>(CFRetain(preset->presetName))
1003 notifyProperty(
id, scope, element);
1006 case kAudioUnitProperty_HostCallbacks:
1008 if (inData ==
nullptr)
return kAudioUnitErr_InvalidPropertyValue;
1010 std::memset(&hostCallbacks, 0,
sizeof(hostCallbacks));
1011 std::memcpy(&hostCallbacks, inData,
1012 inSize <
sizeof(hostCallbacks) ? inSize
1013 :
sizeof(hostCallbacks));
1016 case kAudioUnitProperty_OfflineRender:
1017 if (inData ==
nullptr || inSize <
sizeof(UInt32))
1018 return kAudioUnitErr_InvalidPropertyValue;
1019 offlineRender = *
static_cast<const UInt32*
>(inData);
1020 if constexpr (HasOfflineMode<P>)
1021 user.setOfflineRendering(offlineRender != 0);
1023 case kAudioUnitProperty_BypassEffect:
1024 if (inData ==
nullptr || inSize <
sizeof(UInt32))
1025 return kAudioUnitErr_InvalidPropertyValue;
1026 bypass.store(*
static_cast<const UInt32*
>(inData) != 0,
1027 std::memory_order_relaxed);
1029 case kAudioUnitProperty_SetRenderCallback:
1034 constexpr UInt32 storable = kIsInstrument ? 2 : kNumInputElements;
1035 if (scope != kAudioUnitScope_Input || element >= storable
1036 || inData ==
nullptr || inSize <
sizeof(AURenderCallbackStruct))
1037 return kAudioUnitErr_InvalidPropertyValue;
1038 inputCallback[element] = *
static_cast<const AURenderCallbackStruct*
>(inData);
1039 inputConnection[element] =
nullptr;
1042 case kAudioUnitProperty_MakeConnection:
1044 if (inData ==
nullptr || inSize <
sizeof(AudioUnitConnection))
1045 return kAudioUnitErr_InvalidPropertyValue;
1046 const auto* c =
static_cast<const AudioUnitConnection*
>(inData);
1047 constexpr UInt32 storable = kIsInstrument ? 2 : kNumInputElements;
1048 if (c->destInputNumber >= storable)
1049 return kAudioUnitErr_InvalidPropertyValue;
1050 inputConnection[c->destInputNumber] = c->sourceAudioUnit;
1051 inputConnectionBus[c->destInputNumber] = c->sourceOutputNumber;
1052 inputCallback[c->destInputNumber] = {};
1055 case kAudioUnitProperty_ClassInfo:
1057 if (inData ==
nullptr || inSize <
sizeof(CFPropertyListRef))
1058 return kAudioUnitErr_InvalidPropertyValue;
1059 CFPropertyListRef plist = *
static_cast<const CFPropertyListRef*
>(inData);
1060 if (plist ==
nullptr || CFGetTypeID(plist) != CFDictionaryGetTypeID())
1061 return kAudioUnitErr_InvalidPropertyValue;
1062 auto dict =
static_cast<CFDictionaryRef
>(plist);
1063 auto data =
static_cast<CFDataRef
>(
1064 CFDictionaryGetValue(dict, CFSTR(
"dspark-state")));
1065 if (data ==
nullptr || CFGetTypeID(data) != CFDataGetTypeID())
1067 double norm[kNumParams == 0 ? 1 : kNumParams];
1068 for (
size_t i = 0; i < kNumParams; ++i)
1069 norm[i] = shadow[i].load(std::memory_order_relaxed);
1071 int bypassState = -1;
1072 if (applyState(user,
1073 CFDataGetBytePtr(data),
1074 static_cast<size_t>(CFDataGetLength(data)), norm,
1075 &program, &bypassState))
1077 for (
size_t i = 0; i < kNumParams; ++i)
1078 applyNormalized(
static_cast<int>(i), norm[i]);
1079 if (kNumPresets > 0 && program >= 0 && program < kNumPresets)
1080 currentPresetNumber = program;
1081 if (bypassState >= 0)
1082 bypass.store(bypassState != 0, std::memory_order_relaxed);
1088 return kAudioUnitErr_InvalidProperty;
1094 OSStatus getParameter(AudioUnitParameterID
id, AudioUnitScope scope,
1095 AudioUnitParameterValue* outValue)
noexcept
1097 if (scope != kAudioUnitScope_Global || outValue ==
nullptr)
1098 return kAudioUnitErr_InvalidParameter;
1099 if (
id == kBypassParamId)
1101 *outValue = bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
1104 const int idx = indexOfParamId(
id);
1105 if (idx < 0)
return kAudioUnitErr_InvalidParameter;
1106 *outValue =
static_cast<AudioUnitParameterValue
>(
1107 toPlain(P::parameters[
static_cast<size_t>(idx)],
1108 shadow[
static_cast<size_t>(idx)].load(std::memory_order_relaxed)));
1112 OSStatus setParameter(AudioUnitParameterID
id, AudioUnitScope scope,
1113 AudioUnitParameterValue value)
noexcept
1115 if (scope != kAudioUnitScope_Global)
return kAudioUnitErr_InvalidParameter;
1116 if (
id == kBypassParamId)
1118 bypass.store(value >= 0.5f, std::memory_order_relaxed);
1121 const int idx = indexOfParamId(
id);
1122 if (idx < 0)
return kAudioUnitErr_InvalidParameter;
1123 applyNormalized(idx,
1124 toNormalized(P::parameters[
static_cast<size_t>(idx)], value));
1132 OSStatus pushMidi(UInt32 status, UInt32 data1, UInt32 data2,
1133 UInt32 offsetFrame)
noexcept
1135 if constexpr (HasMidi<P>)
1138 ev.channel =
static_cast<uint8_t
>(status & 0x0Fu);
1139 ev.sampleOffset =
static_cast<int>(offsetFrame);
1140 const uint8_t d1 =
static_cast<uint8_t
>(data1 & 0x7Fu);
1141 const uint8_t d2 =
static_cast<uint8_t
>(data2 & 0x7Fu);
1142 switch (status & 0xF0u)
1145 ev.type = d2 > 0 ? MidiEvent::Type::NoteOn : MidiEvent::Type::NoteOff;
1147 ev.value =
static_cast<float>(d2) / 127.0f;
1150 ev.type = MidiEvent::Type::NoteOff;
1152 ev.value =
static_cast<float>(d2) / 127.0f;
1155 ev.type = MidiEvent::Type::PolyPressure;
1157 ev.value =
static_cast<float>(d2) / 127.0f;
1160 ev.type = MidiEvent::Type::ControlChange;
1162 ev.value =
static_cast<float>(d2) / 127.0f;
1165 ev.type = MidiEvent::Type::ChannelPressure;
1166 ev.value =
static_cast<float>(d1) / 127.0f;
1169 ev.type = MidiEvent::Type::PitchBend;
1170 ev.value = (
static_cast<float>((d2 << 7) | d1) - 8192.0f) / 8192.0f;
1175 const uint32_t tail = midiTail.load(std::memory_order_relaxed);
1176 const uint32_t next = (tail + 1) & (kMidiRingSize - 1);
1177 if (next == midiHead.load(std::memory_order_acquire))
1179 midiRing[tail] = ev;
1180 midiTail.store(next, std::memory_order_release);
1189 return kAudioUnitErr_InvalidProperty;
1194 void forwardTransport()
noexcept
1196 if constexpr (HasTransport<P>)
1198 TransportInfo info {};
1200 if (hostCallbacks.beatAndTempoProc !=
nullptr)
1202 Float64 beat = 0.0, tempo = 0.0;
1203 if (hostCallbacks.beatAndTempoProc(hostCallbacks.hostUserData,
1204 &beat, &tempo) == noErr)
1208 info.tempoBpm = tempo;
1209 info.tempoValid =
true;
1211 info.ppqPosition = beat;
1212 info.positionValid =
true;
1216 if (hostCallbacks.musicalTimeLocationProc !=
nullptr)
1218 UInt32 deltaToNextBeat = 0;
1221 Float64 downbeat = 0.0;
1222 if (hostCallbacks.musicalTimeLocationProc(
1223 hostCallbacks.hostUserData, &deltaToNextBeat, &num, &den,
1224 &downbeat) == noErr)
1226 info.timeSigNumerator =
static_cast<int>(num);
1227 info.timeSigDenominator =
static_cast<int>(den);
1228 info.timeSigValid =
true;
1229 info.barStartPpq = downbeat;
1233 if (hostCallbacks.transportStateProc !=
nullptr)
1235 Boolean isPlaying =
false, changed =
false, isCycling =
false;
1236 Float64 samplePos = 0.0, cycleStart = 0.0, cycleEnd = 0.0;
1237 if (hostCallbacks.transportStateProc(
1238 hostCallbacks.hostUserData, &isPlaying, &changed,
1239 &samplePos, &isCycling, &cycleStart, &cycleEnd) == noErr)
1241 info.playing = isPlaying;
1242 info.looping = isCycling;
1243 info.loopStartPpq = cycleStart;
1244 info.loopEndPpq = cycleEnd;
1245 info.loopValid = isCycling;
1250 user.setTransport(info);
1259 OSStatus pullInput(UInt32 element,
float* dst[2],
1260 const AudioTimeStamp* timeStamp, UInt32 frames)
noexcept
1266 struct StereoBufferList
1268 UInt32 mNumberBuffers;
1269 ::AudioBuffer mBuffers[2];
1271 const UInt32 width =
static_cast<UInt32
>(currentChannels) < 2u
1272 ?
static_cast<UInt32
>(currentChannels) : 2u;
1273 list.mNumberBuffers = width;
1274 for (UInt32 ch = 0; ch < width; ++ch)
1276 list.mBuffers[ch].mNumberChannels = 1;
1277 list.mBuffers[ch].mDataByteSize = frames *
sizeof(float);
1278 list.mBuffers[ch].mData = dst[ch];
1280 auto* abl =
reinterpret_cast<AudioBufferList*
>(&list);
1281 OSStatus status = noErr;
1282 AudioUnitRenderActionFlags flags = 0;
1283 if (inputCallback[element].inputProc !=
nullptr)
1284 status = inputCallback[element].inputProc(
1285 inputCallback[element].inputProcRefCon, &flags, timeStamp,
1286 element, frames, abl);
1287 else if (inputConnection[element] !=
nullptr)
1288 status = AudioUnitRender(inputConnection[element], &flags, timeStamp,
1289 inputConnectionBus[element], frames, abl);
1291 for (UInt32 ch = 0; ch < width; ++ch)
1292 std::memset(dst[ch], 0, frames *
sizeof(
float));
1293 if (status != noErr)
return status;
1294 for (UInt32 ch = 0; ch < width && ch < list.mNumberBuffers; ++ch)
1295 if (list.mBuffers[ch].mData !=
nullptr)
1296 dst[ch] =
static_cast<float*
>(list.mBuffers[ch].mData);
1300 OSStatus render(AudioUnitRenderActionFlags* ioFlags,
1301 const AudioTimeStamp* timeStamp, UInt32 busNumber,
1302 UInt32 frames, AudioBufferList* ioData)
noexcept
1305 if (ioData ==
nullptr || frames == 0)
return noErr;
1306 if (!initialized)
return kAudioUnitErr_Uninitialized;
1307 if (frames > maxFrames)
return kAudioUnitErr_TooManyFramesToProcess;
1317 BlockEvent events[kMaxBlockEvents];
1319 for (
int i = 0; i < scheduledCount; ++i)
1321 BlockEvent ev = scheduled[i];
1322 if (ev.offset >=
static_cast<int32_t
>(frames))
1323 ev.offset =
static_cast<int32_t
>(frames) - 1;
1324 if (ev.offset < 0) ev.offset = 0;
1325 events[eventCount++] = ev;
1328 if constexpr (HasMidi<P>)
1330 uint32_t head = midiHead.load(std::memory_order_relaxed);
1331 const uint32_t tail = midiTail.load(std::memory_order_acquire);
1332 while (head != tail)
1335 ev.kind = BlockEvent::Kind::Midi;
1336 ev.midi = midiRing[head];
1337 ev.offset = ev.midi.sampleOffset;
1338 if (ev.offset >=
static_cast<int32_t
>(frames))
1339 ev.offset =
static_cast<int32_t
>(frames) - 1;
1340 if (ev.offset < 0) ev.offset = 0;
1341 if (eventCount < kMaxBlockEvents) events[eventCount++] = ev;
1342 else events[kMaxBlockEvents - 1] = ev;
1343 head = (head + 1) & (kMidiRingSize - 1);
1345 midiHead.store(head, std::memory_order_release);
1347 sortBlockEvents(events, eventCount);
1349 const UInt32 width =
static_cast<UInt32
>(currentChannels);
1350 const UInt32 outCh = ioData->mNumberBuffers < width
1351 ? ioData->mNumberBuffers : width;
1356 float* pull[2] = { pullL.data(), pullR.data() };
1357 if constexpr (!kIsInstrument)
1359 const OSStatus pullStatus = pullInput(0, pull, timeStamp, frames);
1360 if (pullStatus != noErr)
return pullStatus;
1363 float* out[2] = {
nullptr,
nullptr };
1364 for (UInt32 ch = 0; ch < outCh; ++ch)
1366 if (ioData->mBuffers[ch].mData ==
nullptr)
1367 ioData->mBuffers[ch].mData = pull[ch];
1368 out[ch] =
static_cast<float*
>(ioData->mBuffers[ch].mData);
1369 ioData->mBuffers[ch].mDataByteSize = frames *
sizeof(float);
1374 float* dry[2] = { dryL.data(), dryR.data() };
1375 for (UInt32 ch = 0; ch < outCh; ++ch)
1379 std::memset(out[ch], 0, frames *
sizeof(
float));
1382 std::memcpy(dry[ch], pull[ch], frames *
sizeof(
float));
1383 if (out[ch] != pull[ch])
1384 std::memcpy(out[ch], pull[ch], frames *
sizeof(
float));
1389 float* sc[2] = {
nullptr,
nullptr };
1390 if constexpr (HasSidechain<P>)
1394 if (pullInput(1, sc, timeStamp, frames) != noErr)
1398 std::memset(sc[0], 0, frames *
sizeof(
float));
1399 std::memset(sc[1], 0, frames *
sizeof(
float));
1405 auto applyEvent = [&](
const BlockEvent& ev,
int blockStart)
noexcept ->
bool {
1408 case BlockEvent::Kind::Midi:
1409 if constexpr (HasMidi<P>)
1411 MidiEvent midi = ev.midi;
1412 midi.sampleOffset = ev.offset - blockStart;
1413 if (midi.sampleOffset < 0) midi.sampleOffset = 0;
1414 user.handleMidiEvent(midi);
1417 case BlockEvent::Kind::Param:
1419 if (
const int idx = indexOfParamId(ev.paramId); idx >= 0)
1421 applyNormalized(idx, ev.value);
1427 auto processSegment = [&](
int start,
int length)
noexcept {
1428 float* sub[2] = { out[0] + start,
1429 outCh > 1 ? out[1] + start : out[0] + start };
1432 if constexpr (HasSidechain<P>)
1435 float* scSub[2] = { sc[0] + start, sc[1] + start };
1437 static_cast<int>(outCh), length);
1438 user.processBlock(view, scView);
1441 user.processBlock(view);
1444 bool paramsChanged =
false;
1445 const int total =
static_cast<int>(frames);
1447 if (!sampleAccurateOf<P>())
1449 for (; evIdx < eventCount; ++evIdx)
1450 paramsChanged |= applyEvent(events[evIdx], 0);
1451 processSegment(0, total);
1458 while (evIdx < eventCount
1459 && (events[evIdx].offset / kAutomationQuantum)
1460 * kAutomationQuantum <= pos)
1461 paramsChanged |= applyEvent(events[evIdx++], pos);
1463 if (evIdx < eventCount)
1465 const int snapped = (events[evIdx].offset / kAutomationQuantum)
1466 * kAutomationQuantum;
1467 if (snapped < next) next = snapped;
1469 if (next <= pos) next = pos + kAutomationQuantum < total
1470 ? pos + kAutomationQuantum : total;
1471 processSegment(pos, next - pos);
1474 for (; evIdx < eventCount; ++evIdx)
1475 paramsChanged |= applyEvent(events[evIdx], total);
1478 const float target = bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
1479 if (bypassMix != target || target > 0.0f)
1481 const float step = 1.0f /
static_cast<float>(kBypassRampSamples);
1482 float mix = bypassMix;
1483 for (UInt32 i = 0; i < frames; ++i)
1485 mix += (target > mix) ? step : ((target < mix) ? -step : 0.0f);
1486 mix = mix < 0.0f ? 0.0f : (mix > 1.0f ? 1.0f : mix);
1487 for (UInt32 ch = 0; ch < outCh; ++ch)
1488 out[ch][i] += (dry[ch][i] - out[ch][i]) * mix;
1493 if (paramsChanged) refreshLatency();
1502template <
typename P>
1505 AudioComponentPlugInInterface iface {};
1506 Plugin<P>* state =
nullptr;
1508 static Component* fromSelf(
void* self)
noexcept
1510 return static_cast<Component*
>(self);
1513 static OSStatus sOpen(
void* self, AudioComponentInstance instance)
noexcept
1515 auto* c = fromSelf(self);
1516 c->state =
new (std::nothrow) Plugin<P>();
1517 if (c->state ==
nullptr)
return kAudio_MemFullError;
1518 c->state->instance = instance;
1519 c->state->subtype = gSubtype;
1520 c->state->manufacturer = gManufacturer;
1524 static OSStatus sClose(
void* self)
noexcept
1526 auto* c = fromSelf(self);
1534 static OSStatus sInitialize(
void* self)
noexcept
1535 {
return fromSelf(self)->state->initialize(); }
1537 static OSStatus sUninitialize(
void* self)
noexcept
1538 {
return fromSelf(self)->state->uninitialize(); }
1540 static OSStatus sGetPropertyInfo(
void* self, AudioUnitPropertyID
id,
1541 AudioUnitScope scope, AudioUnitElement element,
1542 UInt32* outSize, Boolean* outWritable)
noexcept
1543 {
return fromSelf(self)->state->getPropertyInfo(
id, scope, element, outSize, outWritable); }
1545 static OSStatus sGetProperty(
void* self, AudioUnitPropertyID
id,
1546 AudioUnitScope scope, AudioUnitElement element,
1547 void* outData, UInt32* ioSize)
noexcept
1548 {
return fromSelf(self)->state->getProperty(
id, scope, element, outData, ioSize); }
1550 static OSStatus sSetProperty(
void* self, AudioUnitPropertyID
id,
1551 AudioUnitScope scope, AudioUnitElement element,
1552 const void* inData, UInt32 inSize)
noexcept
1553 {
return fromSelf(self)->state->setProperty(
id, scope, element, inData, inSize); }
1555 static OSStatus sGetParameter(
void* self, AudioUnitParameterID
id,
1556 AudioUnitScope scope, AudioUnitElement element,
1557 AudioUnitParameterValue* outValue)
noexcept
1560 return fromSelf(self)->state->getParameter(
id, scope, outValue);
1563 static OSStatus sSetParameter(
void* self, AudioUnitParameterID
id,
1564 AudioUnitScope scope, AudioUnitElement element,
1565 AudioUnitParameterValue value,
1566 UInt32 bufferOffset)
noexcept
1569 (void) bufferOffset;
1570 return fromSelf(self)->state->setParameter(
id, scope, value);
1573 static OSStatus sReset(
void* self, AudioUnitScope scope,
1574 AudioUnitElement element)
noexcept
1578 return fromSelf(self)->state->reset();
1581 static OSStatus sRender(
void* self, AudioUnitRenderActionFlags* ioFlags,
1582 const AudioTimeStamp* timeStamp, UInt32 busNumber,
1583 UInt32 frames, AudioBufferList* ioData)
noexcept
1584 {
return fromSelf(self)->state->render(ioFlags, timeStamp, busNumber, frames, ioData); }
1586 static OSStatus sAddPropertyListener(
void* self, AudioUnitPropertyID
id,
1587 AudioUnitPropertyListenerProc proc,
1588 void* user)
noexcept
1590 auto* state = fromSelf(self)->state;
1591 state->listeners.push_back({ id, proc, user });
1595 static OSStatus sRemovePropertyListener(
void* self, AudioUnitPropertyID
id,
1596 AudioUnitPropertyListenerProc proc)
noexcept
1598 auto& ls = fromSelf(self)->state->listeners;
1599 for (
size_t i = ls.size(); i > 0; --i)
1600 if (ls[i - 1].
id ==
id && ls[i - 1].proc == proc)
1601 ls.erase(ls.begin() +
static_cast<ptrdiff_t
>(i - 1));
1605 static OSStatus sRemovePropertyListenerWithUserData(
void* self, AudioUnitPropertyID
id,
1606 AudioUnitPropertyListenerProc proc,
1607 void* user)
noexcept
1609 auto& ls = fromSelf(self)->state->listeners;
1610 for (
size_t i = ls.size(); i > 0; --i)
1611 if (ls[i - 1].
id ==
id && ls[i - 1].proc == proc && ls[i - 1].user == user)
1612 ls.erase(ls.begin() +
static_cast<ptrdiff_t
>(i - 1));
1616 static OSStatus sAddRenderNotify(
void*, AURenderCallback,
void*)
noexcept
1619 static OSStatus sRemoveRenderNotify(
void*, AURenderCallback,
void*)
noexcept
1626 static OSStatus sScheduleParameters(
void* self,
1627 const AudioUnitParameterEvent* events,
1628 UInt32 numEvents)
noexcept
1630 auto* state = fromSelf(self)->state;
1631 auto push = [&](AudioUnitParameterID id, AudioUnitScope scope,
1632 float plain, SInt32 offset) {
1633 if (scope != kAudioUnitScope_Global)
return;
1634 if (
id == kBypassParamId)
1636 state->bypass.store(plain >= 0.5f, std::memory_order_relaxed);
1639 const int idx = Plugin<P>::indexOfParamId(
id);
1640 if (idx < 0 || state->scheduledCount >= kMaxBlockEvents)
return;
1642 ev.offset = offset < 0 ? 0 : offset;
1643 ev.kind = BlockEvent::Kind::Param;
1645 ev.value = toNormalized(P::parameters[
static_cast<size_t>(idx)], plain);
1646 state->scheduled[state->scheduledCount++] = ev;
1648 for (UInt32 i = 0; events !=
nullptr && i < numEvents; ++i)
1650 const auto& ev = events[i];
1651 if (ev.eventType == kParameterEvent_Immediate)
1652 push(ev.parameter, ev.scope, ev.eventValues.immediate.value,
1653 static_cast<SInt32
>(ev.eventValues.immediate.bufferOffset));
1654 else if (ev.eventType == kParameterEvent_Ramped)
1656 push(ev.parameter, ev.scope, ev.eventValues.ramp.startValue,
1657 ev.eventValues.ramp.startBufferOffset);
1658 push(ev.parameter, ev.scope, ev.eventValues.ramp.endValue,
1659 ev.eventValues.ramp.startBufferOffset
1660 +
static_cast<SInt32
>(ev.eventValues.ramp.durationInFrames));
1668 static OSStatus sMidiEvent(
void* self, UInt32 status, UInt32 data1,
1669 UInt32 data2, UInt32 offsetSampleFrame)
noexcept
1671 return fromSelf(self)->state->pushMidi(status, data1, data2,
1675 static OSStatus sSysEx(
void* self,
const UInt8*, UInt32)
noexcept
1681 static AudioComponentMethod sLookup(SInt16 selector)
noexcept
1683 if constexpr (HasMidi<P>)
1685 if (selector == kMusicDeviceMIDIEventSelect)
1686 return reinterpret_cast<AudioComponentMethod
>(&sMidiEvent);
1687 if (selector == kMusicDeviceSysExSelect)
1688 return reinterpret_cast<AudioComponentMethod
>(&sSysEx);
1692 case kAudioUnitInitializeSelect:
1693 return reinterpret_cast<AudioComponentMethod
>(&sInitialize);
1694 case kAudioUnitUninitializeSelect:
1695 return reinterpret_cast<AudioComponentMethod
>(&sUninitialize);
1696 case kAudioUnitGetPropertyInfoSelect:
1697 return reinterpret_cast<AudioComponentMethod
>(&sGetPropertyInfo);
1698 case kAudioUnitGetPropertySelect:
1699 return reinterpret_cast<AudioComponentMethod
>(&sGetProperty);
1700 case kAudioUnitSetPropertySelect:
1701 return reinterpret_cast<AudioComponentMethod
>(&sSetProperty);
1702 case kAudioUnitGetParameterSelect:
1703 return reinterpret_cast<AudioComponentMethod
>(&sGetParameter);
1704 case kAudioUnitSetParameterSelect:
1705 return reinterpret_cast<AudioComponentMethod
>(&sSetParameter);
1706 case kAudioUnitResetSelect:
1707 return reinterpret_cast<AudioComponentMethod
>(&sReset);
1708 case kAudioUnitRenderSelect:
1709 return reinterpret_cast<AudioComponentMethod
>(&sRender);
1710 case kAudioUnitAddPropertyListenerSelect:
1711 return reinterpret_cast<AudioComponentMethod
>(&sAddPropertyListener);
1712 case kAudioUnitRemovePropertyListenerSelect:
1713 return reinterpret_cast<AudioComponentMethod
>(&sRemovePropertyListener);
1714 case kAudioUnitRemovePropertyListenerWithUserDataSelect:
1715 return reinterpret_cast<AudioComponentMethod
>(&sRemovePropertyListenerWithUserData);
1716 case kAudioUnitAddRenderNotifySelect:
1717 return reinterpret_cast<AudioComponentMethod
>(&sAddRenderNotify);
1718 case kAudioUnitRemoveRenderNotifySelect:
1719 return reinterpret_cast<AudioComponentMethod
>(&sRemoveRenderNotify);
1720 case kAudioUnitScheduleParametersSelect:
1721 return reinterpret_cast<AudioComponentMethod
>(&sScheduleParameters);
1727 inline static OSType gSubtype = 0;
1728 inline static OSType gManufacturer = 0;
1730 static void* factory(
const AudioComponentDescription* desc)
noexcept
1734 if (desc !=
nullptr && desc->componentType != Plugin<P>::expectedType())
1736 auto* c =
new (std::nothrow) Component();
1737 if (c ==
nullptr)
return nullptr;
1738 c->iface.Open = &sOpen;
1739 c->iface.Close = &sClose;
1740 c->iface.Lookup = &sLookup;
1741 c->iface.reserved =
nullptr;
1754#define DSPARK_AU_PLUGIN(PluginClass, subtype4, manufacturer4) \
1755 extern "C" __attribute__((visibility("default"))) \
1756 void* DSParkAuFactory(const AudioComponentDescription* desc) \
1758 using Comp = dspark::plugin::au::Component<PluginClass>; \
1759 Comp::gSubtype = dspark::plugin::au::fourCC(subtype4); \
1760 Comp::gManufacturer = dspark::plugin::au::fourCC(manufacturer4); \
1761 return Comp::factory(desc); \
1767#define DSPARK_AU_PLUGIN(PluginClass, subtype4, manufacturer4)
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Describes the audio environment for a DSP processor.