Skip to content

Instantly share code, notes, and snippets.

@wrl
Created August 5, 2020 16:35
Show Gist options
  • Save wrl/3806a344ec2f034697c66e6696ffd6cf to your computer and use it in GitHub Desktop.
Save wrl/3806a344ec2f034697c66e6696ffd6cf to your computer and use it in GitHub Desktop.

getting started

These are my notes for implementing AUv2 support in Cadmium. I've made them public in the hopes that they could be useful.

AUv2, like VST2, is a C linkage ABI/API with a slew of C++ helper code on top constituting the official public interface. It differs from VST2 in that there is a lot more C++ helper code (among other details which we will visit later on in this document). Of note is that we will be targetting the underlying C ABI/API instead of the public interface. We are doing this both as a learning exercise and in an effort to reduce overhead in our final bindings.

As a first aside: The docs are bad. Like, real bad. In general, the comments in the AudioToolbox headers are the most consistently good documentation source I can find, with the Core Audio Utility Classes being somewhat behind those, and the Apple documentation website being a distant last.

The Core Audio Utility Classes can be downloaded directly from Apple. AudioToolbox is a framework inside of the MacOS SDK, which needs to be obtained from an Xcode installation (and unfortunately cannot be redistributed due to license terms).

ComponentManager?

There are two available component interfaces:

  • ComponentManager (which predates macOS 10 and which Apple has unsuccessfully attempted to deprecate once already)
  • AudioComponent (which was introduced in 10.7)

We're not even going to look at the ComponentManager interface. It's a mess of legacy code and basically all you get from it at this point is support for pre-10.7.

This simplifies our work considerably, because otherwise our code must support two separate dispatch methods.

AudioComponent Interface

An AudioComponent is a loadable bundle with the extension .component (e.g. cadmium.component). The AudioComponent interface involves defining a factory function, exporting the symbol, and specifying the name of the symbol in the bundle's Info.plist.

To follow along, open the AudioToolbox AudioComponent.h header.

The type of the factory function is AudioComponentFactoryFunction, and a valid definition for said function type is as follows:

#include <AudioUnit/AudioComponent.h>

AudioComponentPlugInInterface *auv2_factory(const AudioComponentDescription *);

The AudioComponentDescription passed to the factory makes it possible to have more than one AudioComponent in the same bundle, differentiated by calling the factory function with different AudioComponentDescription structures. At least, I assume this is the case. I have yet to find any source code demonstrating this. Note that this is roughly what VST2 shell plugins accomplish.

The AudioComponentDescription has decent documentation on the Apple site, but in essence comprises of the componentManufacturer, the componentSubType, and the componentType, all three of which come from the AudioComponent plist block.

An example AudioComponent plist block looks as follows:

<key>AudioComponents</key>
<array>
    <dict>
        <key>manufacturer</key>
        <string>LHIA</string>
        <key>name</key>
        <string>LHI Audio cadmium</string>
        <key>description</key>
        <string>LHI Audio cadmium: A vector phase-shaping synthesizer.</string>
        <key>factoryFunction</key>
        <string>cadmium_auv2_factory</string>
        <key>subtype</key>
        <string>cadm</string>
        <key>type</key>
        <string>augn</string>
        <key>version</key>
        <integer>65792</integer>
    </dict>
</array>
  • manufacturer

    A 4 letter manufacturer ID. Apple supposedly maintains a database of these and you're supposed to register, but I couldn't find a place to do so. Seems to be a vestige of the past and I don't know if anybody cares anymore.

  • name

    user-friendly name (i guess?) [XXX: where is this displayed?]

  • description

    don't know yet [XXX: where is this displayed?]

  • factoryFunction

    the symbol name of your factory function.

  • subtype

    4 letter product code. This is chiefly for differentiating between multiple plugins in the same bundle, as described above.

  • type

    • aufx: effect. takes audio in, produces audio out.

    • aumu: music device. takes MIDI in, produces audio out.

    • aumf: music effect. takes audio and MIDI in, produces audio out.

    There are more types specified in AudioToolbox AUComponent.h but who cares, really?

  • version

    #include <stdint.h>
    uint32_t
    make_version(int major, int minor, int patch) {
        return
              ((major & 0xFFFF) << 16)
            | ((minor & 0xFF)   << 8)
            |  (patch & 0xFF);
    }

    👍

Editor/View

In VST2, a plugin is passed a parent window (or NSView *) from the host, and the plugin opens its window (or NSView) and attaches it underneath. in AU, the plugin creates an NSView and returns a pointer to it to the host, which can then place it wherever it so chooses.

The host will request a property (GetProperty) of ID kAudioUnitProperty_CocoaUI, with output data a pointer to AudioUnitCocoaViewInfo, which specifies a bundle location and a class name. The class specified must implement the AUCocoaUIBase protocol, and it is through this protocol's uiViewForAudioUnit:withSize: method that the NSView * is returned to the host.

There are several more methods in AUCocoaUIBase that should also be implemented. Please refer to the AudioToolbox AUCocoaUIView.h header, which describes these in the comments.

A plugin can only know that its editor has been closed by getting a dealloc message on the NSView that was returned to the host, so that should be overridden.

/** .__
* ______ | | __ ______________ ______ ___________
* \____ \| | | | \_ __ \__ \ \____ \\____ \__ \
* | |_> > |_| | /| | \// __ \| |_> > |_> > __ \_
* | __/|____/____/ |__| (____ / __/| __(____ /
* |__| T H E W R A P P A \/|__| |__| \/
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <plur/plugin.h>
#include <plur/util.h>
#include <objc/runtime.h>
#include <CoreFoundation/CoreFoundation.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioUnitUtilities.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Cocoa/Cocoa.h>
#import <AudioUnit/AUCocoaUIView.h>
#undef MAX
#undef MIN
#if !defined(PLUR_NO_GUI) && (@PLUR_IFACE_HAS_UI@ == 0)
#define PLUR_NO_GUI 1
#endif
#define CSM_0 0
#include @PLUR_IFACE_HEADER@
#include <@PLUR_IFACE_INCLUDE_DIR@/params.h>
typedef @PLUR_IFACE_OBJECT@ plur_auv2_wrapped_plug_t;
#ifndef ARRAY_LENGTH
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(*a))
#endif
/**
* hacked up shit to avoid creating an API right now
*/
#define PLUR_PLUG_LATENCY 0.0
#define NUM_INPUT_CHANNELS @PLUR_INPUT_CHANNELS@
#define NUM_OUTPUT_CHANNELS @PLUR_OUTPUT_CHANNELS@
#define MAX_CHANNELS_EITHER_DIRECTION 2
#if NUM_INPUT_CHANNELS == 2
# define NUM_INPUT_BUSES 1
#else
# define NUM_INPUT_BUSES 0
#endif
#define NUM_OUTPUT_BUSES 1
#if @PLUR_MIDI_INPUT@ == 1
#define MIDI_INPUT
#endif
#if NUM_INPUT_BUSES > 1
#error "unsupported number of input buses"
#endif
/**
* custom AU properties
*/
#define PLUR_AUV2_AUPROP_WRAPPED_PLUG_PTR 0x4242426
/**
* types
*/
struct plur_auv2_buffer_list {
int mNumberBuffers;
AudioBuffer mBuffers[MAX_CHANNELS_EITHER_DIRECTION];
};
struct plur_auv2_input_bus {
int connected_channels;
struct {
AudioUnit upstream_au;
UInt32 upstream_output_elem;
} connection;
struct {
AURenderCallback proc;
void *ctx;
} render_cb;
};
struct plur_auv2_output_bus {
int connected_channels;
};
struct plur_auv2_prop_listener {
AudioUnitPropertyID prop_id;
AudioUnitPropertyListenerProc cb;
void *user_data;
};
struct plur_auv2_render_notify {
AURenderCallback cb;
void *user_data;
};
struct plur_auv2_wrapper {
AudioComponentPlugInInterface iface;
AudioUnit au_instance;
HostCallbackInfo host_callbacks;
uint32_t block_size;
float *scratch_buffer, *zero_buffer, *input_buffers, *output_buffers;
struct {
struct plur_auv2_input_bus input[NUM_INPUT_BUSES];
struct plur_auv2_output_bus output[NUM_OUTPUT_BUSES];
} bus;
VECTOR(plur_auv2_prop_listener_vec, struct plur_auv2_prop_listener)
prop_listeners;
VECTOR(plur_auv2_render_cb_list_vec, struct plur_auv2_render_notify)
render_notify;
unsigned bypass;
Class cocoa_ui_cls;
plur_auv2_wrapped_plug_t plug;
};
static struct plur_auv2_wrapper *
wrapper_from_plug(struct plur_plugin *plug)
{
plur_auv2_wrapped_plug_t *wp = PLUR_CONTAINER_OF(
plug, plur_auv2_wrapped_plug_t, PLUR_INHERITED_MEMBER(plur_plugin));
return PLUR_CONTAINER_OF(wp, struct plur_auv2_wrapper, plug);
}
/**
* plur templated types and functions
*/
static const struct {
struct plur_bus_metadata input[NUM_INPUT_BUSES];
struct plur_bus_metadata output[NUM_OUTPUT_BUSES];
} bus_metadata = {
.input = {
#if NUM_INPUT_BUSES == 1
{.channels = NUM_INPUT_CHANNELS}
#endif
},
.output = {
{.channels = NUM_OUTPUT_CHANNELS}
}
};
static const struct plur_param *
auv2_param_to_plur(AudioUnitParameterID param_id)
{
switch (param_id) {
@PLUR_AUV2_PARAM_MAP_CASES@
default: return NULL;
}
}
@PLUR_AUV2_CLUMP_NAME_CONSTANTS@
static CFStringRef
auv2_clump_name_for_id(uint32_t clump_id)
{
switch (clump_id) {
@PLUR_AUV2_CLUMP_TO_NAME_CASES@
default: return NULL;
}
}
static uint32_t
auv2_param_to_clump_id(AudioUnitParameterID param_id)
{
switch (param_id) {
@PLUR_AUV2_PARAM_TO_CLUMP_CASES@
default:
return 0;
}
}
@PLUR_AUV2_PARAM_VALUE_LISTS@
static ssize_t
auv2_value_list_for_param_id(AudioUnitParameterID param_id, const void **vals)
{
switch (param_id) {
@PLUR_AUV2_PARAM_VALUE_LIST_CASES@
default: return -1;
}
}
static const AudioUnitParameterID auv2_param_ids[] = {
@PLUR_AUV2_PARAM_ID_LIST@
};
static const CFStringRef auv2_param_name_cfstrs[] = {
@PLUR_AUV2_PARAM_NAME_CFSTRS@
};
static CFStringRef
auv2_param_name_map(AudioUnitParameterID param_id)
{
switch (param_id) {
@PLUR_AUV2_PARAM_NAME_CASES@
default: return NULL;
}
}
static const AUPreset plur_auv2_factory_presets[] = {
@PLUR_AUV2_FACTORY_PRESET_LIST@
};
/**
* scope helpers
*/
#define ASSERT_SCOPE(SCOPE) \
if (scope != SCOPE) { \
return kAudioUnitErr_InvalidProperty; \
}
#define ASSERT_GLOBAL_SCOPE() ASSERT_SCOPE(kAudioUnitScope_Global)
#define ASSERT_INPUT_SCOPE() ASSERT_SCOPE(kAudioUnitScope_Input)
#define ASSERT_INPUT_OR_GLOBAL_SCOPE() \
if (!(scope == kAudioUnitScope_Global \
|| scope == kAudioUnitScope_Input)) { \
return kAudioUnitErr_InvalidProperty; \
}
/**
* utilities and helpers
*/
static int
resize_scratch_buffer(struct plur_auv2_wrapper *wrapper)
{
unsigned i, input_ch, output_ch;
uint32_t block_size;
size_t need;
float *new;
input_ch = 0;
output_ch = 0;
block_size = wrapper->block_size;
for (i = 0; i < ARRAY_LENGTH(bus_metadata.input); i++)
input_ch += bus_metadata.input[i].channels;
for (i = 0; i < ARRAY_LENGTH(bus_metadata.output); i++)
output_ch += bus_metadata.output[i].channels;
// 2 extra: one silent (zero) buffer for unassigned inputs, and one scratch
// buffer for unbound outputs
need = input_ch + output_ch + 2;
need *= block_size * sizeof(*wrapper->scratch_buffer);
new = realloc(wrapper->scratch_buffer, need);
if (!new)
return 1;
wrapper->scratch_buffer = new;
wrapper->zero_buffer = &new[block_size];
wrapper->input_buffers = &new[block_size * 2];
wrapper->output_buffers = &new[block_size * (input_ch + 2)];
memset(wrapper->zero_buffer, '\0', sizeof(*new) * block_size);
return 0;
}
static float *
get_input_buffer(struct plur_auv2_wrapper *wrapper,
unsigned channel)
{
return &wrapper->input_buffers[channel * wrapper->block_size];
}
static float *
get_output_buffer(struct plur_auv2_wrapper *wrapper,
unsigned channel)
{
return &wrapper->output_buffers[channel * wrapper->block_size];
}
/**
* lifecycle methods
*/
static OSStatus
auv2_initialize(void *self)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
/* XXX: get sample rate */
plur_plug_resume(plug);
PLUR_TRACE(plug, " :: auv2_initialize()\n");
return noErr;
}
static OSStatus
auv2_uninitialize(void *self)
{
#ifdef PLUR_ENABLE_TRACE
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
PLUR_TRACE(plug, " :: auv2_uninitialize()\n");
#endif
return noErr;
}
/**
* parameter methods
*/
static AudioUnitParameterUnit
plur_param_unit_to_au(plur_param_unit_t unit)
{
switch (unit) {
#define MAP(plur, au) \
case PLUR_PARAM_UNIT_ ## plur: return (kAudioUnitParameterUnit_ ## au);
MAP(GENERIC, Generic);
MAP(BOOLEAN, Boolean);
MAP(ENUM_VALUES, Indexed);
MAP(PERCENT, Percent);
MAP(SECONDS, Seconds);
MAP(MILLISECONDS, Milliseconds);
MAP(DECIBELS, Decibels);
MAP(LINEAR_GAIN, LinearGain);
MAP(PAN, Pan);
MAP(DEGREES, Degrees);
MAP(RATIO, Ratio);
MAP(HZ, Hertz);
MAP(OCTAVES, Octaves);
MAP(SEMITONES, RelativeSemiTones);
MAP(CENTS, Cents);
MAP(BPM, BPM);
MAP(BEATS, Beats);
#undef MAP
}
return kAudioUnitParameterUnit_Generic;
}
static OSStatus
auv2_get_parameter(void *self, AudioUnitParameterID param_id,
AudioUnitScope scope, AudioUnitElement elem,
AudioUnitParameterValue *value)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
const struct plur_param *d;
const void *values;
ssize_t nvalues;
ASSERT_GLOBAL_SCOPE();
if (!(d = auv2_param_to_plur(param_id)))
return kAudioUnitErr_InvalidParameter;
*value = plur_param_get_value(plug, d);
// FIXME: need a better thing for this
nvalues = auv2_value_list_for_param_id(param_id, &values);
if (nvalues > 2)
*value *= (float) (nvalues - 1);
return noErr;
}
static OSStatus
auv2_set_parameter(void *self, AudioUnitParameterID param_id,
AudioUnitScope scope, AudioUnitElement elem,
AudioUnitParameterValue value, UInt32 buffer_offset)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
const struct plur_param *param;
const void *values;
ssize_t nvalues;
ASSERT_GLOBAL_SCOPE();
if (!(param = auv2_param_to_plur(param_id)))
return kAudioUnitErr_InvalidParameter;
// FIXME: need a better thing for this
nvalues = auv2_value_list_for_param_id(param_id, &values);
if (nvalues > 2)
value /= (float) (nvalues - 1);
plur_param_set(plug, param, value);
#ifndef PLUR_NO_GUI
plur_notification_param_was_set(plug, param, value);
#endif
return noErr;
}
static OSStatus
auv2_schedule_parameters(void *self, const AudioUnitParameterEvent *events,
UInt32 nevents)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
const struct plur_param *d;
const AudioUnitParameterEvent *ev;
unsigned i;
for (i = 0; i < nevents; i++) {
ev = &events[i];
if (!(d = auv2_param_to_plur(ev->parameter)))
return kAudioUnitErr_InvalidParameter;
switch (ev->eventType) {
case kParameterEvent_Immediate: {
plur_plug_enqueue_event(plug,
&((struct plur_event) {
.ev_type = PLUR_EVENT_TYPE_PARAM,
.frame_offset = ev->eventValues.immediate.bufferOffset,
.param = {
.descriptor = d,
.value = ev->eventValues.immediate.value
}
})
);
break;
}
case kParameterEvent_Ramped:
// ¯\_(ツ)_/¯
//
// perhaps emit an event for the beginning and also the end?
// i don't know.
break;
}
}
return noErr;
}
/**
* property listener methods
*/
static OSStatus
auv2_add_property_listener(void *self, AudioUnitPropertyID prop_id,
AudioUnitPropertyListenerProc listener_proc, void *user_data)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_auv2_prop_listener *pl;
size_t i, nlisteners;
nlisteners = wrapper->prop_listeners.size;
for (i = 0; i < nlisteners; i++) {
pl = &wrapper->prop_listeners.data[i];
if (pl->prop_id == prop_id && pl->cb == listener_proc)
return noErr;
}
VECTOR_PUSH_BACK(&wrapper->prop_listeners,
&((struct plur_auv2_prop_listener) {
.prop_id = prop_id,
.cb = listener_proc,
.user_data = user_data
}));
return noErr;
}
static OSStatus
auv2_remove_property_listener(void *self, AudioUnitPropertyID prop_id,
AudioUnitPropertyListenerProc listener_proc)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_auv2_prop_listener *pl;
size_t i, nlisteners;
nlisteners = wrapper->prop_listeners.size;
for (i = 0; i < nlisteners; i++) {
pl = &wrapper->prop_listeners.data[i];
if (pl->prop_id == prop_id && pl->cb == listener_proc) {
VECTOR_ERASE(&wrapper->prop_listeners, i);
return noErr;
}
}
// couldn't find it, return noErr??
return noErr;
}
static OSStatus
auv2_remove_property_listener_with_user_data(void *self,
AudioUnitPropertyID prop_id,
AudioUnitPropertyListenerProc listener_proc, void *user_data)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_auv2_prop_listener *pl;
size_t i, nlisteners;
nlisteners = wrapper->prop_listeners.size;
for (i = 0; i < nlisteners; i++) {
pl = &wrapper->prop_listeners.data[i];
if (pl->prop_id == prop_id && pl->cb == listener_proc
&& pl->user_data == user_data) {
VECTOR_ERASE(&wrapper->prop_listeners, i);
return noErr;
}
}
// couldn't find it, return noErr??
return noErr;
}
static void
inform_prop_listeners(struct plur_auv2_wrapper *wrapper,
AudioUnitPropertyID prop_id, AudioUnitScope scope)
{
struct plur_auv2_prop_listener *pl;
size_t i, nlisteners;
nlisteners = wrapper->prop_listeners.size;
for (i = 0; i < nlisteners; i++) {
pl = &wrapper->prop_listeners.data[i];
if (pl->prop_id == prop_id) {
pl->cb(pl->user_data, wrapper->au_instance, prop_id, scope, 0);
}
}
}
/**
* property methods
*/
#define ASBD_FORMAT_ID (kAudioFormatLinearPCM)
#define ASBD_FORMAT_FLAGS \
(kAudioFormatFlagsNativeEndian \
| kAudioFormatFlagIsFloat \
| kAudioFormatFlagsNativeFloatPacked \
| kAudioFormatFlagIsNonInterleaved)
static void
construct_asbd(AudioStreamBasicDescription *asbd, double sample_rate,
int nchannels)
{
size_t audio_sample_bytes = sizeof(float);
asbd->mSampleRate = sample_rate;
asbd->mFormatID = ASBD_FORMAT_ID;
asbd->mFormatFlags = ASBD_FORMAT_FLAGS;
asbd->mBitsPerChannel = 8 * audio_sample_bytes;
asbd->mChannelsPerFrame = nchannels;
asbd->mFramesPerPacket = 1;
asbd->mBytesPerPacket = audio_sample_bytes;
asbd->mBytesPerFrame = audio_sample_bytes;
}
static OSStatus
auv2_get_property_info(void *self, AudioUnitPropertyID prop_id,
AudioUnitScope scope, AudioUnitElement elem, UInt32 *data_size,
Boolean *writable)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
// iPlug2 does this. do some hosts pass NULL in for these? fuck.
UInt32 _data_size;
Boolean _writable;
data_size = data_size ? data_size : &_data_size;
writable = writable ? writable : &_writable;
switch (prop_id) {
case kAudioUnitProperty_ParameterList:
if (scope == kAudioUnitScope_Global)
*data_size = sizeof(auv2_param_ids);
else
*data_size = 0;
*writable = false;
return noErr;
case kAudioUnitProperty_ParameterInfo:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(AudioUnitParameterInfo);
*writable = false;
return noErr;
case kAudioUnitProperty_ParameterClumpName:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(AudioUnitParameterIDName);
*writable = false;
return noErr;
case kAudioUnitProperty_ParameterValueStrings: {
const void *values;
ssize_t nvalues;
ASSERT_GLOBAL_SCOPE();
nvalues = auv2_value_list_for_param_id(elem, &values);
if (nvalues <= 0)
return kAudioUnitErr_InvalidParameter;
*data_size = sizeof(CFArrayRef);
*writable = false;
return noErr;
}
case kAudioUnitProperty_ParameterStringFromValue:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(AudioUnitParameterStringFromValue);
*writable = false;
return noErr;
case kAudioUnitProperty_ParameterValueFromString:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(AudioUnitParameterValueFromString);
*writable = false;
return noErr;
case kAudioUnitProperty_ClassInfo:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(CFPropertyListRef);
*writable = true;
return noErr;
case kAudioUnitProperty_FactoryPresets:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(CFArrayRef);
*writable = false;
return noErr;
case kAudioUnitProperty_CurrentPreset:
case kAudioUnitProperty_PresentPreset:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(AUPreset);
*writable = true;
return noErr;
case kAudioUnitProperty_AudioChannelLayout:
*data_size = 0;
*writable = false;
return kAudioUnitErr_InvalidPropertyValue;
case kAudioUnitProperty_SupportedChannelLayoutTags:
switch (scope) {
#if NUM_INPUT_BUSES == 0
case kAudioUnitScope_Input:
*data_size = 0;
return kAudioUnitErr_InvalidProperty;
case kAudioUnitScope_Output:
*data_size = sizeof(AudioChannelLayoutTag) * 1;
break;
#else
case kAudioUnitScope_Input:
case kAudioUnitScope_Output:
*data_size = sizeof(AudioChannelLayoutTag) * 2;
break;
#endif
default:
return kAudioUnitErr_InvalidScope;
}
*writable = false;
return noErr;
case kAudioUnitProperty_Latency:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(Float64);
*writable = false;
return noErr;
case kAudioUnitProperty_MaximumFramesPerSlice:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(UInt32);
*writable = true;
return noErr;
case kAudioUnitProperty_SampleRate:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(Float64);
*writable = true;
return noErr;
case kAudioUnitProperty_TailTime:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(Float64);
*writable = false;
return noErr;
case kAudioUnitProperty_BypassEffect:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(UInt32);
*writable = true;
return noErr;
case kAudioUnitProperty_HostCallbacks:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(wrapper->host_callbacks);
*writable = true;
return noErr;
case kAudioUnitProperty_ElementCount:
*data_size = sizeof(UInt32);
*writable = false;
return noErr;
case kAudioUnitProperty_ElementName:
*data_size = sizeof(CFStringRef);
*writable = false;
return noErr;
case kAudioUnitProperty_StreamFormat:
*data_size = sizeof(AudioStreamBasicDescription);
*writable = true;
return noErr;
case kAudioUnitProperty_SupportedNumChannels:
#if NUM_INPUT_BUSES == 1
*data_size = sizeof(AUChannelInfo) * 2;
#else
*data_size = sizeof(AUChannelInfo);
#endif
*writable = false;
return noErr;
#ifndef PLUR_NO_GUI
case kAudioUnitProperty_CocoaUI:
if (wrapper->cocoa_ui_cls == nil) {
*data_size = 0;
*writable = false;
return kAudioUnitErr_InvalidProperty;
}
*data_size = sizeof(AudioUnitCocoaViewInfo);
*writable = false;
return noErr;
#endif
case kAudioUnitProperty_SetRenderCallback:
ASSERT_INPUT_SCOPE();
if (elem >= ARRAY_LENGTH(wrapper->bus.input))
return kAudioUnitErr_InvalidElement;
*data_size = sizeof(AURenderCallbackStruct);
*writable = true;
return noErr;
case kAudioUnitProperty_MakeConnection:
ASSERT_INPUT_OR_GLOBAL_SCOPE();
*data_size = sizeof(AudioUnitConnection);
*writable = true;
return noErr;
case PLUR_AUV2_AUPROP_WRAPPED_PLUG_PTR:
ASSERT_GLOBAL_SCOPE();
*data_size = sizeof(void *);
*writable = false;
return noErr;
default:
PLUR_TRACE(plug, " ??? unsupported get property info: %d\n",
(int) prop_id);
*data_size = 0;
*writable = false;
return kAudioUnitErr_InvalidProperty;
}
PLUR_UNUSED(plug);
}
static OSStatus
auv2_get_property(void *self, AudioUnitPropertyID prop_id,
AudioUnitScope scope, AudioUnitElement elem,
void *data, UInt32 *data_size)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
{
// this is kind of a stupid little dance.
//
// if get_property is called with `data == NULL`, we're basically just
// supposed to fill in `data_size` and then return. the host could
// absolutely just call get_property_info – but no, we have to handle
// this case.
OSStatus ret;
Boolean writable;
ret = auv2_get_property_info(self, prop_id, scope, elem, data_size,
&writable);
if (!data)
return ret;
}
switch(prop_id) {
/**
* parameters
*/
case kAudioUnitProperty_ParameterList:
PLUR_TRACE(plug, " <:: get property ParameterList\n");
if (scope == kAudioUnitScope_Global)
memcpy(data, auv2_param_ids, sizeof(auv2_param_ids));
return noErr;
case kAudioUnitProperty_ParameterInfo: {
ASSERT_GLOBAL_SCOPE();
AudioUnitParameterInfo* info = data;
const struct plur_param *desc;
const void *values;
ssize_t nvalues;
uint32_t clump;
memset(info, 0, sizeof(*info));
if (!(desc = auv2_param_to_plur(elem)))
return kAudioUnitErr_InvalidParameter;
clump = auv2_param_to_clump_id(elem);
PLUR_TRACE(plug, " <:: get property ParameterInfo for %s\n",
desc->name);
info->flags =
kAudioUnitParameterFlag_IsHighResolution
| kAudioUnitParameterFlag_HasCFNameString
| kAudioUnitParameterFlag_IsWritable
| kAudioUnitParameterFlag_IsReadable;
info->cfNameString = auv2_param_name_map(elem);
info->unit = plur_param_unit_to_au(desc->unit);
info->minValue = 0.0;
if (clump) {
info->flags |= kAudioUnitParameterFlag_HasClump;
info->clumpID = clump;
}
nvalues = auv2_value_list_for_param_id(elem, &values);
if (nvalues > 0) {
info->maxValue = nvalues - 1;
info->unit = kAudioUnitParameterUnit_Indexed;
} else {
info->maxValue = 1.0;
info->defaultValue = 0.0; // FIXME: linkage to program??
info->flags |= kAudioUnitParameterFlag_HasName;
}
return noErr;
}
case kAudioUnitProperty_ParameterClumpName: {
ASSERT_GLOBAL_SCOPE();
AudioUnitParameterIDName *idn = data;
CFStringRef name;
if (!idn->inID)
return kAudioUnitErr_PropertyNotInUse;
name = auv2_clump_name_for_id(idn->inID);
if (!name)
return kAudioUnitErr_PropertyNotInUse;
idn->outName = name;
return noErr;
}
case kAudioUnitProperty_ParameterValueStrings: {
const void *values;
ssize_t nvalues;
ASSERT_GLOBAL_SCOPE();
nvalues = auv2_value_list_for_param_id(elem, &values);
if (nvalues <= 0)
return kAudioUnitErr_InvalidParameter;
*((CFArrayRef *) data) = CFArrayCreate(NULL,
(const void **) values, nvalues, NULL);
return noErr;
}
case kAudioUnitProperty_ParameterStringFromValue: {
AudioUnitParameterStringFromValue *sfv = data;
const struct plur_param *d;
ssize_t nbytes, ret;
char buf[128];
ASSERT_GLOBAL_SCOPE();
if (!(d = auv2_param_to_plur(sfv->inParamID)))
return kAudioUnitErr_InvalidParameter;
PLUR_TRACE(plug,
" <:: get property ParameterStringFromValue for %s\n", d->name);
if (sfv->inValue)
ret = plur_param_get_display_for_value(plug, d,
*sfv->inValue, buf, sizeof(buf));
else
ret = plur_param_get_display(plug, d, buf, sizeof(buf));
if (ret < 0)
return kAudioUnitErr_InvalidParameter;
nbytes = ret;
buf[nbytes++] = ' ';
buf[nbytes] = '\0';
ret = plur_param_get_label(plug, d,
&buf[nbytes], sizeof(buf) - nbytes);
if (ret < 0)
return kAudioUnitErr_InvalidParameter;
nbytes += ret;
sfv->outString = CFStringCreateWithBytes(NULL, (uint8_t *) buf,
nbytes, kCFStringEncodingUTF8, false);
return noErr;
}
case kAudioUnitProperty_ParameterValueFromString: {
AudioUnitParameterValueFromString *vfs = data;
const struct plur_param *d;
char buf[128];
float val;
ASSERT_GLOBAL_SCOPE();
if (!(d = auv2_param_to_plur(vfs->inParamID)))
return kAudioUnitErr_InvalidParameter;
if (!CFStringGetCString(vfs->inString, buf, sizeof(buf),
kCFStringEncodingUTF8))
return kAudioUnitErr_InvalidPropertyValue;
if (plur_param_translate_string(plug, d, buf, &val))
return kAudioUnitErr_InvalidPropertyValue;
vfs->outValue = val;
return noErr;
}
/**
* presets
*/
case kAudioUnitProperty_FactoryPresets: {
unsigned i;
PLUR_TRACE(plug, " <:: get property FactoryPresets\n");
CFMutableArrayRef presets = CFArrayCreateMutable(NULL,
ARRAY_LENGTH(plur_auv2_factory_presets), 0);
for (i = 0; i < ARRAY_LENGTH(plur_auv2_factory_presets); i++)
CFArrayAppendValue(presets, &plur_auv2_factory_presets[i]);
*((CFMutableArrayRef *) data) = presets;
return noErr;
}
case kAudioUnitProperty_CurrentPreset:
case kAudioUnitProperty_PresentPreset: {
PLUR_TRACE(plug, " <:: get property CurrentPreset/PresentPreset\n");
*((AUPreset *) data) =
plur_auv2_factory_presets[plug->current_program];
return noErr;
}
case kAudioUnitProperty_ClassInfo: {
AudioComponentDescription desc;
CFMutableDictionaryRef dict;
struct plur_iovec iovec;
AudioComponent comp;
CFDataRef cfdata;
int32_t version;
OSStatus ret;
PLUR_TRACE(plug, " <:: get property ClassInfo\n");
comp = AudioComponentInstanceGetComponent(wrapper->au_instance);
ret = AudioComponentGetDescription(comp, &desc);
if (ret != noErr)
return ret;
iovec = plur_plug_get_chunk(plug);
dict = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
#define SET_I32(K, V) do { \
CFNumberRef n = CFNumberCreate(NULL, kCFNumberSInt32Type, &V); \
CFDictionarySetValue(dict, CFSTR(K), n); \
CFRelease(n); \
} while (0)
SET_I32(kAUPresetTypeKey, desc.componentType);
SET_I32(kAUPresetSubtypeKey, desc.componentSubType);
SET_I32(kAUPresetManufacturerKey, desc.componentManufacturer);
version = 0;
SET_I32(kAUPresetVersionKey, version);
CFDictionarySetValue(dict, CFSTR(kAUPresetNameKey), CFSTR(""));
cfdata = CFDataCreate(NULL, iovec.base, iovec.len);
CFDictionarySetValue(dict, CFSTR(kAUPresetDataKey), cfdata);
CFRelease(cfdata);
#undef SET_I32
free(iovec.base);
*((CFMutableDictionaryRef *) data) = dict;
return noErr;
}
/**
* process/render
*/
case kAudioUnitProperty_BypassEffect:
ASSERT_GLOBAL_SCOPE();
PLUR_TRACE(plug, " <:: get property BypassEffect\n");
*((UInt32 *) data) = wrapper->bypass;
return noErr;
case kAudioUnitProperty_TailTime:
ASSERT_GLOBAL_SCOPE();
PLUR_TRACE(plug, " <:: get property TailTime\n");
*((Float64 *) data) = 0.0;
return noErr;
case kAudioUnitProperty_Latency:
ASSERT_GLOBAL_SCOPE();
PLUR_TRACE(plug, " <:: get property Latency\n");
*((Float64 *) data) = PLUR_PLUG_LATENCY;
return noErr;
case kAudioUnitProperty_MaximumFramesPerSlice:
ASSERT_GLOBAL_SCOPE();
PLUR_TRACE(plug, " <:: get property BlockSize (%u)\n",
wrapper->block_size);
*((UInt32 *) data) = wrapper->block_size;
return noErr;
case kAudioUnitProperty_SampleRate:
ASSERT_GLOBAL_SCOPE();
*((Float64 *) data) = plur_plug_get_sample_rate(plug);
PLUR_TRACE(plug, " <:: get property SampleRate (%f)\n",
*((Float64 *) data));
return noErr;
/**
* input/output
*/
case kAudioUnitProperty_ElementName: {
// FIXME: put this stuff in plur_bus_metadata
PLUR_TRACE(plug, " <:: get property ElementName\n");
switch (scope) {
case kAudioUnitScope_Input:
*((CFStringRef *) data) = CFSTR("input");
return noErr;
case kAudioUnitScope_Output:
*((CFStringRef *) data) = CFSTR("output");
return noErr;
default:
return kAudioUnitErr_InvalidScope;
}
}
case kAudioUnitProperty_AudioChannelLayout:
return kAudioUnitErr_InvalidPropertyValue;
case kAudioUnitProperty_SupportedChannelLayoutTags: {
AudioChannelLayoutTag *tags = data;
PLUR_TRACE(plug, " <:: get property SupportedChannelLayoutTags\n");
// FIXME: flexible channels
switch (scope) {
#if NUM_INPUT_BUSES == 0
case kAudioUnitScope_Input:
return kAudioUnitErr_InvalidProperty;
case kAudioUnitScope_Output:
tags[0] = kAudioChannelLayoutTag_Stereo;
return noErr;
#else
case kAudioUnitScope_Input:
case kAudioUnitScope_Output:
tags[0] = kAudioChannelLayoutTag_Stereo;
tags[1] = kAudioChannelLayoutTag_Mono;
return noErr;
#endif
default:
return kAudioUnitErr_InvalidScope;
}
}
case kAudioUnitProperty_ElementCount: {
uint32_t count;
PLUR_TRACE(plug, " <:: get property ElementCount\n");
switch(scope) {
case kAudioUnitScope_Input:
count = ARRAY_LENGTH(wrapper->bus.input);
break;
case kAudioUnitScope_Output:
count = ARRAY_LENGTH(wrapper->bus.output);
break;
case kAudioUnitScope_Global:
count = 1;
break;
default:
return kAudioUnitErr_InvalidScope;
}
*((UInt32 *) data) = count;
return noErr;
}
case kAudioUnitProperty_StreamFormat: {
const struct plur_bus_metadata *md;
int connected_channels, channels;
PLUR_TRACE(plug, " <:: get property StreamFormat\n");
switch (scope) {
case kAudioUnitScope_Input:
if (elem >= ARRAY_LENGTH(wrapper->bus.input))
return kAudioUnitErr_InvalidElement;
connected_channels = wrapper->bus.input[elem].connected_channels;
md = &bus_metadata.input[elem];
break;
case kAudioUnitScope_Global:
if (elem != 0)
return kAudioUnitErr_InvalidElement;
/* fall through */
case kAudioUnitScope_Output:
if (elem >= ARRAY_LENGTH(wrapper->bus.output))
return kAudioUnitErr_InvalidElement;
connected_channels = wrapper->bus.output[elem].connected_channels;
md = &bus_metadata.output[elem];
break;
default:
return kAudioUnitErr_InvalidScope;
}
channels = (connected_channels >= 0)
? connected_channels
: md->channels;
construct_asbd(data, plur_plug_get_sample_rate(plug), channels);
return noErr;
}
case kAudioUnitProperty_SupportedNumChannels: {
AUChannelInfo *info = data;
PLUR_TRACE(plug, " <:: get property SupportedNumChannels\n");
info[0].outChannels = 2;
#if NUM_INPUT_BUSES == 1
info[0].inChannels = 2;
info[1].inChannels = 1;
info[1].outChannels = 1;
#else
info[0].inChannels = 0;
#endif
return noErr;
}
/**
* UI
*/
#ifndef PLUR_NO_GUI
case kAudioUnitProperty_CocoaUI: {
if (wrapper->cocoa_ui_cls == nil)
return kAudioUnitErr_InvalidProperty;
AudioUnitCocoaViewInfo *cvi = data;
@autoreleasepool {
NSURL *path = [NSURL
fileURLWithPath:[[NSBundle
bundleForClass:wrapper->cocoa_ui_cls] bundlePath]
];
cvi->mCocoaAUViewBundleLocation = (CFURLRef) [path retain];
}
cvi->mCocoaAUViewClass[0] = CFStringCreateWithCString(NULL,
class_getName(wrapper->cocoa_ui_cls), kCFStringEncodingUTF8);
return noErr;
}
#endif
/**
* ¯\_(ツ)_/¯
*/
case PLUR_AUV2_AUPROP_WRAPPED_PLUG_PTR:
ASSERT_GLOBAL_SCOPE();
PLUR_TRACE(plug, " <:: get property PLUR_AUV2_AUPROP_WRAPPED_PLUG_PTR"
"(%zx <- %zx)\n", (intptr_t) data, (intptr_t) wrapper);
*((void **) data) = wrapper;
return noErr;
default:
PLUR_TRACE(plug, " <!! unsupported get property: %d\n",
(int) prop_id);
return kAudioUnitErr_InvalidProperty;
}
}
static int
i32_val_eq(CFDictionaryRef dict, CFStringRef key, int32_t eq_with)
{
int32_t x;
CFNumberRef n = CFDictionaryGetValue(dict, key);
if (!n)
return 0;
CFNumberGetValue(n, kCFNumberSInt32Type, &x);
return (x == eq_with);
}
static OSStatus
auv2_set_property(void *self, AudioUnitPropertyID prop_id,
AudioUnitScope scope, AudioUnitElement elem,
const void *data, UInt32 data_size)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
inform_prop_listeners(wrapper, prop_id, scope);
switch (prop_id) {
/**
* preset handling
*/
case kAudioUnitProperty_ClassInfo: {
AudioComponentDescription desc;
struct plur_iovec_ref ref;
CFDictionaryRef dict;
AudioComponent comp;
CFDataRef cfdata;
OSStatus ret;
PLUR_TRACE(plug, " ::> set property ClassInfo\n");
comp = AudioComponentInstanceGetComponent(wrapper->au_instance);
ret = AudioComponentGetDescription(comp, &desc);
if (ret != noErr)
return ret;
dict = *((CFPropertyListRef *) data);
#define I32_VAL_EQ(K, V) i32_val_eq(dict, CFSTR(K), (int32_t) (V))
if (!I32_VAL_EQ(kAUPresetManufacturerKey, desc.componentManufacturer)
|| !I32_VAL_EQ(kAUPresetTypeKey, desc.componentType)
|| !I32_VAL_EQ(kAUPresetSubtypeKey, desc.componentSubType)
|| !(cfdata = CFDictionaryGetValue(dict, CFSTR(kAUPresetDataKey))))
return kAudioUnitErr_InvalidPropertyValue;
#undef I32_VAL_EQ
ref.base = CFDataGetBytePtr(cfdata);
ref.len = CFDataGetLength(cfdata);
if (plur_plug_set_chunk(plug, &ref))
return kAudioUnitErr_InvalidPropertyValue;
return noErr;
}
case kAudioUnitProperty_CurrentPreset:
case kAudioUnitProperty_PresentPreset: {
int32_t idx = ((AUPreset *) data)->presetNumber;
PLUR_TRACE(plug, " ::> set property CurrentPreset/PresentPreset\n");
if (idx < 0) {
// auval doesn't like it when i return an error here
return noErr;
}
if (plur_plug_set_factory_preset(plug, idx))
return kAudioUnitErr_InvalidPropertyValue;
return noErr;
}
/**
* audio path shit
*/
case kAudioUnitProperty_BypassEffect:
ASSERT_GLOBAL_SCOPE();
wrapper->bypass = *((UInt32 *) data);
PLUR_TRACE(plug, " ::> set property BypassEffect to %d\n",
wrapper->bypass);
return noErr;
case kAudioUnitProperty_SampleRate:
PLUR_TRACE(plug, " ::> set property SampleRate to %f\n",
*((Float64 *) data));
plur_plug_set_sample_rate(plug, *((Float64 *) data));
plur_plug_reset(plug);
return noErr;
case kAudioUnitProperty_HostCallbacks:
ASSERT_GLOBAL_SCOPE();
PLUR_TRACE(plug, " ::> set property HostCallbacks\n");
memcpy(&wrapper->host_callbacks, data,
sizeof(wrapper->host_callbacks));
return noErr;
case kAudioUnitProperty_MaximumFramesPerSlice:
ASSERT_GLOBAL_SCOPE();
wrapper->block_size = *((UInt32 *) data);
PLUR_TRACE(plug, " ::> set property MaximumFramesPerSlice to %d\n",
wrapper->block_size);
if (resize_scratch_buffer(wrapper))
return kAudio_MemFullError;
return noErr;
case kAudioUnitProperty_StreamFormat: {
const AudioStreamBasicDescription *asbd = data;
PLUR_TRACE(plug, " ::> set property StreamFormat\n",
wrapper->block_size);
if (asbd->mFormatID != ASBD_FORMAT_ID
|| asbd->mFormatFlags != ASBD_FORMAT_FLAGS)
return kAudioUnitErr_InvalidPropertyValue;
switch (scope) {
case kAudioUnitScope_Input:
if (elem >= ARRAY_LENGTH(wrapper->bus.input))
return kAudioUnitErr_InvalidElement;
if (asbd->mChannelsPerFrame > bus_metadata.input[elem].channels)
return kAudioUnitErr_InvalidPropertyValue;
wrapper->bus.input[elem].connected_channels =
asbd->mChannelsPerFrame;
break;
case kAudioUnitScope_Global:
if (elem != 0)
return kAudioUnitErr_InvalidElement;
/* fall through */
case kAudioUnitScope_Output:
if (elem >= ARRAY_LENGTH(wrapper->bus.output))
return kAudioUnitErr_InvalidElement;
if (asbd->mChannelsPerFrame > bus_metadata.output[elem].channels)
return kAudioUnitErr_InvalidPropertyValue;
wrapper->bus.output[elem].connected_channels =
asbd->mChannelsPerFrame;
break;
}
if (asbd->mSampleRate != plur_plug_get_sample_rate(plug))
plur_plug_set_sample_rate(plug, *((Float64 *) data));
return noErr;
}
case kAudioUnitProperty_SetRenderCallback: {
const AURenderCallbackStruct *cb = data;
struct plur_auv2_input_bus *bus;
ASSERT_INPUT_SCOPE();
PLUR_TRACE(plug, " ::> set property SetRenderCallback, element %d\n",
elem);
if (elem >= ARRAY_LENGTH(wrapper->bus.input))
return kAudioUnitErr_InvalidElement;
bus = &wrapper->bus.input[elem];
bus->connection.upstream_au = 0;
bus->render_cb.proc = cb->inputProc;
bus->render_cb.ctx = cb->inputProcRefCon;
return noErr;
}
case kAudioUnitProperty_MakeConnection: {
const AudioUnitConnection *conn = data;
AudioStreamBasicDescription asbd;
struct plur_auv2_input_bus *bus;
AudioUnit upstream_au;
UInt32 asbd_size;
ASSERT_INPUT_OR_GLOBAL_SCOPE();
PLUR_TRACE(plug, " ::> set property MakeConnection, element %d\n",
elem);
if (elem >= ARRAY_LENGTH(wrapper->bus.input))
return kAudioUnitErr_InvalidElement;
upstream_au = conn->sourceAudioUnit;
bus = &wrapper->bus.input[elem];
bus->connection.upstream_au = 0;
if (!upstream_au)
return noErr;
asbd_size = sizeof(asbd);
if (AudioUnitGetProperty(upstream_au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, conn->sourceOutputNumber,
&asbd, &asbd_size)
!= noErr)
return kAudioUnitErr_InvalidPropertyValue;
if (auv2_set_property(wrapper, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, conn->destInputNumber,
&asbd, asbd_size)
!= noErr)
return kAudioUnitErr_InvalidPropertyValue;
bus->connection.upstream_au = upstream_au;
bus->connection.upstream_output_elem = conn->sourceOutputNumber;
return noErr;
}
default:
PLUR_TRACE(plug, " >!! unsupported set property: %d\n",
(int) prop_id);
return kAudioUnitErr_InvalidProperty;
}
}
/**
* render methods
*/
static OSStatus
auv2_add_render_notify(void *self, AURenderCallback render_cb,
void *user_data)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_auv2_render_notify *rn;
size_t i, nnotifies;
if (!render_cb)
return kAudioUnitErr_InvalidParameter;
nnotifies = wrapper->render_notify.size;
for (i = 0; i < nnotifies; i++) {
rn = &wrapper->render_notify.data[i];
if (rn->cb == render_cb && rn->user_data == user_data)
return noErr;
}
VECTOR_PUSH_BACK(&wrapper->render_notify,
&((struct plur_auv2_render_notify) {
.cb = render_cb,
.user_data = user_data,
}));
return noErr;
}
static OSStatus
auv2_remove_render_notify(void *self, AURenderCallback render_cb,
void *user_data)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_auv2_render_notify *rn;
size_t i, nnotifies;
nnotifies = wrapper->render_notify.size;
for (i = 0; i < nnotifies; i++) {
rn = &wrapper->render_notify.data[i];
if (rn->cb == render_cb && rn->user_data == user_data) {
VECTOR_ERASE(&wrapper->render_notify, i);
return noErr;
}
}
return noErr;
}
/**
* render methods
*/
static void
dispatch_render_notify(struct plur_auv2_wrapper *wrapper,
AudioUnitRenderActionFlags action_flags,
const AudioTimeStamp *timestamp, UInt32 output_bus_number,
UInt32 nframes, AudioBufferList *bufs)
{
struct plur_auv2_render_notify *rn;
AudioUnitRenderActionFlags flags_copy;
size_t i, nnotifies;
nnotifies = wrapper->render_notify.size;
for (i = 0; i < nnotifies; i++) {
rn = &wrapper->render_notify.data[i];
// just here so the callback can't clobber this for everybody, but I
// suppose it doesn't matter because hella other params are non-const
// passed by reference too, so whatever.
flags_copy = action_flags;
rn->cb(rn->user_data, &flags_copy, timestamp, output_bus_number,
nframes, bufs);
}
}
static struct plur_musical_time
get_musical_time(struct plur_auv2_wrapper *wrapper)
{
HostCallbackInfo *cb = &wrapper->host_callbacks;
struct plur_musical_time mtime = {
.bpm = 0.0,
.beat = 0.0
};
if (!cb->beatAndTempoProc)
return mtime;
if (cb->beatAndTempoProc(cb->hostUserData, &mtime.beat, &mtime.bpm)
!= noErr) {
mtime.bpm = 0.0;
mtime.beat = 0.0;
}
return mtime;
}
static OSStatus
pull_input_bus(struct plur_auv2_wrapper *wrapper,
const AudioTimeStamp *timestamp, UInt32 nframes, unsigned bus_idx,
float **inputs, unsigned nchannels)
{
AudioUnitRenderActionFlags render_flags;
struct plur_auv2_buffer_list buf_list;
struct plur_auv2_input_bus *bus;
unsigned i, connected_channels;
uint32_t buf_size;
OSStatus r;
for (i = 0; i < nchannels; i++)
inputs[i] = wrapper->scratch_buffer;
if (!ARRAY_LENGTH(wrapper->bus.input))
return noErr;
PLUR_TRACE(PLUR_PLUGIN(&wrapper->plug), " :<> pull_input_bus %d start\n",
bus_idx);
bus = &wrapper->bus.input[bus_idx];
connected_channels = bus->connected_channels;
buf_size = sizeof(*wrapper->scratch_buffer) * nframes;
render_flags = 0;
buf_list.mNumberBuffers = connected_channels;
for (i = 0; i < connected_channels; i++) {
AudioBuffer *buf = &buf_list.mBuffers[i];
buf->mNumberChannels = 1;
buf->mDataByteSize = buf_size;
buf->mData = get_input_buffer(wrapper, i);
}
if (bus->render_cb.proc) {
r = bus->render_cb.proc(bus->render_cb.ctx, &render_flags, timestamp,
bus_idx, nframes, (void *) &buf_list);
} else if (bus->connection.upstream_au) {
r = AudioUnitRender(bus->connection.upstream_au, &render_flags,
timestamp, bus->connection.upstream_output_elem, nframes,
(void *) &buf_list);
} else {
// not connected
return noErr;
}
if (r != noErr)
return r;
connected_channels = MINT(nchannels, connected_channels);
for (i = 0; i < connected_channels; i++)
inputs[i] = buf_list.mBuffers[i].mData;
for (; i < nchannels; i++)
inputs[i] = wrapper->zero_buffer;
PLUR_TRACE(PLUR_PLUGIN(&wrapper->plug), " :<> pull_input_bus %d end\n",
bus_idx);
return noErr;
}
static void
map_output_buffers(struct plur_auv2_wrapper *wrapper, AudioBufferList *bufs,
float **outputs, unsigned nchannels)
{
unsigned i, connected_channels;
connected_channels = MINT(bufs->mNumberBuffers, nchannels);
for (i = 0; i < connected_channels; i++) {
if (!bufs->mBuffers[i].mData)
bufs->mBuffers[i].mData = get_output_buffer(wrapper, i);
outputs[i] = bufs->mBuffers[i].mData;
}
for (; i < nchannels; i++)
outputs[i] = wrapper->scratch_buffer;
}
static OSStatus
auv2_render(void *self, AudioUnitRenderActionFlags *action_flags,
const AudioTimeStamp *timestamp, UInt32 output_bus_number,
UInt32 nframes, AudioBufferList *bufs)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
AudioUnitRenderActionFlags flags_if_passed_null = 0;
float *inputs[NUM_INPUT_CHANNELS], *outputs[NUM_OUTPUT_CHANNELS];
struct plur_musical_time mtime;
OSStatus r;
// Dplug implies that Garageband does this
if (!action_flags)
action_flags = &flags_if_passed_null;
PLUR_TRACE(plug, " ::> render start\n");
// sanity checks
if (!(*action_flags & kAudioUnitRenderAction_DoNotCheckRenderArgs)) {
if (output_bus_number > ARRAY_LENGTH(wrapper->bus.output))
return kAudioUnitErr_InvalidElement;
if (!(timestamp->mFlags & kAudioTimeStampSampleTimeValid))
return kAudio_ParamError;
if (nframes > wrapper->block_size)
return kAudioUnitErr_TooManyFramesToProcess;
}
dispatch_render_notify(wrapper,
(*action_flags) | kAudioUnitRenderAction_PreRender,
timestamp, output_bus_number, nframes, bufs);
r = pull_input_bus(wrapper, timestamp, nframes,
0, inputs, NUM_INPUT_CHANNELS);
if (r != noErr)
return r;
map_output_buffers(wrapper, bufs, outputs, NUM_OUTPUT_CHANNELS);
mtime = get_musical_time(wrapper);
// FIXME: bypass/passthrough
plur_plug_process(plug, &mtime, inputs, outputs, nframes);
dispatch_render_notify(wrapper,
(*action_flags) | kAudioUnitRenderAction_PostRender,
timestamp, output_bus_number, nframes, bufs);
PLUR_TRACE(plug, " ::> render end\n");
return noErr;
}
static OSStatus
auv2_reset(void *self, AudioUnitScope scope, AudioUnitElement elem)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
PLUR_TRACE(plug, " !:: reset\n");
plur_plug_reset(plug);
return noErr;
}
/**
* MIDI methods
*/
#ifdef MIDI_INPUT
static OSStatus
auv2_midi_in(void *self, UInt32 status, UInt32 data1, UInt32 data2,
UInt32 frame_offset)
{
struct plur_auv2_wrapper *wrapped = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapped->plug);
struct plur_event e = {
.ev_type = PLUR_EVENT_TYPE_MIDI,
.frame_offset = frame_offset,
.midi = {
.data = {
status & 0xFF,
data1 & 0xFF,
data2 & 0xFF,
0x00
},
.detune = 0.f,
.note_off_velocity = 0.f
}
};
plur_plug_enqueue_event(plug, &e);
return noErr;
}
static OSStatus
auv2_sysex_in(void *_self, const UInt8 *data, UInt32 nbytes)
{
return noErr;
}
#endif
/**
* dispatch
*/
static AudioComponentMethod
ac_lookup(SInt16 selector)
{
switch (selector) {
#define SEL(sel, method) case sel: return (AudioComponentMethod) method;
SEL(kAudioUnitInitializeSelect, auv2_initialize)
SEL(kAudioUnitUninitializeSelect, auv2_uninitialize)
SEL(kAudioUnitGetPropertyInfoSelect, auv2_get_property_info)
SEL(kAudioUnitGetPropertySelect, auv2_get_property)
SEL(kAudioUnitSetPropertySelect, auv2_set_property)
SEL(kAudioUnitAddPropertyListenerSelect, auv2_add_property_listener)
SEL(kAudioUnitRemovePropertyListenerSelect,
auv2_remove_property_listener)
SEL(kAudioUnitRemovePropertyListenerWithUserDataSelect,
auv2_remove_property_listener_with_user_data)
SEL(kAudioUnitAddRenderNotifySelect,
auv2_add_render_notify)
SEL(kAudioUnitRemoveRenderNotifySelect,
auv2_remove_render_notify)
SEL(kAudioUnitGetParameterSelect, auv2_get_parameter)
SEL(kAudioUnitSetParameterSelect, auv2_set_parameter)
SEL(kAudioUnitScheduleParametersSelect, auv2_schedule_parameters)
SEL(kAudioUnitRenderSelect, auv2_render)
SEL(kAudioUnitResetSelect, auv2_reset)
#ifdef MIDI_INPUT
SEL(kMusicDeviceMIDIEventSelect, auv2_midi_in);
SEL(kMusicDeviceSysExSelect, auv2_sysex_in);
#endif
#undef SEL
default:
return NULL;
}
}
/**
* ui factory nonsense
*/
#ifndef PLUR_NO_GUI
static unsigned int
view_interfaceVersion(id self, SEL cmd)
{
return 0;
}
static NSString *
view_description(id self, SEL cmd)
{
return @"@PLUR_EFFECT_NAME@ UI";
}
static NSView *
view_uiViewForAudioUnit(id self, SEL cmd, AudioUnit au, NSSize size)
{
struct plur_auv2_wrapper *wrapper;
struct plur_plugin *plug;
UInt32 nbytes;
if (AudioUnitGetProperty(au, PLUR_AUV2_AUPROP_WRAPPED_PLUG_PTR,
kAudioUnitScope_Global, 0, &wrapper, &nbytes) != noErr)
return nil;
plug = PLUR_PLUGIN(&wrapper->plug);
if (plur_plug_editor_open(plug, 0)) {
PLUR_TRACE(plug, " >> couldn't open UI\n");
return nil;
}
PLUR_TRACE(plug, " >> ui open, returning %zx\n",
(intptr_t) plur_plug_editor_get_native_window(plug));
return (NSView *) plur_plug_editor_get_native_window(plug);
}
static Class
create_view_class(struct plur_auv2_wrapper *wrapper)
{
char cls_name_buf[128];
Class cls;
snprintf(cls_name_buf, sizeof(cls_name_buf), "@PLUR_EFFECT_NAME@_view_%zx",
(intptr_t) wrapper);
cls = objc_allocateClassPair([NSObject class], cls_name_buf, 0);
class_addMethod(cls, @selector(interfaceVersion),
(IMP) view_interfaceVersion, "I@:");
class_addMethod(cls, @selector(description),
(IMP) view_description, "@@:");
// here's how to create this argument list:
// for some reason @encode() doesn't reduce to a string constant at compile
// time??? otherwise we wouldn't need to precalc it like this.
//
// printf(" >> \"%s@:%s%s\"\n", @encode(NSView*),
// @encode(AudioUnit), @encode(NSSize));
class_addMethod(cls, @selector(uiViewForAudioUnit:withSize:),
(IMP) view_uiViewForAudioUnit,
"@@:^{ComponentInstanceRecord=[1q]}{CGSize=dd}");
class_addProtocol(cls, @protocol(AUCocoaUIBase));
return cls;
}
#else
static Class
create_view_class(struct plur_auv2_wrapper *wrapper)
{
return nil;
}
#endif
/**
* lifecycle
*/
static struct wwrl_allocator stdlib_allocator = {
.malloc = malloc,
.free = free,
.calloc = calloc,
.realloc = realloc
};
static OSStatus
ac_open(void *self, AudioUnit instance)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
unsigned i;
wrapper->cocoa_ui_cls = nil;
wrapper->au_instance = instance;
plug->program_ptr = &wrapper->plug.@PLUR_IFACE_PROGRAM_MEMBER@;
#ifdef PLUR_ENABLE_TRACE
plur_trace_init(plug);
#endif
PLUR_TRACE(plug, " :: PLUR AUv2 adapter initialised, hi!\n");
memset(&wrapper->host_callbacks, 0, sizeof(wrapper->host_callbacks));
wrapper->block_size = 1024;
wrapper->scratch_buffer = NULL;
if (resize_scratch_buffer(wrapper)) {
PLUR_TRACE(plug, " [-] couldn't allocate scratch buffers, bailing.\n");
goto err_cannot_alloc_scratch;
}
for (i = 0; i < ARRAY_LENGTH(wrapper->bus.input); i++) {
struct plur_auv2_input_bus *bus = &wrapper->bus.input[i];
bus->connected_channels = -1;
bus->render_cb.proc = NULL;
bus->connection.upstream_au = 0;
}
for (i = 0; i < ARRAY_LENGTH(wrapper->bus.output); i++)
wrapper->bus.output[i].connected_channels = -1;
plur_plug_init(plug);
VECTOR_INIT(&plug->events, &stdlib_allocator, 256);
VECTOR_INIT(&wrapper->prop_listeners, &stdlib_allocator, 32);
VECTOR_INIT(&wrapper->render_notify, &stdlib_allocator, 32);
wrapper->cocoa_ui_cls = create_view_class(wrapper);
if (wrapper->cocoa_ui_cls != nil)
objc_registerClassPair(wrapper->cocoa_ui_cls);
wrapper->bypass = 0;
return noErr;
err_cannot_alloc_scratch:
free(self);
return kAudioUnitErr_FailedInitialization;
}
static OSStatus
ac_close(void *self)
{
struct plur_auv2_wrapper *wrapper = self;
struct plur_plugin *plug = PLUR_PLUGIN(&wrapper->plug);
#ifndef PLUR_NO_GUI
plur_plug_editor_close(plug);
#endif
free(wrapper->scratch_buffer);
VECTOR_FREE(&wrapper->render_notify);
VECTOR_FREE(&wrapper->prop_listeners);
VECTOR_FREE(&plug->events);
plur_plug_fini(plug);
PLUR_TRACE(plug, " :: PLUR AUv2 plugin freed, bye!\n\n");
#ifdef PLUR_ENABLE_TRACE
plur_trace_fini(plug);
#endif
if (wrapper->cocoa_ui_cls != nil)
objc_disposeClassPair(wrapper->cocoa_ui_cls);
free(self);
return noErr;
}
/**
* ABI entry point
*
* this symbol is reference as `factoryFunction` in the component bundle's
* Info.plist
*/
AudioComponentPlugInInterface *
@PLUR_AUV2_FACTORY_FUNC@(const AudioComponentDescription *desc)
{
struct plur_auv2_wrapper *wrapper = calloc(1, sizeof(*wrapper));
if (!wrapper)
return NULL;
wrapper->iface = (AudioComponentPlugInInterface) {
.Open = ac_open,
.Close = ac_close,
.Lookup = ac_lookup,
.reserved = NULL
};
return (void *) wrapper;
}
/**
* plug -> host iface
*/
static void
notify_param_event(struct plur_plugin *plug, AudioUnitEventType ev_type,
const struct plur_param *d)
{
struct plur_auv2_wrapper *wrapper = wrapper_from_plug(plug);
AudioUnitEvent ev = {
.mEventType = ev_type,
.mArgument.mParameter = {
.mAudioUnit = wrapper->au_instance,
.mParameterID = d->id.hash,
.mScope = kAudioUnitScope_Global,
.mElement = 0
}
};
AUEventListenerNotify(NULL, NULL, &ev);
}
void
plur_notify_param_change(struct plur_plugin *plug,
const struct plur_param *d, float val)
{
notify_param_event(plug, kAudioUnitEvent_ParameterValueChange, d);
}
void
plur_notify_param_edit_status(struct plur_plugin *plug,
const struct plur_param *d, int being_edited)
{
AudioUnitEventType ev_type = (being_edited) ?
kAudioUnitEvent_BeginParameterChangeGesture
: kAudioUnitEvent_EndParameterChangeGesture;
notify_param_event(plug, ev_type, d);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment