13
0

AU: use AudioUnit scanner app, overhaul AU support

This commit is contained in:
Robin Gareus 2021-06-05 23:38:27 +02:00
parent d603124743
commit 088ccd24f0
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
5 changed files with 284 additions and 933 deletions

View File

@ -144,15 +144,14 @@ class LIBARDOUR_API AUPlugin : public ARDOUR::Plugin
Float64* outCycleStartBeat,
Float64* outCycleEndBeat);
static std::string maybe_fix_broken_au_id (const std::string&);
/* this MUST be called from thread in which you want to receive notifications
about parameter changes.
*/
/* this MUST be called from thread in which you want to receive notifications
* about parameter changes.
*/
int create_parameter_listener (AUEventListenerProc callback, void *arg, float interval_secs);
/* these can be called from any thread but SHOULD be called from the same thread
that will receive parameter change notifications.
*/
/* these can be called from any thread but SHOULD be called from the same thread
* that will receive parameter change notifications.
*/
int listen_to_parameter (uint32_t param_id);
int end_listen_to_parameter (uint32_t param_id);
@ -238,14 +237,8 @@ class LIBARDOUR_API AUPlugin : public ARDOUR::Plugin
void parameter_change_listener (void* /*arg*/, void* /*src*/, const AudioUnitEvent* event, UInt64 host_time, Float32 new_value);
};
typedef boost::shared_ptr<AUPlugin> AUPluginPtr;
struct LIBARDOUR_API AUPluginCachedInfo {
std::vector<std::pair<int,int> > io_configs;
};
class LIBARDOUR_API AUPluginInfo : public PluginInfo {
public:
public:
AUPluginInfo (boost::shared_ptr<CAComponentDescription>);
~AUPluginInfo () {}
@ -262,42 +255,17 @@ class LIBARDOUR_API AUPluginInfo : public PluginInfo {
bool is_instrument () const;
bool is_utility () const;
AUPluginCachedInfo cache;
bool reconfigurable_io() const { return true; }
uint32_t max_configurable_ouputs () const { return max_outputs; }
static void clear_cache ();
static PluginInfoList* discover (bool scan_only);
static bool au_get_crashlog (std::string &msg);
static std::string stringify_descriptor (const CAComponentDescription&);
static int load_cached_info ();
private:
boost::shared_ptr<CAComponentDescription> descriptor;
UInt32 version;
uint32_t max_outputs;
static FILE * _crashlog_fd;
static bool _scan_only;
std::vector<std::pair<int,int> > io_configs;
static void au_start_crashlog (void);
static void au_remove_crashlog (void);
static void au_crashlog (std::string);
static std::string convert_old_unique_id (std::string const&);
static void discover_music (PluginInfoList&);
static void discover_fx (PluginInfoList&);
static void discover_generators (PluginInfoList&);
static void discover_instruments (PluginInfoList&);
static void discover_by_description (PluginInfoList&, CAComponentDescription&);
static Glib::ustring au_cache_path ();
typedef std::map<std::string,AUPluginCachedInfo> CachedInfoMap;
static CachedInfoMap cached_info;
static int cached_io_configuration (const std::string&, UInt32, CAComponent&, AUPluginCachedInfo&, const std::string& name);
static void add_cached_info (const std::string&, AUPluginCachedInfo&);
static void save_cached_info ();
private:
boost::shared_ptr<CAComponentDescription> descriptor;
};
typedef boost::shared_ptr<AUPluginInfo> AUPluginInfoPtr;

View File

@ -40,6 +40,10 @@
#include "ardour/plugin.h"
#include "ardour/plugin_scan_result.h"
#ifdef AUDIOUNIT_SUPPORT
class CAComponentDescription;
#endif
namespace ARDOUR {
class Plugin;
@ -52,9 +56,16 @@ struct VST3Info;
struct VST2Info;
#endif
#ifdef AUDIOUNIT_SUPPORT
struct AUv2Info;
struct AUv2DescStr;
#endif
class LIBARDOUR_API PluginManager : public boost::noncopyable {
public:
static PluginManager& instance();
static std::string auv2_scanner_bin_path;
static std::string vst2_scanner_bin_path;
static std::string vst3_scanner_bin_path;
@ -295,7 +306,12 @@ private:
void add_lxvst_presets ();
void add_presets (std::string domain);
#ifdef AUDIOUNIT_SUPPORT
void au_refresh (bool cache_only = false);
void auv2_plugin (CAComponentDescription const&, AUv2Info const&);
int auv2_discover (AUv2DescStr const&, bool);
bool run_auv2_scanner_app (CAComponentDescription const&, AUv2DescStr const&, PSLEPtr) const;
#endif
void lv2_refresh ();

View File

@ -29,6 +29,8 @@
#include <math.h>
#include <ctype.h>
#include <boost/algorithm/string.hpp>
#include "pbd/gstdio_compat.h"
#include "pbd/transmitter.h"
#include "pbd/xml++.h"
@ -45,6 +47,7 @@
#include "ardour/audio_unit.h"
#include "ardour/audioengine.h"
#include "ardour/audio_buffer.h"
#include "ardour/auv2_scan.h"
#include "ardour/debug.h"
#include "ardour/filesystem_paths.h"
#include "ardour/io.h"
@ -81,88 +84,9 @@ using namespace std;
using namespace PBD;
using namespace ARDOUR;
AUPluginInfo::CachedInfoMap AUPluginInfo::cached_info;
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;
#if 1 // remove me -> libs/ardour/auv2_scan.cc
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;
}
#endif
static OSStatus
_render_callback(void *userData,
@ -574,32 +498,13 @@ AUPlugin::init ()
g_atomic_int_set (&_current_latency, UINT_MAX);
OSErr err;
CFStringRef itemName;
/* these keep track of *configured* channel set up,
not potential set ups.
*/
* not potential set ups.
*/
input_channels = -1;
output_channels = -1;
{
CAComponentDescription temp;
#ifdef COREAUDIO105
GetComponentInfo (comp.get()->Comp(), &temp, NULL, NULL, NULL);
#else
AudioComponentGetDescription (comp.get()->Comp(), &temp);
#endif
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::AudioUnitConfig, "opening AudioUnit\n");
@ -697,9 +602,6 @@ AUPlugin::init ()
discover_factory_presets ();
// Plugin::setup_controls ();
au_unblacklist(CFStringRefToStdString(itemName));
if (itemName != NULL) CFRelease(itemName);
}
void
@ -826,115 +728,11 @@ AUPlugin::discover_parameters ()
}
}
static unsigned int
four_ints_to_four_byte_literal (unsigned char n[4])
{
/* this is actually implementation dependent. sigh. this is what gcc
and quite a few others do.
*/
return ((n[0] << 24) + (n[1] << 16) + (n[2] << 8) + n[3]);
}
std::string
AUPlugin::maybe_fix_broken_au_id (const std::string& id)
{
if (isdigit (id[0])) {
return id;
}
/* ID format is xxxx-xxxx-xxxx
where x maybe \xNN or a printable character.
Split at the '-' and and process each part into an integer.
Then put it back together.
*/
unsigned char nascent[4];
const char* cstr = id.c_str();
const char* estr = cstr + id.size();
uint32_t n[3];
int in;
int next_int;
char short_buf[3];
stringstream s;
in = 0;
next_int = 0;
short_buf[2] = '\0';
while (*cstr && next_int < 4) {
if (*cstr == '\\') {
if (estr - cstr < 3) {
/* too close to the end for \xNN parsing: treat as literal characters */
nascent[in] = *cstr;
++cstr;
++in;
} else {
if (cstr[1] == 'x' && isxdigit (cstr[2]) && isxdigit (cstr[3])) {
/* parse \xNN */
memcpy (short_buf, &cstr[2], 2);
nascent[in] = strtol (short_buf, NULL, 16);
cstr += 4;
++in;
} else {
/* treat as literal characters */
nascent[in] = *cstr;
++cstr;
++in;
}
}
} else {
nascent[in] = *cstr;
++cstr;
++in;
}
if (in && (in % 4 == 0)) {
/* nascent is ready */
n[next_int] = four_ints_to_four_byte_literal (nascent);
in = 0;
next_int++;
/* swallow space-hyphen-space */
if (next_int < 3) {
++cstr;
++cstr;
++cstr;
}
}
}
if (next_int != 3) {
goto err;
}
s << n[0] << '-' << n[1] << '-' << n[2];
return s.str();
err:
return string();
}
string
AUPlugin::unique_id () const
{
return AUPluginInfo::stringify_descriptor (comp->Desc());
assert (_info->unique_id == auv2_stringify_descriptor (comp->Desc()));
return auv2_stringify_descriptor (comp->Desc());
}
const char *
@ -1327,7 +1125,7 @@ AUPlugin::match_variable_io (ChanCount& in, ChanCount& aux_in, ChanCount& out)
const int32_t preferred_out = out.n_audio ();
AUPluginInfoPtr pinfo = boost::dynamic_pointer_cast<AUPluginInfo>(get_info());
vector<pair<int,int> > io_configs = pinfo->cache.io_configs;
vector<pair<int,int> > io_configs = pinfo->io_configs;
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::AudioUnitConfig)) {
@ -1345,7 +1143,7 @@ AUPlugin::match_variable_io (ChanCount& in, ChanCount& aux_in, ChanCount& out)
bool outs_added = false;
#endif
if (output_elements > 1) {
const vector<pair<int,int> >& ioc (pinfo->cache.io_configs);
const vector<pair<int,int> >& ioc (pinfo->io_configs);
for (vector<pair<int,int> >::const_iterator i = ioc.begin(); i != ioc.end(); ++i) {
int32_t possible_in = i->first;
int32_t possible_out = i->second;
@ -2459,75 +2257,6 @@ check_and_get_preset_name (ArdourComponent component, const string& pathstr, str
return true;
}
#if 1 // remove me
static void
#ifdef COREAUDIO105
get_names (CAComponentDescription& comp_desc, std::string& name, std::string& maker)
#else
get_names (ArdourComponent& comp, std::string& name, std::string& maker)
#endif
{
CFStringRef itemName = NULL;
// Marc Poirier-style item name
#ifdef COREAUDIO105
CAComponent auComponent (comp_desc);
if (auComponent.IsValid()) {
CAComponentDescription dummydesc;
Handle nameHandle = NewHandle(sizeof(void*));
if (nameHandle != NULL) {
OSErr err = GetComponentInfo(auComponent.Comp(), &dummydesc, nameHandle, NULL, NULL);
if (err == noErr) {
ConstStr255Param nameString = (ConstStr255Param) (*nameHandle);
if (nameString != NULL) {
itemName = CFStringCreateWithPascalString(kCFAllocatorDefault, nameString, CFStringGetSystemEncoding());
}
}
DisposeHandle(nameHandle);
}
}
#else
assert (comp);
AudioComponentCopyName (comp, &itemName);
#endif
// if Marc-style fails, do the original way
if (itemName == NULL) {
#ifndef COREAUDIO105
CAComponentDescription comp_desc;
AudioComponentGetDescription (comp, &comp_desc);
#endif
CFStringRef compTypeString = UTCreateStringForOSType(comp_desc.componentType);
CFStringRef compSubTypeString = UTCreateStringForOSType(comp_desc.componentSubType);
CFStringRef compManufacturerString = UTCreateStringForOSType(comp_desc.componentManufacturer);
itemName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ - %@ - %@"),
compTypeString, compManufacturerString, compSubTypeString);
if (compTypeString != NULL)
CFRelease(compTypeString);
if (compSubTypeString != NULL)
CFRelease(compSubTypeString);
if (compManufacturerString != NULL)
CFRelease(compManufacturerString);
}
string str = CFStringRefToStdString(itemName);
string::size_type colon = str.find (':');
if (colon) {
name = str.substr (colon+1);
maker = str.substr (0, colon);
strip_whitespace_edges (maker);
strip_whitespace_edges (name);
} else {
name = str;
maker = "unknown";
strip_whitespace_edges (name);
}
}
#endif
std::string
AUPlugin::current_preset() const
{
@ -2608,10 +2337,12 @@ AUPlugin::has_editor () const
return true;
}
/* ****************************************************************************/
AUPluginInfo::AUPluginInfo (boost::shared_ptr<CAComponentDescription> d)
: descriptor (d)
, version (0)
: version (0)
, max_outputs (0)
, descriptor (d)
{
type = ARDOUR::AudioUnit;
}
@ -2724,593 +2455,6 @@ AUPluginInfo::get_presets (bool user_only) const
return p;
}
Glib::ustring
AUPluginInfo::au_cache_path ()
{
return Glib::build_filename (ARDOUR::user_cache_directory(), "au_cache");
}
PluginInfoList*
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 ...)"));
// flush RAM cache -- after clear_cache()
cached_info.clear();
}
// create crash log file
au_start_crashlog ();
PluginInfoList* plugs = new PluginInfoList;
discover_fx (*plugs);
discover_music (*plugs);
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;
}
void
AUPluginInfo::discover_music (PluginInfoList& plugs)
{
CAComponentDescription desc;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentSubType = 0;
desc.componentManufacturer = 0;
desc.componentType = kAudioUnitType_MusicEffect;
discover_by_description (plugs, desc);
}
void
AUPluginInfo::discover_fx (PluginInfoList& plugs)
{
CAComponentDescription desc;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentSubType = 0;
desc.componentManufacturer = 0;
desc.componentType = kAudioUnitType_Effect;
discover_by_description (plugs, desc);
}
void
AUPluginInfo::discover_generators (PluginInfoList& plugs)
{
CAComponentDescription desc;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentSubType = 0;
desc.componentManufacturer = 0;
desc.componentType = kAudioUnitType_Generator;
discover_by_description (plugs, desc);
}
void
AUPluginInfo::discover_instruments (PluginInfoList& plugs)
{
CAComponentDescription desc;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentSubType = 0;
desc.componentManufacturer = 0;
desc.componentType = kAudioUnitType_MusicDevice;
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::AudioUnitConfig, 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::AudioUnitConfig, 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)
{
ArdourComponent comp = 0;
au_crashlog(string_compose("Start AU discovery for Type: %1", (int)desc.componentType));
comp = ArdourFindNext (NULL, &desc);
while (comp != NULL) {
CAComponentDescription temp;
#ifdef COREAUDIO105
GetComponentInfo (comp, &temp, NULL, NULL, NULL);
#else
AudioComponentGetDescription (comp, &temp);
#endif
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;
if (itemName != NULL) {
CFRelease(itemName);
itemName = NULL;
}
comp = ArdourFindNext (comp, &desc);
continue;
}
bool has_midi_in = false;
AUPluginInfoPtr info (new AUPluginInfo (boost::shared_ptr<CAComponentDescription> (new CAComponentDescription(temp))));
/* although apple designed the subtype field to be a "category" indicator,
its really turned into a plugin ID field for a given manufacturer. Hence
there are no categories for AudioUnits. However, to keep the plugins
showing up under "categories", we'll use the "type" as a high level
selector.
NOTE: no panners, format converters or i/o AU's for our purposes
*/
switch (info->descriptor->Type()) {
case kAudioUnitType_Panner:
case kAudioUnitType_OfflineEffect:
case kAudioUnitType_FormatConverter:
comp = ArdourFindNext (comp, &desc);
continue;
case kAudioUnitType_Output:
info->category = _("Output");
break;
case kAudioUnitType_MusicDevice:
info->category = _("Instrument");
has_midi_in = true;
break;
case kAudioUnitType_MusicEffect:
info->category = _("Effect");
has_midi_in = true;
break;
case kAudioUnitType_Effect:
info->category = _("Effect");
break;
case kAudioUnitType_Mixer:
info->category = _("Mixer");
break;
case kAudioUnitType_Generator:
info->category = _("Generator");
break;
default:
info->category = _("(Unknown)");
break;
}
au_blacklist(CFStringRefToStdString(itemName));
#ifdef COREAUDIO105
get_names (temp, info->name, info->creator);
#else
get_names (comp, info->name, info->creator);
#endif
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);
/* XXX not sure of the best way to handle plugin versioning yet */
CAComponent cacomp (*info->descriptor);
#ifdef COREAUDIO105
if (cacomp.GetResourceVersion (info->version) != noErr)
#else
if (cacomp.GetVersion (info->version) != noErr)
#endif
{
info->version = 0;
}
const int rv = cached_io_configuration (info->unique_id, info->version, cacomp, info->cache, info->name);
info->max_outputs = 0;
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
remove any use of these values.
for now, if the plugin provides a wildcard, treat it as 1. we really
don't care much, because whether we can handle an i/o configuration
depends upon ::configure_variable_io(), not these counts.
they exist because other parts of ardour try to present i/o configuration
info to the user, which should perhaps be revisited.
*/
const vector<pair<int,int> >& ioc (info->cache.io_configs);
for (vector<pair<int,int> >::const_iterator i = ioc.begin(); i != ioc.end(); ++i) {
int32_t possible_out = i->second;
if (possible_out < 0) {
continue;
} else if (possible_out > info->max_outputs) {
info->max_outputs = possible_out;
}
}
int32_t possible_in = ioc.front().first;
int32_t possible_out = ioc.front().second;
if (possible_in > 0) {
info->n_inputs.set (DataType::AUDIO, possible_in);
} else {
info->n_inputs.set (DataType::AUDIO, 1);
}
info->n_inputs.set (DataType::MIDI, has_midi_in ? 1 : 0);
if (possible_out > 0) {
info->n_outputs.set (DataType::AUDIO, possible_out);
} else {
info->n_outputs.set (DataType::AUDIO, 1);
}
DEBUG_TRACE (DEBUG::AudioUnitConfig, string_compose ("detected AU %1 with %2 i/o configurations - %3\n",
info->name.c_str(), info->cache.io_configs.size(), info->unique_id));
plugs.push_back (info);
}
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 = ArdourFindNext (comp, &desc);
if (itemName != NULL) CFRelease(itemName); itemName = NULL;
}
au_crashlog(string_compose("End AU discovery for Type: %1", (int)desc.componentType));
}
int
AUPluginInfo::cached_io_configuration (const std::string& unique_id,
UInt32 version,
CAComponent& comp,
AUPluginCachedInfo& cinfo,
const std::string& name)
{
std::string id;
char buf[32];
/* concatenate unique ID with version to provide a key for cached info lookup.
this ensures we don't get stale information, or should if plugin developers
follow Apple "guidelines".
*/
snprintf (buf, sizeof (buf), "%u", (uint32_t) version);
id = unique_id;
id += '/';
id += buf;
CachedInfoMap::iterator cim = cached_info.find (id);
if (cim != cached_info.end()) {
cinfo = cim->second;
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;
AUChannelInfo* channel_info;
UInt32 cnt;
int ret;
ARDOUR::BootMessage (string_compose (_("Checking AudioUnit: %1"), name));
try {
if (CAAudioUnit::Open (comp, unit) != noErr) {
return -1;
}
} catch (...) {
warning << string_compose (_("Could not load AU plugin %1 - ignored"), name) << endmsg;
return -1;
}
DEBUG_TRACE (DEBUG::AudioUnitConfig, "get AU channel info\n");
if ((ret = unit.GetChannelInfo (&channel_info, cnt)) < 0) {
return -1;
}
if (ret > 0) {
/* AU is expected to deal with same channel valance in and out */
cinfo.io_configs.push_back (pair<int,int> (-1, -1));
} else {
/* CAAudioUnit::GetChannelInfo silently merges bus formats
* check if this was the case and if so, add
* bus configs as incremental options.
*/
Boolean* isWritable = 0;
UInt32 dataSize = 0;
OSStatus result = AudioUnitGetPropertyInfo (unit.AU(),
kAudioUnitProperty_SupportedNumChannels,
kAudioUnitScope_Global, 0,
&dataSize, isWritable);
if (result != noErr && (comp.Desc().IsGenerator() || comp.Desc().IsMusicDevice())) {
/* incrementally add busses */
int in = 0;
int out = 0;
for (uint32_t n = 0; n < cnt; ++n) {
in += channel_info[n].inChannels;
out += channel_info[n].outChannels;
cinfo.io_configs.push_back (pair<int,int> (in, out));
}
} else {
/* store each configuration */
for (uint32_t n = 0; n < cnt; ++n) {
cinfo.io_configs.push_back (pair<int,int> (channel_info[n].inChannels,
channel_info[n].outChannels));
}
}
free (channel_info);
}
add_cached_info (id, cinfo);
save_cached_info ();
return 0;
}
void
AUPluginInfo::clear_cache ()
{
const string& fn = au_cache_path();
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
::g_unlink(fn.c_str());
}
// keep cached_info in RAM until restart or re-scan
cached_info.clear();
}
void
AUPluginInfo::add_cached_info (const std::string& id, AUPluginCachedInfo& cinfo)
{
cached_info[id] = cinfo;
}
#define AU_CACHE_VERSION "2.0"
void
AUPluginInfo::save_cached_info ()
{
XMLNode* node;
node = new XMLNode (X_("AudioUnitPluginCache"));
node->set_property( "version", AU_CACHE_VERSION );
for (map<string,AUPluginCachedInfo>::iterator i = cached_info.begin(); i != cached_info.end(); ++i) {
XMLNode* parent = new XMLNode (X_("plugin"));
parent->set_property ("id", i->first);
node->add_child_nocopy (*parent);
for (vector<pair<int, int> >::iterator j = i->second.io_configs.begin(); j != i->second.io_configs.end(); ++j) {
XMLNode* child = new XMLNode (X_("io"));
child->set_property (X_("in"), j->first);
child->set_property (X_("out"), j->second);
parent->add_child_nocopy (*child);
}
}
Glib::ustring path = au_cache_path ();
XMLTree tree;
tree.set_root (node);
if (!tree.write (path)) {
error << string_compose (_("could not save AU cache to %1"), path) << endmsg;
g_unlink (path.c_str());
}
}
int
AUPluginInfo::load_cached_info ()
{
Glib::ustring path = au_cache_path ();
XMLTree tree;
if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
return 0;
}
if ( !tree.read (path) ) {
error << "au_cache is not a valid XML file. AU plugins will be re-scanned" << endmsg;
return -1;
}
const XMLNode* root (tree.root());
if (root->name() != X_("AudioUnitPluginCache")) {
return -1;
}
//initial version has incorrectly stored i/o info, and/or garbage chars.
XMLProperty const * version = root->property(X_("version"));
if (! ((version != NULL) && (version->value() == X_(AU_CACHE_VERSION)))) {
error << "au_cache is not correct version. AU plugins will be re-scanned" << endmsg;
return -1;
}
cached_info.clear ();
const XMLNodeList children = root->children();
for (XMLNodeConstIterator iter = children.begin(); iter != children.end(); ++iter) {
const XMLNode* child = *iter;
if (child->name() == X_("plugin")) {
const XMLNode* gchild;
const XMLNodeList gchildren = child->children();
string id;
if (!child->get_property (X_("id"), id)) {
continue;
}
string fixed;
string version;
string::size_type slash = id.find_last_of ('/');
if (slash == string::npos) {
continue;
}
version = id.substr (slash);
id = id.substr (0, slash);
fixed = AUPlugin::maybe_fix_broken_au_id (id);
if (fixed.empty()) {
error << string_compose (_("Your AudioUnit configuration cache contains an AU plugin whose ID cannot be understood - ignored (%1)"), id) << endmsg;
continue;
}
id = fixed;
id += version;
AUPluginCachedInfo cinfo;
for (XMLNodeConstIterator giter = gchildren.begin(); giter != gchildren.end(); giter++) {
gchild = *giter;
if (gchild->name() == X_("io")) {
int32_t in;
int32_t out;
if (gchild->get_property (X_("in"), in) && gchild->get_property (X_("out"), out)) {
cinfo.io_configs.push_back (pair<int,int> (in, out));
}
}
}
if (cinfo.io_configs.size()) {
add_cached_info (id, cinfo);
}
}
}
return 0;
}
#if 1 // code dup ? -> auv2_scan
std::string
AUPluginInfo::stringify_descriptor (const CAComponentDescription& desc)
{
stringstream s;
/* note: OSType is a compiler-implemenation-defined value,
historically a 32 bit integer created with a multi-character
constant such as 'abcd'. It is, fundamentally, an abomination.
*/
s << desc.Type();
s << '-';
s << desc.SubType();
s << '-';
s << desc.Manu();
return s.str();
}
#endif
bool
AUPluginInfo::needs_midi_input () const
{
@ -3348,6 +2492,21 @@ AUPluginInfo::is_utility () const
// kAudioUnitType_MidiProcessor ..looks like we aren't even scanning for these yet?
}
std::string
AUPluginInfo::convert_old_unique_id (std::string const& id)
{
vector<std::string> p;
boost::split (p, id, boost::is_any_of ("-"));
if (p.size () == 3) {
OSType t (PBD::atoi (p[0]));
OSType s (PBD::atoi (p[1]));
OSType m (PBD::atoi (p[2]));
CAComponentDescription desc (t, s, m);
return auv2_stringify_descriptor (desc);
}
return id;
}
void
AUPlugin::set_info (PluginInfoPtr info)
{

View File

@ -65,10 +65,6 @@
#include "ardour/linux_vst_support.h"
#endif
#ifdef AUDIOUNIT_SUPPORT
#include "ardour/audio_unit.h"
#endif
#if defined(__SSE__) || defined(USE_XMMINTRIN)
#include <xmmintrin.h>
#endif
@ -615,10 +611,6 @@ ARDOUR::init (bool use_windows_vst, bool try_optimization, const char* localedir
}
#endif
#ifdef AUDIOUNIT_SUPPORT
AUPluginInfo::load_cached_info ();
#endif
setup_hardware_optimization (try_optimization);
if (Config->get_cpu_dma_latency () >= 0) {

View File

@ -104,7 +104,14 @@
#endif
#ifdef AUDIOUNIT_SUPPORT
#include "CAAudioUnit.h"
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioUnitUtilities.h>
#include "ardour/audio_unit.h"
#include "ardour/auv2_scan.h"
#include <Carbon/Carbon.h>
#endif
@ -127,6 +134,7 @@ using namespace PBD;
using namespace std;
PluginManager* PluginManager::_instance = 0;
std::string PluginManager::auv2_scanner_bin_path = "";
std::string PluginManager::vst2_scanner_bin_path = "";
std::string PluginManager::vst3_scanner_bin_path = "";
@ -230,6 +238,14 @@ PluginManager::PluginManager ()
#endif // VST3_SUPPORT
#endif // any VST
#ifdef AUDIOUNIT_SUPPORT
PBD::Searchpath ausp (Glib::build_filename(ARDOUR::ardour_dll_directory(), "auscan"));
ausp += ARDOUR::ardour_dll_directory();
if (!PBD::find_file (ausp, "ardour-au-scanner" , auv2_scanner_bin_path)) {
PBD::warning << "AUv2 scanner app (ardour-au-scanner) not found in path " << ausp.to_string() << endmsg;
}
#endif
load_statuses ();
load_tags ();
load_stats ();
@ -660,7 +676,12 @@ void
PluginManager::clear_au_cache ()
{
#ifdef AUDIOUNIT_SUPPORT
AUPluginInfo::clear_cache ();
string dn = Glib::build_filename (ARDOUR::user_cache_directory(), "auv2");
vector<string> a2i_files;
find_files_matching_regex (a2i_files, dn, "\\.a2i$", false);
for (vector<string>::iterator i = a2i_files.begin(); i != a2i_files.end (); ++i) {
::g_unlink(i->c_str());
}
Config->set_plugin_cache_version (0);
Config->save_state();
#endif
@ -1003,25 +1024,220 @@ PluginManager::lv2_refresh ()
}
#ifdef AUDIOUNIT_SUPPORT
static void auv2_scanner_log (std::string msg, PluginScanLogEntry* psle)
{
psle->msg (PluginScanLogEntry::OK, msg);
}
bool
PluginManager::run_auv2_scanner_app (CAComponentDescription const& desc, AUv2DescStr const& d, PSLEPtr psle) const
{
char **argp= (char**) calloc (7, sizeof (char*));
argp[0] = strdup (auv2_scanner_bin_path.c_str ());
argp[1] = strdup ("-f");
argp[2] = strdup ("--");
argp[3] = strdup (d.type.c_str());
argp[4] = strdup (d.subt.c_str());
argp[5] = strdup (d.manu.c_str());
argp[6] = 0;
ARDOUR::SystemExec scanner (auv2_scanner_bin_path, argp);
PBD::ScopedConnection c;
scanner.ReadStdout.connect_same_thread (c, boost::bind (&auv2_scanner_log, _1, &(*psle)));
if (scanner.start (ARDOUR::SystemExec::MergeWithStdin)) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cannot launch AU scanner app '%1': %2"), auv2_scanner_bin_path, strerror (errno)));
return false;
}
int timeout = Config->get_vst_scan_timeout(); // deciseconds
bool notime = (timeout <= 0);
while (scanner.is_running () && (notime || timeout > 0)) {
if (!notime && no_timeout ()) {
notime = true;
timeout = -1;
}
ARDOUR::PluginScanTimeout (timeout);
--timeout;
Glib::usleep (100000);
if (cancelled () || (!notime && timeout == 0)) {
scanner.terminate ();
if (cancelled ()) {
psle->msg (PluginScanLogEntry::New, "Scan was cancelled.");
} else {
psle->msg (PluginScanLogEntry::TimeOut, "Scan Timed Out.");
}
/* may be partially written */
g_unlink (auv2_cache_file (desc).c_str ());
auv2_whitelist (d.to_s ());
return false;
}
}
return true;
}
void
PluginManager::auv2_plugin (CAComponentDescription const& desc, AUv2Info const& nfo)
{
PSLEPtr psle (scan_log_entry (AudioUnit, auv2_stringify_descriptor (desc)));
AUPluginInfoPtr info (new AUPluginInfo (boost::shared_ptr<CAComponentDescription> (new CAComponentDescription (desc))));
psle->msg (PluginScanLogEntry::OK);
info->unique_id = nfo.id;
info->name = nfo.name;
info->creator = nfo.creator;
info->category = nfo.category;
info->version = nfo.version;
info->max_outputs = nfo.max_outputs;
info->io_configs = nfo.io_configs;
_au_plugin_info->push_back (info);
psle->add (info);
}
int
PluginManager::auv2_discover (AUv2DescStr const& d, bool cache_only)
{
std::string dstr = d.to_s ();
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("checking AU plugin at %1\n", dstr));
PSLEPtr psle (scan_log_entry (AudioUnit, dstr));
if (auv2_is_blacklisted (dstr)) {
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
CAComponentDescription desc (d.desc ());
bool run_scan = false;
bool is_new = false;
string cache_file = auv2_valid_cache_file (desc, false, &is_new);
if (!cache_only && auv2_scanner_bin_path.empty () && cache_file.empty ()) {
/* scan in host context */
psle->reset ();
auv2_blacklist (dstr);
psle->msg (PluginScanLogEntry::OK, "(internal scan)");
if (!auv2_scan_and_cache (desc, sigc::mem_fun (*this, &PluginManager::auv2_plugin), false)) {
psle->msg (PluginScanLogEntry::Error, "Cannot load AUv2");
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
psle->msg (PluginScanLogEntry::OK, string_compose (_("Saved AUV2 plugin cache to %1"), auv2_cache_file (desc)));
auv2_whitelist (dstr);
return 0;
}:
XMLTree tree;
if (cache_file.empty ()) {
run_scan = true;
} else if (tree.read (cache_file)) {
/* valid cache file was found, now check version */
int cf_version = 0;
if (!tree.root()->get_property ("version", cf_version) || cf_version < 2) {
run_scan = true;
}
} else {
/* failed to parse XML */
run_scan = true;
}
if (!cache_only && run_scan) {
/* re/generate cache file */
psle->reset ();
auv2_blacklist (dstr);
if (!run_auv2_scanner_app (desc, d, psle)) {
return -1;
}
cache_file = auv2_cache_file (desc);
if (cache_file.empty ()) {
psle->msg (PluginScanLogEntry::Error, _("Scan Failed."));
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
/* re-read cache file */
if (!tree.read (cache_file)) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cannot parse AUv2 cache file '%1' for plugin '%2'"), cache_file, dstr));
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
run_scan = false; // mark as scanned
}
if (cache_file.empty () || run_scan) {
/* cache file does not exist and cache_only == true,
* or cache file is invalid (scan needed)
*/
psle->msg (is_new ? PluginScanLogEntry::New : PluginScanLogEntry::Updated);
return -1;
}
auv2_whitelist (dstr);
psle->set_result (PluginScanLogEntry::OK);
uint32_t discovered = 0;
for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) {
try {
AUv2Info nfo (**i);
if (nfo.id != dstr) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cache file %1 ID mismatch '%2' vs '%3'"), cache_file, nfo.id, dstr));
continue;
}
auv2_plugin (desc, nfo);
++discovered;
} catch (...) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Corrupt AUv2 cache file '%1'"), cache_file));
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot load AUv2 '%1'\n", dstr));
}
}
return discovered;
}
void
PluginManager::au_refresh (bool cache_only)
{
DEBUG_TRACE (DEBUG::PluginManager, "AU: refresh\n");
delete _au_plugin_info;
_au_plugin_info = new ARDOUR::PluginInfoList();
bool discover_at_start = Config->get_discover_audio_units ();
if (discover_at_start) {
/* disable automatic discovery in case scanning crashes */
Config->set_discover_audio_units (false);
Config->save_state();
if (!Config->get_discover_audio_units ()) { // TODO rename: enable AU
return;
}
delete _au_plugin_info;
_au_plugin_info = AUPluginInfo::discover(cache_only && !discover_at_start);
ARDOUR::PluginScanMessage(_("AUv2"), _("Indexing"), false);
/* disable AU in case indexing crashes */
Config->set_discover_audio_units (false);
Config->save_state();
if (discover_at_start) {
/* successful scan re-enabled automatic discovery if it was set */
Config->set_discover_audio_units (discover_at_start);
Config->save_state();
string aucrsh = Glib::build_filename (ARDOUR::user_cache_directory(), "au_crash");
g_file_set_contents (aucrsh.c_str(), "", -1, NULL);
std::vector<AUv2DescStr> audesc;
auv2_list_plugins (audesc);
/* successful, re-enabled AU support */
Config->set_discover_audio_units (true);
Config->save_state();
::g_unlink (aucrsh.c_str());
for (std::vector<AUv2DescStr>::const_iterator i = audesc.begin (); i != audesc.end (); ++i) {
ARDOUR::PluginScanMessage(_("AUv2"), i->to_s(), !cache_only && !cancelled());
auv2_discover (*i, cache_only);
}
for (PluginInfoList::iterator i = _au_plugin_info->begin(); i != _au_plugin_info->end(); ++i) {