diff --git a/gtk2_ardour/arauv2 b/gtk2_ardour/arauv2 new file mode 100755 index 0000000000..8236755656 --- /dev/null +++ b/gtk2_ardour/arauv2 @@ -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 diff --git a/libs/ardour/ardour/auv2_scan.h b/libs/ardour/ardour/auv2_scan.h new file mode 100644 index 0000000000..9101a36fa6 --- /dev/null +++ b/libs/ardour/ardour/auv2_scan.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * 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 +#include +#include + +#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 > 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 cb, bool verbose = false); + +LIBARDOUR_API extern void +auv2_list_plugins (std::vector& rv); + +} // namespace ARDOUR + +#endif diff --git a/libs/ardour/audio_unit.cc b/libs/ardour/audio_unit.cc index 6fa5b0b11a..d24adc3ae6 100644 --- a/libs/ardour/audio_unit.cc +++ b/libs/ardour/audio_unit.cc @@ -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 (new CAComponentDescription(temp)))); + AUPluginInfoPtr info (new AUPluginInfo (boost::shared_ptr (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 diff --git a/libs/ardour/auv2_scan.cc b/libs/ardour/auv2_scan.cc new file mode 100644 index 0000000000..39e367c72d --- /dev/null +++ b/libs/ardour/auv2_scan.cc @@ -0,0 +1,733 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * 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 +#include +#include +#include + +#include +#include "pbd/gstdio_compat.h" + +#include "CAAudioUnit.h" +#include "CAAUParameter.h" + +#include +#include +#include +#include +#ifdef WITH_CARBON +#include +#endif + +#ifdef COREAUDIO105 +#define ArdourComponent Component +#define ArdourDescription ComponentDescription +#define ArdourFindNext FindNextComponent +#else +#define ArdourComponent AudioComponent +#define ArdourDescription AudioComponentDescription +#define ArdourFindNext AudioComponentFindNext +#endif + +#include +#include + +#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& 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 (-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 (in, out)); + } + } else { + /* store each configuration */ + for (uint32_t n = 0; n < cnt; ++n) { + info.io_configs.push_back (pair (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 >& ioc (info.io_configs); + for (vector >::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& 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 cb, bool verbose) +{ + XMLNode* root = new XMLNode ("AUv2Cache"); + root->set_property ("version", 2); + + try { + std::vector 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::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& 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& 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 (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 >::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; +} diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 141579ea34..9c63a57995 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -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 = [] diff --git a/libs/auscan/au-scanner.cc b/libs/auscan/au-scanner.cc new file mode 100644 index 0000000000..2abec55c14 --- /dev/null +++ b/libs/auscan/au-scanner.cc @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2021 Robin Gareus + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 ] \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 \n" + "Website: \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 \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; + } +} diff --git a/libs/auscan/wscript b/libs/auscan/wscript new file mode 100644 index 0000000000..3a8c02d334 --- /dev/null +++ b/libs/auscan/wscript @@ -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'] diff --git a/wscript b/wscript index f4edec444e..468e17b189 100644 --- a/wscript +++ b/wscript @@ -291,6 +291,7 @@ children = [ 'libs/fst', 'libs/vfork', 'libs/ardouralsautil', + 'libs/auscan', ] i18n_children = [