*/
#include <sstream>
+#include <fstream>
#include <errno.h>
#include <string.h>
#include <math.h>
#include "pbd/xml++.h"
#include "pbd/convert.h"
#include "pbd/whitespace.h"
-#include "pbd/pathscanner.h"
+#include "pbd/file_utils.h"
#include "pbd/locale_guard.h"
#include <glibmm/threads.h>
static string preset_search_path = "/Library/Audio/Presets:/Network/Library/Audio/Presets";
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.lower = info.minValue;
d.upper = info.maxValue;
- d.default_value = info.defaultValue;
+ d.normal = info.defaultValue;
d.integer_step = (info.unit == kAudioUnitParameterUnit_Indexed);
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);
d.logarithmic = (info.flags & kAudioUnitParameterFlag_DisplayLogarithmic);
- d.unit = info.unit;
+ d.au_unit = info.unit;
+ switch (info.unit) {
+ case kAudioUnitParameterUnit_Decibels:
+ d.unit = ParameterDescriptor::DB;
+ break;
+ case kAudioUnitParameterUnit_MIDINoteNumber:
+ d.unit = ParameterDescriptor::MIDI_NOTE;
+ break;
+ case kAudioUnitParameterUnit_Hertz:
+ d.unit = ParameterDescriptor::HZ;
+ break;
+ }
- d.step = 1.0;
- d.smallstep = 0.1;
- d.largestep = 10.0;
d.min_unbound = 0; // lower is bound
d.max_unbound = 0; // upper is bound
+ d.update_steps();
descriptors.push_back (d);
AUPlugin::default_value (uint32_t port)
{
if (port < descriptors.size()) {
- return descriptors[port].default_value;
+ return descriptors[port].normal;
}
return 0;
{
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);
//configurations in most cases. so first lets see
//if there's a configuration that keeps out==in
- audio_out = audio_in;
+ if (in.n_midi() > 0 && audio_in == 0) {
+ 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) {
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
AUPlugin::add_state (XMLNode* root) const
{
- LocaleGuard lg (X_("POSIX"));
+ LocaleGuard lg (X_("C"));
CFDataRef xmlData;
CFPropertyListRef propertyList;
{
int ret = -1;
CFPropertyListRef propertyList;
- LocaleGuard lg (X_("POSIX"));
+ LocaleGuard lg (X_("C"));
if (node.name() != state_node_name()) {
error << _("Bad node sent to AUPlugin::set_state") << endmsg;
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;
}
void
AUPlugin::find_presets ()
{
- vector<string*>* preset_files;
- PathScanner scanner;
+ vector<string> preset_files;
user_preset_map.clear ();
- preset_files = scanner (preset_search_path, au_preset_filter, this, true, true, -1, true);
+ find_files_matching_filter (preset_files, preset_search_path, au_preset_filter, this, true, true, true);
- if (!preset_files) {
+ if (preset_files.empty()) {
+ DEBUG_TRACE (DEBUG::AudioUnits, "AU No Preset Files found for given plugin.\n");
return;
}
- for (vector<string*>::iterator x = preset_files->begin(); x != preset_files->end(); ++x) {
+ for (vector<string>::iterator x = preset_files.begin(); x != preset_files.end(); ++x) {
- string path = *(*x);
+ string path = *x;
string preset_name;
/* make an initial guess at the preset name using the path */
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));
}
- delete *x;
}
- delete preset_files;
-
/* now fill the vector<string> with the names we have */
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 ...)"));
}
+ // create crash log file
+ au_start_crashlog ();
PluginInfoList* plugs = new PluginInfoList;
discover_generators (*plugs);
discover_instruments (*plugs);
+ // all fine if we get here
+ au_remove_crashlog ();
+
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("AU: discovered %1 plugins\n", plugs->size()));
return plugs;
discover_by_description (plugs, desc);
}
+
+bool
+AUPluginInfo::au_get_crashlog (std::string &msg)
+{
+ string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_crashlog.txt");
+ if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
+ return false;
+ }
+ std::ifstream ifs(fn.c_str());
+ msg.assign ((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
+ au_remove_crashlog ();
+ return true;
+}
+
+void
+AUPluginInfo::au_start_crashlog ()
+{
+ string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_crashlog.txt");
+ 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" << fn << "\n";
+ cerr << "Cannot create AU error-log" << fn << "\n";
+ }
+}
+
+void
+AUPluginInfo::au_remove_crashlog ()
+{
+ if (_crashlog_fd) {
+ ::fclose(_crashlog_fd);
+ _crashlog_fd = NULL;
+ }
+ string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_crashlog.txt");
+ ::g_unlink(fn.c_str());
+ DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Remove AU Log: %1\n", fn));
+}
+
+
+void
+AUPluginInfo::au_crashlog (std::string msg)
+{
+ 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
AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescription& desc)
{
Component comp = 0;
+ au_crashlog(string_compose("Start AU discovery for Type: %1", (int)desc.componentType));
comp = FindNextComponent (NULL, &desc);
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);
+ itemName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ - %@ - %@"),
+ compTypeString, compManufacturerString, compSubTypeString);
+ au_crashlog(string_compose("Scanning ID: %1", CFStringRefToStdString(itemName)));
+ if (compTypeString != NULL)
+ CFRelease(compTypeString);
+ if (compSubTypeString != NULL)
+ CFRelease(compSubTypeString);
+ if (compManufacturerString != 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->type = ARDOUR::AudioUnit;
info->unique_id = stringify_descriptor (*info->descriptor);
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