AU: standalone scanner tool

This commit is contained in:
Robin Gareus 2021-05-24 02:25:36 +02:00
parent b166db5496
commit d4212da693
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
8 changed files with 1147 additions and 3 deletions

49
gtk2_ardour/arauv2 Executable file
View File

@ -0,0 +1,49 @@
#!/bin/sh
TOP=`dirname "$0"`/..
. $TOP/build/gtk2_ardour/ardev_common_waf.sh
export UBUNTU_MENUPROXY=""
if [ $# -gt 0 ] ; then
case $1 in
-g|--gdb) DBG=gdb; shift ;;
esac
case $1 in
--valgrind) DBG=valgrind; shift ;;
esac
case $1 in
--callgrind) DBG=callgrind; shift ;;
esac
fi
if test -z "$DBG"; then
exec $TOP/build/libs/auscan/ardour-au-scanner "$@"
fi
if test "$DBG" = "valgrind"; then
export ARDOUR_RUNNING_UNDER_VALGRIND=TRUE
exec valgrind \
--error-limit=no --num-callers=50 \
--tool=memcheck \
--track-origins=yes \
--leak-check=full --show-leak-kinds=all \
--suppressions=${TOP}/tools/valgrind.supp \
$TOP/build/libs/auscan/ardour-au-scanner "$@"
fi
if test "$DBG" = "callgrind"; then
exec valgrind \
--error-limit=no --num-callers=50 \
--tool=callgrind \
--separate-callers=3 \
--separate-threads=yes \
--collect-systime=yes \
--collect-jumps=yes \
$TOP/build/libs/auscan/ardour-au-scanner "$@"
fi
if test -n "`which gdb`"; then
exec gdb --args $TOP/build/libs/auscan/ardour-au-scanner "$@"
fi
if test -n "`which lldb`"; then
exec lldb -- $TOP/build/libs/auscan/ardour-au-scanner "$@"
fi

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2021 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _ardour_auv2_scan_h_
#define _ardour_auv2_scan_h_
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>
#include "pbd/xml++.h"
#include "ardour/libardour_visibility.h"
#include "ardour/types.h"
namespace ARDOUR {
struct AUv2Info {
AUv2Info ()
: version (0)
, n_inputs (0)
, n_outputs (0)
, n_midi_inputs (0)
, n_midi_outputs (0)
, max_outputs (0)
{}
AUv2Info (XMLNode const&);
XMLNode& state () const;
std::string id;
std::string name;
std::string creator;
std::string category;
uint32_t version;
int n_inputs;
int n_outputs;
int n_midi_inputs; // has_midi_in
int n_midi_outputs; // == 0
int max_outputs;
std::vector<std::pair<int,int> > io_configs;
};
class AUv2DescStr {
public:
AUv2DescStr (std::string const& desc = "");
CAComponentDescription desc () const;
std::string to_s () const;
bool valid () const;
std::string type;
std::string subt;
std::string manu;
};
LIBARDOUR_API extern void
auv2_blacklist (std::string const&);
LIBARDOUR_API extern void
auv2_whitelist (std::string);
LIBARDOUR_API extern bool
auv2_is_blacklisted (std::string const&);
LIBARDOUR_API extern std::string
auv2_stringify_descriptor (CAComponentDescription const&);
LIBARDOUR_API extern std::string
auv2_cache_file (CAComponentDescription const&);
LIBARDOUR_API extern std::string
auv2_valid_cache_file (CAComponentDescription const&, bool verbose = false, bool* is_new = NULL);
LIBARDOUR_API extern bool
auv2_scan_and_cache (CAComponentDescription&, boost::function<void (CAComponentDescription const&, AUv2Info const&)> cb, bool verbose = false);
LIBARDOUR_API extern void
auv2_list_plugins (std::vector<AUv2DescStr>& rv);
} // namespace ARDOUR
#endif

View File

@ -90,6 +90,7 @@ 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");
@ -160,6 +161,7 @@ static bool is_blacklisted (std::string id)
return false;
}
#endif
static OSStatus
@ -2458,6 +2460,7 @@ check_and_get_preset_name (ArdourComponent component, const string& pathstr, str
}
#if 1 // remove me
static void
#ifdef COREAUDIO105
get_names (CAComponentDescription& comp_desc, std::string& name, std::string& maker)
@ -2523,6 +2526,7 @@ get_names (ArdourComponent& comp, std::string& name, std::string& maker)
strip_whitespace_edges (name);
}
}
#endif
std::string
AUPlugin::current_preset() const
@ -2910,8 +2914,7 @@ AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescrip
bool has_midi_in = false;
AUPluginInfoPtr info (new AUPluginInfo
(boost::shared_ptr<CAComponentDescription> (new CAComponentDescription(temp))));
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
@ -3287,6 +3290,7 @@ AUPluginInfo::load_cached_info ()
}
#if 1 // code dup ? -> auv2_scan
std::string
AUPluginInfo::stringify_descriptor (const CAComponentDescription& desc)
{
@ -3305,6 +3309,7 @@ AUPluginInfo::stringify_descriptor (const CAComponentDescription& desc)
return s.str();
}
#endif
bool
AUPluginInfo::needs_midi_input () const

733
libs/ardour/auv2_scan.cc Normal file
View File

@ -0,0 +1,733 @@
/*
* Copyright (C) 2021 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <utime.h>
#include <glib.h>
#include "pbd/gstdio_compat.h"
#include "CAAudioUnit.h"
#include "CAAUParameter.h"
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioUnitUtilities.h>
#ifdef WITH_CARBON
#include <Carbon/Carbon.h>
#endif
#ifdef COREAUDIO105
#define ArdourComponent Component
#define ArdourDescription ComponentDescription
#define ArdourFindNext FindNextComponent
#else
#define ArdourComponent AudioComponent
#define ArdourDescription AudioComponentDescription
#define ArdourFindNext AudioComponentFindNext
#endif
#include <glibmm/fileutils.h>
#include <glibmm/miscutils.h>
#include "pbd/basename.h"
#include "pbd/error.h"
#include "pbd/failed_constructor.h"
#include "pbd/whitespace.h"
#include "ardour/filesystem_paths.h"
#include "ardour/auv2_scan.h"
#include "pbd/i18n.h"
using namespace std;
using namespace PBD;
void
ARDOUR::auv2_blacklist (std::string const& id)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_blacklist.txt");
FILE* f = NULL;
if (! (f = g_fopen (fn.c_str (), "a"))) {
error << "Cannot append to AU blacklist for '" << id <<"'\n";
return;
}
assert (id.find ("\n") == string::npos);
fprintf (f, "%s\n", id.c_str ());
::fclose (f);
}
void
ARDOUR::auv2_whitelist (std::string id)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_blacklist.txt");
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
warning << _("Expected AUv2 Blacklist file does not exist.") << endmsg;
return;
}
std::string bl;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return;
}
::g_unlink (fn.c_str ());
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;
}
Glib::file_set_contents (fn, bl);
}
bool
ARDOUR::auv2_is_blacklisted (std::string const& 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;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return false;
}
return bl.find (id + "\n") != string::npos;
}
/* ****************************************************************************/
ARDOUR::AUv2DescStr::AUv2DescStr (std::string const& desc)
{
if (desc.empty ()) {
return;
}
if (desc.size () == 14 && desc[4] == '-' && desc[9] == '-') {
type = desc.substr (0,4);
subt = desc.substr (5,4);
manu = desc.substr (10,4);
}
if (!valid ()) {
type = "";
subt = "";
manu = "";
}
}
std::string
ARDOUR::AUv2DescStr::to_s () const
{
return type + "-" + subt + "-" + manu;
}
bool
ARDOUR::AUv2DescStr::valid () const {
return type.size () == 4 && subt.size() == 4 && manu.size() == 4;
}
CAComponentDescription
ARDOUR::AUv2DescStr::desc () const {
CFStringRef s_type = CFStringCreateWithCString (kCFAllocatorDefault, type.c_str(), kCFStringEncodingUTF8);
CFStringRef s_subt = CFStringCreateWithCString (kCFAllocatorDefault, subt.c_str(), kCFStringEncodingUTF8);
CFStringRef s_manu = CFStringCreateWithCString (kCFAllocatorDefault, manu.c_str(), kCFStringEncodingUTF8);
OSType t = UTGetOSTypeFromString (s_type);
OSType s = UTGetOSTypeFromString (s_subt);
OSType m = UTGetOSTypeFromString (s_manu);
CAComponentDescription desc (t, s, m);
CFRelease (s_type);
CFRelease (s_subt);
CFRelease (s_manu);
return desc;
}
/* ****************************************************************************/
/* copied from ardour/utils.cc */
static string
CFStringRefToStdString(CFStringRef stringRef)
{
CFIndex size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(stringRef) , kCFStringEncodingUTF8);
char *buf = new char[size];
std::string result;
if(CFStringGetCString(stringRef, buf, size, kCFStringEncodingUTF8)) {
result = buf;
}
delete [] buf;
return result;
}
std::string
ARDOUR::auv2_stringify_descriptor (CAComponentDescription const& desc)
{
CFStringRef s_type = UTCreateStringForOSType (desc.Type ());
CFStringRef s_subt = UTCreateStringForOSType (desc.SubType ());
CFStringRef s_manu = UTCreateStringForOSType (desc.Manu ());
ARDOUR::AUv2DescStr foo;
if (s_type && s_subt && s_manu) {
char tmp[5];
CFStringGetCString (s_type, tmp, 5, kCFStringEncodingUTF8);
foo.type = tmp;
CFStringGetCString (s_subt, tmp, 5, kCFStringEncodingUTF8);
foo.subt = tmp;
CFStringGetCString (s_manu, tmp, 5, kCFStringEncodingUTF8);
foo.manu = tmp;
} else {
assert (0);
}
CFRelease (s_type);
CFRelease (s_subt);
CFRelease (s_manu);
return foo.to_s ();
}
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);
}
#if 0
if (itemName != NULL) {
CFRelease(itemName);
}
#endif
}
static void
auv2_plugin_info (ArdourComponent& comp, CAComponentDescription& desc, std::vector<ARDOUR::AUv2Info>& rv, bool verbose)
{
ARDOUR::AUv2Info info;
switch (desc.Type()) {
case kAudioUnitType_Panner:
case kAudioUnitType_OfflineEffect:
case kAudioUnitType_FormatConverter:
//
return;
case kAudioUnitType_Output:
info.category = _("Output");
break;
case kAudioUnitType_MusicDevice:
info.category = _("Instrument");
info.n_midi_inputs = 1;
break;
case kAudioUnitType_MusicEffect:
info.category = _("Effect");
info.n_midi_inputs = 1;
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;
}
info.id = ARDOUR::auv2_stringify_descriptor (desc);
#ifdef COREAUDIO105
get_names (desc, info.name, info.creator);
#else
get_names (comp, info.name, info.creator);
#endif
CAComponent cacomp (desc);
UInt32 version;
#ifdef COREAUDIO105
if (cacomp.GetResourceVersion (version) != noErr)
#else
if (cacomp.GetVersion (version) != noErr)
#endif
{
info.version = 0;
}
info.version = version;
info.max_outputs = 0;
/// DEBUG_TRACE (DEBUG::AudioUnitConfig, "get AU channel info\n");
CAAudioUnit unit;
AUChannelInfo* channel_info;
UInt32 cnt;
int ret;
try {
if (CAAudioUnit::Open (cacomp, unit) != noErr) {
return;
}
} catch (...) {
warning << string_compose (_("Could not load AU plugin %1 - ignored"), info.name) << endmsg;
return;
}
if ((ret = unit.GetChannelInfo (&channel_info, cnt)) < 0) {
return;
}
if (ret > 0) {
/* AU is expected to deal with same channel valance in and out */
info.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 && (cacomp.Desc().IsGenerator() || cacomp.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;
info.io_configs.push_back (pair<int,int> (in, out));
}
} else {
/* store each configuration */
for (uint32_t n = 0; n < cnt; ++n) {
info.io_configs.push_back (pair<int,int> (channel_info[n].inChannels, channel_info[n].outChannels));
}
}
free (channel_info);
}
/* 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.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 = possible_in;
} else {
info.n_inputs = 1;
}
if (possible_out > 0) {
info.n_outputs = possible_out;
} else {
info.n_outputs = 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));
rv.push_back (info);
}
/* ****************************************************************************/
static bool
discover_auv2 (CAComponentDescription& desc, std::vector<ARDOUR::AUv2Info>& rv, bool verbose)
{
ArdourComponent comp = ArdourFindNext (NULL, &desc);
if (comp == NULL) {
error << ("AU was not found.") << endmsg;
return false;
}
while (comp != NULL) {
CAComponentDescription temp;
#ifdef COREAUDIO105
GetComponentInfo (comp, &temp, NULL, NULL, NULL);
#else
AudioComponentGetDescription (comp, &temp);
#endif
info << ("Component loaded") << endmsg;
assert (temp.componentType == desc.componentType);
assert (temp.componentSubType == desc.componentSubType);
assert (temp.componentManufacturer == desc.componentManufacturer);
auv2_plugin_info (comp, desc, rv, verbose);
comp = ArdourFindNext (comp, &desc);
assert (comp == NULL);
}
return true;
}
static string
auv2_info_cache_dir ()
{
string dir = Glib::build_filename (ARDOUR::user_cache_directory (), "auv2");
/* if the directory doesn't exist, try to create it */
if (!Glib::file_test (dir, Glib::FILE_TEST_IS_DIR)) {
if (g_mkdir (dir.c_str (), 0700)) {
PBD::fatal << "Cannot create AudioUnit cache folder '" << dir << "'" << endmsg;
}
}
return dir;
}
#include "sha1.c"
string
ARDOUR::auv2_cache_file (CAComponentDescription const& desc)
{
std::string id = auv2_stringify_descriptor (desc);
char hash[41];
Sha1Digest s;
sha1_init (&s);
sha1_write (&s, (const uint8_t *) id.c_str(), id.size());
sha1_result_hash (&s, hash);
return Glib::build_filename (auv2_info_cache_dir (), std::string (hash) + std::string (".a2i"));
}
string
ARDOUR::auv2_valid_cache_file (CAComponentDescription const& desc, bool verbose, bool* is_new)
{
string const cache_file = ARDOUR::auv2_cache_file (desc);
if (!Glib::file_test (cache_file, Glib::FileTest (Glib::FILE_TEST_EXISTS | Glib::FILE_TEST_IS_REGULAR))) {
if (is_new) {
*is_new = true;
}
return "";
}
if (is_new) {
*is_new = false;
}
if (verbose) {
info << "Found cache file: '" << cache_file <<"'" << endmsg;
}
// TODO check version
return cache_file;
}
static bool
auv2_save_cache_file (CAComponentDescription& desc, XMLNode* root, bool verbose)
{
string const cache_file = ARDOUR::auv2_cache_file (desc);
XMLTree tree;
tree.set_root (root);
if (!tree.write (cache_file)) {
error << "Could not save AUv2 plugin cache to: " << cache_file << endmsg;
return false;
}
if (verbose) {
root->dump (std::cout, "\t");
}
return true;
}
bool
ARDOUR::auv2_scan_and_cache (CAComponentDescription& desc, boost::function<void (CAComponentDescription const&, AUv2Info const&)> cb, bool verbose)
{
XMLNode* root = new XMLNode ("AUv2Cache");
root->set_property ("version", 2);
try {
std::vector<AUv2Info> nfo;
if (!discover_auv2 (desc, nfo, verbose)) {
delete root;
return false;
}
if (nfo.empty ()) {
cerr << "No plugins matching ID: '" << auv2_stringify_descriptor (desc) << "'\n";
delete root;
return false;
}
for (std::vector<AUv2Info>::const_iterator i = nfo.begin(); i != nfo.end(); ++i) {
cb (desc, *i);
root->add_child_nocopy (i->state ());
}
} catch (...) {
cerr << "Cannot load AudioUnit plugin: '" << auv2_stringify_descriptor (desc) << "'\n";
delete root;
return false;
}
return auv2_save_cache_file (desc, root, verbose);
}
/* ****************************************************************************/
static void
index_components (std::vector<ARDOUR::AUv2DescStr>& rv, CAComponentDescription &desc)
{
ArdourComponent comp = 0;
do {
CAComponentDescription temp;
comp = ArdourFindNext (comp, &desc);
if (!comp) {
break;
}
#ifdef COREAUDIO105
GetComponentInfo (comp, &temp, NULL, NULL, NULL);
#else
AudioComponentGetDescription (comp, &temp);
#endif
CAComponent cacomp (desc);
UInt32 version;
#ifdef COREAUDIO105
if (cacomp.GetResourceVersion (version) != noErr)
#else
if (cacomp.GetVersion (version) != noErr)
#endif
{
version = 0;
//continue;
}
switch (temp.Type()) {
case kAudioUnitType_Panner:
case kAudioUnitType_OfflineEffect:
case kAudioUnitType_FormatConverter:
continue;
default:
break;
}
CFStringRef s_type = UTCreateStringForOSType (temp.componentType);
CFStringRef s_subt = UTCreateStringForOSType (temp.componentSubType);
CFStringRef s_manu = UTCreateStringForOSType (temp.componentManufacturer);
if (s_type && s_subt && s_manu) {
ARDOUR::AUv2DescStr foo;
char tmp[5];
CFStringGetCString (s_type, tmp, 5, kCFStringEncodingUTF8);
foo.type = tmp;
CFStringGetCString (s_subt, tmp, 5, kCFStringEncodingUTF8);
foo.subt = tmp;
CFStringGetCString (s_manu, tmp, 5, kCFStringEncodingUTF8);
foo.manu = tmp;
// TODO add version
rv.push_back (foo);
}
CFRelease (s_type);
CFRelease (s_subt);
CFRelease (s_manu);
}
while (true);
}
void
ARDOUR::auv2_list_plugins (std::vector<AUv2DescStr>& rv)
{
CAComponentDescription desc;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentSubType = 0;
desc.componentManufacturer = 0;
desc.componentType = kAudioUnitType_Effect;
index_components (rv, desc);
desc.componentType = kAudioUnitType_MusicEffect;
index_components (rv, desc);
desc.componentType = kAudioUnitType_Generator;
index_components (rv, desc);
desc.componentType = kAudioUnitType_MusicDevice;
index_components (rv, desc);
}
/* ****************************************************************************/
using namespace ARDOUR;
AUv2Info::AUv2Info (XMLNode const& node)
: version (0)
, n_inputs (0)
, n_outputs (0)
, n_midi_inputs (0)
, n_midi_outputs (0)
, max_outputs (0)
{
bool err = false;
if (node.name() != "AUv2Info") {
throw failed_constructor ();
}
err |= !node.get_property ("id", id);
err |= !node.get_property ("name", name);
err |= !node.get_property ("creator", creator);
err |= !node.get_property ("category", category);
err |= !node.get_property ("version", version);
err |= !node.get_property ("n_inputs", n_inputs);
err |= !node.get_property ("n_outputs", n_outputs);
err |= !node.get_property ("n_midi_inputs", n_midi_inputs);
err |= !node.get_property ("n_midi_outputs", n_midi_outputs);
err |= !node.get_property ("max_outputs", max_outputs);
const XMLNodeList children = node.children();
for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
if ((*i)->name() != X_("io_config")) {
continue;
}
int32_t in;
int32_t out;
if ((*i)->get_property (X_("in"), in) && (*i)->get_property (X_("out"), out)) {
io_configs.push_back (std::pair<int,int> (in, out));
}
}
if (err) {
throw failed_constructor ();
}
}
XMLNode&
AUv2Info::state () const
{
XMLNode* node = new XMLNode("AUv2Info");
node->set_property ("id", id);
node->set_property ("name", name);
node->set_property ("creator", creator);
node->set_property ("category", category);
node->set_property ("version", version);
node->set_property ("n_inputs", n_inputs);
node->set_property ("n_outputs", n_outputs);
node->set_property ("n_midi_inputs", n_midi_inputs);
node->set_property ("n_midi_outputs", n_midi_outputs);
node->set_property ("max_outputs", max_outputs);
for (vector<pair<int, int> >::const_iterator i = io_configs.begin(); i != io_configs.end(); ++i) {
XMLNode* child = new XMLNode (X_("io_config"));
child->set_property (X_("in"), i->first);
child->set_property (X_("out"), i->second);
node->add_child_nocopy (*child);
}
return *node;
}

View File

@ -473,7 +473,7 @@ def build(bld):
obj.use += ['libappleutility']
if bld.is_defined('AUDIOUNIT_SUPPORT'):
obj.source += [ 'audio_unit.cc' ]
obj.source += [ 'audio_unit.cc', 'auv2_scan.cc' ]
avx_sources = []
fma_sources = []

216
libs/auscan/au-scanner.cc Normal file
View File

@ -0,0 +1,216 @@
/*
* Copyright (C) 2021 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <cassert>
#include <cstdlib>
#include <cstdio>
#include <getopt.h>
#include <iostream>
#include <string>
#include <strings.h>
#include "pbd/error.h"
#include "pbd/transmitter.h"
#include "pbd/receiver.h"
#include "pbd/pbd.h"
using namespace std;
#include "../ardour/auv2_scan.cc"
#include "../ardour/filesystem_paths.cc"
using namespace PBD;
class LogReceiver : public Receiver
{
protected:
void receive (Transmitter::Channel chn, const char * str) {
const char *prefix = "";
switch (chn) {
case Transmitter::Debug:
/* ignore */
break;
case Transmitter::Info:
prefix = "[Info]: ";
break;
case Transmitter::Warning:
prefix = "[WARNING]: ";
break;
case Transmitter::Error:
prefix = "[ERROR]: ";
break;
case Transmitter::Fatal:
prefix = "[FATAL]: ";
break;
case Transmitter::Throw:
abort ();
}
std::cout << prefix << str << std::endl;
if (chn == Transmitter::Fatal) {
::exit (EXIT_FAILURE);
}
}
};
LogReceiver log_receiver;
static void auv2_plugin (CAComponentDescription const&, AUv2Info const& i)
{
info << "Found Plugin: '" << i.id << "' " << i.name << endmsg;
}
static bool
scan_auv2 (CAComponentDescription& desc, bool force, bool verbose)
{
info << "Scanning AU: " << desc.Type () << "-" << desc.SubType () << "-" << desc.Manu() << endmsg;
if (!auv2_valid_cache_file (desc, verbose).empty ()) {
if (!force) {
info << "Skipping scan." << endmsg;
return true;
}
}
if (auv2_scan_and_cache (desc, sigc::ptr_fun (&auv2_plugin), verbose)) {
info << string_compose (_("Saved AUV2 plugin cache to %1"), auv2_cache_file (desc)) << endmsg;
}
return true;
}
static void
usage ()
{
// help2man compatible format (standard GNU help-text)
printf ("ardour-au-scanner - load and index AudioUnit plugins.\n\n");
printf ("Usage: ardour-au-scanner [ OPTIONS ] <TYPE> <SUBT> <MANU>\n\n");
printf ("Options:\n\
-f, --force Force update of cache file\n\
-h, --help Display this help and exit\n\
-q, --quiet Hide usual output, only print errors\n\
-v, --verbose Give verbose output (unless quiet)\n\
-V, --version Print version information and exit\n\
\n");
printf ("\n\
This tool ...\n\
\n");
printf ("Report bugs to <http://tracker.ardour.org/>\n"
"Website: <http://ardour.org/>\n");
::exit (EXIT_SUCCESS);
}
int
main (int argc, char **argv)
{
bool print_log = true;
bool force = false;
bool verbose = false;
const char* optstring = "fhqvV";
/* clang-format off */
const struct option longopts[] = {
{ "force", no_argument, 0, 'f' },
{ "help", no_argument, 0, 'h' },
{ "quiet", no_argument, 0, 'q' },
{ "verbose", no_argument, 0, 'v' },
{ "version", no_argument, 0, 'V' },
};
/* clang-format on */
int c = 0;
while (EOF != (c = getopt_long (argc, argv, optstring, longopts, (int*)0))) {
switch (c) {
case 'V':
printf ("ardour-au-scanner version %s\n\n", VERSIONSTRING);
printf ("Copyright (C) GPL 2021 Robin Gareus <robin@gareus.org>\n");
exit (EXIT_SUCCESS);
break;
case 'f':
force = true;
break;
case 'h':
usage ();
break;
case 'q':
print_log = false;
break;
case 'v':
verbose = true;
break;
default:
std::cerr << "Error: unrecognized option. See --help for usage information.\n";
::exit (EXIT_FAILURE);
break;
}
}
if (optind + 3 != argc) {
std::cerr << "Error: Missing parameter. See --help for usage information.\n";
::exit (EXIT_FAILURE);
}
PBD::init();
if (print_log) {
log_receiver.listen_to (info);
log_receiver.listen_to (warning);
log_receiver.listen_to (error);
log_receiver.listen_to (fatal);
} else {
verbose = false;
}
bool err = false;
CFStringRef s_type = CFStringCreateWithCString (kCFAllocatorDefault, argv[optind++], kCFStringEncodingUTF8);
CFStringRef s_subt = CFStringCreateWithCString (kCFAllocatorDefault, argv[optind++], kCFStringEncodingUTF8);
CFStringRef s_manu = CFStringCreateWithCString (kCFAllocatorDefault, argv[optind], kCFStringEncodingUTF8);
OSType type = UTGetOSTypeFromString (s_type);
OSType subt = UTGetOSTypeFromString (s_subt);
OSType manu = UTGetOSTypeFromString (s_manu);
CAComponentDescription desc (type, subt, manu);
if (!scan_auv2 (desc, force, verbose)) {
err = true;
}
CFRelease (s_type);
CFRelease (s_subt);
CFRelease (s_manu);
PBD::cleanup();
if (err) {
return EXIT_FAILURE;
} else {
return EXIT_SUCCESS;
}
}

37
libs/auscan/wscript Normal file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env python
from waflib.extras import autowaf as autowaf
from waflib import TaskGen
import os
import sys
# Mandatory variables
top = '.'
out = 'build'
# needed for code used from libardour
I18N_PACKAGE = 'ardour'
def options(opt):
autowaf.set_options(opt)
def configure(conf):
conf.load('compiler_cxx')
autowaf.configure(conf)
def build(bld):
if bld.is_defined('AUDIOUNIT_SUPPORT'):
obj = bld (features = 'cxx c cxxprogram')
obj.source = 'au-scanner.cc'
obj.target = 'ardour-au-scanner'
obj.includes = [ '../pbd/', '../ardour/', '..' ]
obj.defines = [
'AU_SCANNER_APP',
'VERSIONSTRING="' + str(bld.env['VERSION']) + '"',
'PACKAGE="' + I18N_PACKAGE + str(bld.env['MAJOR']) + '"',
'LIBARDOUR="' + bld.env['lwrcase_dirname'] + '"',
'LOCALEDIR="' + os.path.join(os.path.normpath(bld.env['DATADIR']), 'locale') + '"',
]
obj.use = [ 'libpbd', 'libappleutility', 'libtemporal', 'libevoral' ]
obj.uselib = 'GIOMM DL UUID ARCHIVE CURL XML COREAUDIO AUDIOUNITS OSX'
obj.install_path = os.path.join(bld.env['LIBDIR'])
obj.cxxflags = ['-Wno-deprecated-declarations']

View File

@ -291,6 +291,7 @@ children = [
'libs/fst',
'libs/vfork',
'libs/ardouralsautil',
'libs/auscan',
]
i18n_children = [