|
/** .__ |
|
* ______ | | __ ______________ ______ ___________ |
|
* \____ \| | | | \_ __ \__ \ \____ \\____ \__ \ |
|
* | |_> > |_| | /| | \// __ \| |_> > |_> > __ \_ |
|
* | __/|____/____/ |__| (____ / __/| __(____ / |
|
* |__| 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); |
|
} |