static string preset_suffix = ".aupreset";
static bool preset_search_path_initialized = false;
FILE * AUPluginInfo::_crashlog_fd = NULL;
+bool AUPluginInfo::_scan_only = true;
+
+
+static void au_blacklist (std::string id)
+{
+ string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_blacklist.txt");
+ FILE * blacklist_fd = NULL;
+ if (! (blacklist_fd = fopen(fn.c_str(), "a"))) {
+ PBD::error << "Cannot append to AU blacklist for '"<< id <<"'\n";
+ return;
+ }
+ assert(id.find("\n") == string::npos);
+ fprintf(blacklist_fd, "%s\n", id.c_str());
+ ::fclose(blacklist_fd);
+}
+
+static void au_unblacklist (std::string id)
+{
+ string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_blacklist.txt");
+ if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
+ PBD::warning << "Expected Blacklist file does not exist.\n";
+ return;
+ }
+
+ std::string bl;
+ {
+ std::ifstream ifs(fn.c_str());
+ bl.assign ((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
+ }
+
+ ::g_unlink (fn.c_str());
+
+ assert (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS));
+ assert(id.find("\n") == string::npos);
+
+ id += "\n"; // add separator
+ const size_t rpl = bl.find(id);
+ if (rpl != string::npos) {
+ bl.replace(rpl, id.size(), "");
+ }
+ if (bl.empty()) {
+ return;
+ }
+
+ FILE * blacklist_fd = NULL;
+ if (! (blacklist_fd = fopen(fn.c_str(), "w"))) {
+ PBD::error << "Cannot open AU blacklist.\n";
+ return;
+ }
+ fprintf(blacklist_fd, "%s", bl.c_str());
+ ::fclose(blacklist_fd);
+}
+
+static bool is_blacklisted (std::string id)
+{
+ string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_blacklist.txt");
+ if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
+ return false;
+ }
+ std::string bl;
+ std::ifstream ifs(fn.c_str());
+ bl.assign ((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
+
+ assert(id.find("\n") == string::npos);
+
+ id += "\n"; // add separator
+ const size_t rpl = bl.find(id);
+ if (rpl != string::npos) {
+ return true;
+ }
+ return false;
+}
+
+
static OSStatus
_render_callback(void *userData,
, input_offset (0)
, input_buffers (0)
, frames_processed (0)
+ , audio_input_cnt (0)
, _parameter_listener (0)
, _parameter_listener_arg (0)
, last_transport_rolling (false)
p += preset_search_path;
preset_search_path = p;
preset_search_path_initialized = true;
+ DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Preset Path: %1\n", preset_search_path));
}
init ();
{
init ();
+ for (size_t i = 0; i < descriptors.size(); ++i) {
+ set_parameter (i, other.get_parameter (i));
+ }
}
AUPlugin::~AUPlugin ()
string name = CFStringRefToStdString (preset->presetName);
factory_preset_map[name] = preset->presetNumber;
+ DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Factory Preset: %1 > %2\n", name, preset->presetNumber));
}
CFRelease (presets);
AUPlugin::init ()
{
OSErr err;
+ CFStringRef itemName;
/* these keep track of *configured* channel set up,
not potential set ups.
input_channels = -1;
output_channels = -1;
+ {
+ CAComponentDescription temp;
+ GetComponentInfo (comp.get()->Comp(), &temp, NULL, NULL, NULL);
+ CFStringRef compTypeString = UTCreateStringForOSType(temp.componentType);
+ CFStringRef compSubTypeString = UTCreateStringForOSType(temp.componentSubType);
+ CFStringRef compManufacturerString = UTCreateStringForOSType(temp.componentManufacturer);
+ itemName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ - %@ - %@"),
+ compTypeString, compManufacturerString, compSubTypeString);
+ if (compTypeString != NULL) CFRelease(compTypeString);
+ if (compSubTypeString != NULL) CFRelease(compSubTypeString);
+ if (compManufacturerString != NULL) CFRelease(compManufacturerString);
+ }
+
+ au_blacklist(CFStringRefToStdString(itemName));
try {
DEBUG_TRACE (DEBUG::AudioUnits, "opening AudioUnit\n");
discover_factory_presets ();
// Plugin::setup_controls ();
+
+ au_unblacklist(CFStringRefToStdString(itemName));
+ if (itemName != NULL) CFRelease(itemName);
}
void
for (uint32_t i = 0; i < sizeof (scopes) / sizeof (scopes[0]); ++i) {
- AUParamInfo param_info (unit->AU(), false, false, scopes[i]);
+ AUParamInfo param_info (unit->AU(), false, /* include read only */ true, scopes[i]);
for (uint32_t i = 0; i < param_info.NumParams(); ++i) {
d.toggled = (info.unit == kAudioUnitParameterUnit_Boolean) ||
(d.integer_step && ((d.upper - d.lower) == 1.0));
d.sr_dependent = (info.unit == kAudioUnitParameterUnit_SampleFrames);
- d.automatable = !d.toggled &&
+ d.automatable = /* !d.toggled && -- ardour can automate toggles, can AU ? */
!(info.flags & kAudioUnitParameterFlag_NonRealTime) &&
(info.flags & kAudioUnitParameterFlag_IsWritable);
{
AudioStreamBasicDescription streamFormat;
bool was_initialized = initialized;
- int32_t audio_in = in.n_audio();
int32_t audio_out = out.n_audio();
+ if (audio_input_cnt > 0) {
+ in.set (DataType::AUDIO, audio_input_cnt);
+ }
+ int32_t audio_in = in.n_audio();
DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("configure %1 for %2 in %3 out\n", name(), in, out));
{
ChanCount c;
- c.set (DataType::AUDIO, 1);
- c.set (DataType::MIDI, 0);
if (input_channels < 0) {
- warning << string_compose (_("AUPlugin: %1 input_streams() called without any format set!"), name()) << endmsg;
+ // force PluginIoReConfigure -- see also commit msg e38eb06
+ c.set (DataType::AUDIO, 0);
+ c.set (DataType::MIDI, 0);
} else {
c.set (DataType::AUDIO, input_channels);
c.set (DataType::MIDI, _has_midi_input ? 1 : 0);
{
ChanCount c;
- c.set (DataType::AUDIO, 1);
- c.set (DataType::MIDI, 0);
-
if (output_channels < 0) {
- warning << string_compose (_("AUPlugin: %1 output_streams() called without any format set!"), name()) << endmsg;
+ // force PluginIoReConfigure - see also commit msg e38eb06
+ c.set (DataType::AUDIO, 0);
+ c.set (DataType::MIDI, 0);
} else {
c.set (DataType::AUDIO, output_channels);
c.set (DataType::MIDI, _has_midi_output ? 1 : 0);
//if there's a configuration that keeps out==in
if (in.n_midi() > 0 && audio_in == 0) {
- audio_out = -1;
+ audio_out = 2; // prefer stereo version if available.
} else {
audio_out = audio_in;
}
+ /* kAudioUnitProperty_SupportedNumChannels
+ * https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html#//apple_ref/doc/uid/TP40003278-CH12-SW20
+ *
+ * - both fields are -1
+ * e.g. inChannels = -1 outChannels = -1
+ * This is the default case. Any number of input and output channels, as long as the numbers match
+ *
+ * - one field is -1, the other field is positive
+ * e.g. inChannels = -1 outChannels = 2
+ * Any number of input channels, exactly two output channels
+ *
+ * - one field is -1, the other field is -2
+ * e.g. inChannels = -1 outChannels = -2
+ * Any number of input channels, any number of output channels
+ *
+ * - both fields have non-negative values
+ * e.g. inChannels = 2 outChannels = 6
+ * Exactly two input channels, exactly six output channels
+ * e.g. inChannels = 0 outChannels = 2
+ * No input channels, exactly two output channels (such as for an instrument unit with stereo output)
+ *
+ * - both fields have negative values, neither of which is –1 or –2
+ * e.g. inChannels = -4 outChannels = -8
+ * Up to four input channels and up to eight output channels
+ */
+
for (vector<pair<int,int> >::iterator i = io_configs.begin(); i != io_configs.end(); ++i) {
int32_t possible_in = i->first;
audio_out = 2;
found = true;
} else if (possible_out < -2) {
- /* explicitly variable number of outputs.
-
- Since Ardour can handle any configuration,
- we have to somehow pick a number.
-
- We'll use the number of inputs
- to the master bus, or 2 if there
- is no master bus.
- */
- boost::shared_ptr<Route> master = _session.master_out();
- if (master) {
- audio_out = master->input()->n_ports().n_audio();
- } else {
- audio_out = 2;
- }
+ /* explicitly variable number of outputs.
+ *
+ * We really need to ask the user in this case.
+ * stereo will be correct in 99.9% of all cases.
+ */
+ audio_out = 2;
found = true;
} else {
/* exact number of outputs */
audio_out = 2;
found = true;
} else if (possible_out < -2) {
- /* explicitly variable number of outputs.
-
- Since Ardour can handle any configuration,
- we have to somehow pick a number.
-
- We'll use the number of inputs
- to the master bus, or 2 if there
- is no master bus.
- */
- boost::shared_ptr<Route> master = _session.master_out();
- if (master) {
- audio_out = master->input()->n_ports().n_audio();
- } else {
- audio_out = 2;
- }
+ /* explicitly variable number of outputs.
+ *
+ * We really need to ask the user in this case.
+ * stereo will be correct in 99.9% of all cases.
+ */
+ audio_out = 2;
found = true;
} else {
/* exact number of outputs */
}
if (found) {
+ if (possible_in < -2 && audio_in == 0) {
+ // input-port count cannot be zero, use as many ports
+ // as outputs, but at most abs(possible_in)
+ audio_input_cnt = max (1, min (audio_out, -possible_in));
+ }
break;
}
}
if (found) {
+ out.set (DataType::MIDI, 0); /// XXX
+ out.set (DataType::AUDIO, audio_out);
DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("\tCHOSEN: in %1 out %2\n", in, out));
} else {
DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("\tFAIL: no io configs match %1\n", in));
return false;
}
- out.set (DataType::MIDI, 0);
- out.set (DataType::AUDIO, audio_out);
-
return true;
}
}
bool
-AUPlugin::parameter_is_control (uint32_t) const
+AUPlugin::parameter_is_control (uint32_t param) const
{
- return true;
+ assert(param < descriptors.size());
+ if (descriptors[param].automatable) {
+ /* corrently ardour expects all controls to be automatable
+ * IOW ardour GUI elements mandate an Evoral::Parameter
+ * for all input+control ports.
+ */
+ return true;
+ }
+ return false;
}
bool
-AUPlugin::parameter_is_input (uint32_t) const
+AUPlugin::parameter_is_input (uint32_t param) const
{
- return false;
+ /* AU params that are both readable and writeable,
+ * are listed in kAudioUnitScope_Global
+ */
+ return (descriptors[param].scope == kAudioUnitScope_Input || descriptors[param].scope == kAudioUnitScope_Global);
}
bool
-AUPlugin::parameter_is_output (uint32_t) const
+AUPlugin::parameter_is_output (uint32_t param) const
{
- return false;
+ assert(param < descriptors.size());
+ // TODO check if ardour properly handles ports
+ // that report is_input + is_output == true
+ // -> add || descriptors[param].scope == kAudioUnitScope_Global
+ return (descriptors[param].scope == kAudioUnitScope_Output);
}
void
CFRelease(propertyList);
+ user_preset_map[preset_name] = user_preset_path;;
+
+ DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Saving Preset to %1\n", user_preset_path));
+
return string ("file:///") + user_preset_path;
}
find_files_matching_filter (preset_files, preset_search_path, au_preset_filter, this, true, true, true);
if (preset_files.empty()) {
+ DEBUG_TRACE (DEBUG::AudioUnits, "AU No Preset Files found for given plugin.\n");
return;
}
if (check_and_get_preset_name (get_comp()->Comp(), path, preset_name)) {
user_preset_map[preset_name] = path;
+ DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Preset File: %1 > %2\n", preset_name, path));
+ } else {
+ DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU INVALID Preset: %1 > %2\n", preset_name, path));
}
}
for (UserPresetMap::iterator i = user_preset_map.begin(); i != user_preset_map.end(); ++i) {
_presets.insert (make_pair (i->second, Plugin::PresetRecord (i->second, i->first)));
+ DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Adding User Preset: %1 > %2\n", i->first, i->second));
}
/* add factory presets */
/* XXX: dubious */
string const uri = string_compose ("%1", _presets.size ());
_presets.insert (make_pair (uri, Plugin::PresetRecord (uri, i->first, i->second)));
+ DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Adding Factory Preset: %1 > %2\n", i->first, i->second));
}
}
AUPluginInfo::AUPluginInfo (boost::shared_ptr<CAComponentDescription> d)
: descriptor (d)
+ , version (0)
{
type = ARDOUR::AudioUnit;
}
}
PluginInfoList*
-AUPluginInfo::discover ()
+AUPluginInfo::discover (bool scan_only)
{
XMLTree tree;
+ /* AU require a CAComponentDescription pointer provided by the OS.
+ * Ardour only caches port and i/o config. It can't just 'scan' without
+ * 'discovering' (like we do for VST).
+ *
+ * "Scan Only" means
+ * "Iterate over all plugins. skip the ones where there's no io-cache".
+ */
+ _scan_only = scan_only;
+
if (!Glib::file_test (au_cache_path(), Glib::FILE_TEST_EXISTS)) {
ARDOUR::BootMessage (_("Discovering AudioUnit plugins (could take some time ...)"));
}
assert(!_crashlog_fd);
DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Creating AU Log: %1\n", fn));
if (!(_crashlog_fd = fopen(fn.c_str(), "w"))) {
- PBD::error << "Cannot create AU error-log\n";
+ PBD::error << "Cannot create AU error-log" << fn << "\n";
+ cerr << "Cannot create AU error-log" << fn << "\n";
}
}
void
AUPluginInfo::au_crashlog (std::string msg)
{
- assert(_crashlog_fd);
- fprintf(_crashlog_fd, "AU: %s\n", msg.c_str());
- ::fflush(_crashlog_fd);
+ if (!_crashlog_fd) {
+ fprintf(stderr, "AU: %s\n", msg.c_str());
+ } else {
+ fprintf(_crashlog_fd, "AU: %s\n", msg.c_str());
+ ::fflush(_crashlog_fd);
+ }
}
void
while (comp != NULL) {
CAComponentDescription temp;
GetComponentInfo (comp, &temp, NULL, NULL, NULL);
+ CFStringRef itemName = NULL;
{
+ if (itemName != NULL) CFRelease(itemName);
CFStringRef compTypeString = UTCreateStringForOSType(temp.componentType);
CFStringRef compSubTypeString = UTCreateStringForOSType(temp.componentSubType);
CFStringRef compManufacturerString = UTCreateStringForOSType(temp.componentManufacturer);
- CFStringRef itemName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ - %@ - %@"),
+ itemName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ - %@ - %@"),
compTypeString, compManufacturerString, compSubTypeString);
au_crashlog(string_compose("Scanning ID: %1", CFStringRefToStdString(itemName)));
if (compTypeString != NULL)
CFRelease(compManufacturerString);
}
+ if (is_blacklisted(CFStringRefToStdString(itemName))) {
+ info << string_compose (_("Skipped blacklisted AU plugin %1 "), CFStringRefToStdString(itemName)) << endmsg;
+ comp = FindNextComponent (comp, &desc);
+ continue;
+ }
+
AUPluginInfoPtr info (new AUPluginInfo
(boost::shared_ptr<CAComponentDescription> (new CAComponentDescription(temp))));
case kAudioUnitType_Panner:
case kAudioUnitType_OfflineEffect:
case kAudioUnitType_FormatConverter:
+ comp = FindNextComponent (comp, &desc);
continue;
case kAudioUnitType_Output:
break;
}
+ au_blacklist(CFStringRefToStdString(itemName));
AUPluginInfo::get_names (temp, info->name, info->creator);
ARDOUR::PluginScanMessage(_("AU"), info->name, false);
au_crashlog(string_compose("Plugin: %1", info->name));
info->version = 0;
}
- if (cached_io_configuration (info->unique_id, info->version, cacomp, info->cache, info->name)) {
+ const int rv = cached_io_configuration (info->unique_id, info->version, cacomp, info->cache, info->name);
+ if (rv == 0) {
/* here we have to map apple's wildcard system to a simple pair
of values. in ::can_do() we use the whole system, but here
we need a single pair of values. XXX probably means we should
plugs.push_back (info);
- } else {
+ }
+ else if (rv == -1) {
error << string_compose (_("Cannot get I/O configuration info for AU %1"), info->name) << endmsg;
}
+ au_unblacklist(CFStringRefToStdString(itemName));
au_crashlog("Success.");
comp = FindNextComponent (comp, &desc);
+ if (itemName != NULL) CFRelease(itemName); itemName = NULL;
}
au_crashlog(string_compose("End AU discovery for Type: %1", (int)desc.componentType));
}
-bool
+int
AUPluginInfo::cached_io_configuration (const std::string& unique_id,
UInt32 version,
CAComponent& comp,
if (cim != cached_info.end()) {
cinfo = cim->second;
- return true;
+ return 0;
+ }
+
+ if (_scan_only) {
+ PBD::info << string_compose (_("Skipping AU %1 (not indexed. Discover new plugins to add)"), name) << endmsg;
+ return 1;
}
CAAudioUnit unit;
try {
if (CAAudioUnit::Open (comp, unit) != noErr) {
- return false;
+ return -1;
}
} catch (...) {
warning << string_compose (_("Could not load AU plugin %1 - ignored"), name) << endmsg;
- return false;
+ return -1;
}
DEBUG_TRACE (DEBUG::AudioUnits, "get AU channel info\n");
if ((ret = unit.GetChannelInfo (&channel_info, cnt)) < 0) {
- return false;
+ return -1;
}
if (ret > 0) {
add_cached_info (id, cinfo);
save_cached_info ();
- return true;
+ return 0;
}
void