Robin Gareus
74c4ca3e52
the rest from `tools/convert_boost.sh`. * replace boost::function, boost::bind with std::function and std::bind. This required some manual fixes, notably std::placeholders, some static_casts<>, and boost::function::clear -> = {}.
624 lines
16 KiB
C++
624 lines
16 KiB
C++
/*
|
|
* 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
|
|
|
|
#undef nil
|
|
|
|
|
|
#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;
|
|
|
|
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
|
|
get_names (AudioComponent& comp, std::string& name, std::string& maker)
|
|
{
|
|
CFStringRef itemName = NULL;
|
|
// Marc Poirier-style item name
|
|
assert (comp);
|
|
AudioComponentCopyName (comp, &itemName);
|
|
|
|
// if Marc-style fails, do the original way
|
|
if (itemName == NULL) {
|
|
CAComponentDescription comp_desc;
|
|
AudioComponentGetDescription (comp, &comp_desc);
|
|
|
|
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 (AudioComponent& 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);
|
|
|
|
get_names (comp, info.name, info.creator);
|
|
|
|
CAComponent cacomp (desc);
|
|
|
|
UInt32 version;
|
|
if (cacomp.GetVersion (version) != noErr)
|
|
{
|
|
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)
|
|
{
|
|
AudioComponent comp = AudioComponentFindNext (NULL, &desc);
|
|
|
|
if (comp == NULL) {
|
|
error << ("AU was not found.") << endmsg;
|
|
return false;
|
|
}
|
|
|
|
while (comp != NULL) {
|
|
CAComponentDescription temp;
|
|
AudioComponentGetDescription (comp, &temp);
|
|
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 = AudioComponentFindNext (comp, &desc);
|
|
assert (comp == NULL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static string
|
|
auv2_info_cache_dir ()
|
|
{
|
|
#ifdef __aarch64__
|
|
string dir = Glib::build_filename (ARDOUR::user_cache_directory (), "auv2-arm64");
|
|
#else
|
|
string dir = Glib::build_filename (ARDOUR::user_cache_directory (), "auv2");
|
|
#endif
|
|
/* 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, std::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)
|
|
{
|
|
AudioComponent comp = 0;
|
|
do {
|
|
CAComponentDescription temp;
|
|
comp = AudioComponentFindNext (comp, &desc);
|
|
|
|
if (!comp) {
|
|
break;
|
|
}
|
|
|
|
AudioComponentGetDescription (comp, &temp);
|
|
|
|
CAComponent cacomp (desc);
|
|
UInt32 version;
|
|
if (cacomp.GetVersion (version) != noErr)
|
|
{
|
|
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;
|
|
}
|