rework AudioUnit variable input port count.
[ardour.git] / libs / ardour / audio_unit.cc
index aeb91e684806cfd37247c9284ca07345412ac336..063055b5e63fc018b22bdee07aea3dda4aceeb98 100644 (file)
@@ -73,6 +73,80 @@ static string preset_search_path = "/Library/Audio/Presets:/Network/Library/Audi
 static string preset_suffix = ".aupreset";
 static bool preset_search_path_initialized = false;
 FILE * AUPluginInfo::_crashlog_fd = NULL;
+bool AUPluginInfo::_scan_only = true;
+
+
+static void au_blacklist (std::string id)
+{
+       string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_blacklist.txt");
+       FILE * blacklist_fd = NULL;
+       if (! (blacklist_fd = fopen(fn.c_str(), "a"))) {
+               PBD::error << "Cannot append to AU blacklist for '"<< id <<"'\n";
+               return;
+       }
+       assert(id.find("\n") == string::npos);
+       fprintf(blacklist_fd, "%s\n", id.c_str());
+       ::fclose(blacklist_fd);
+}
+
+static void au_unblacklist (std::string id)
+{
+       string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_blacklist.txt");
+       if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
+               PBD::warning << "Expected Blacklist file does not exist.\n";
+               return;
+       }
+
+       std::string bl;
+       {
+               std::ifstream ifs(fn.c_str());
+               bl.assign ((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
+       }
+
+       ::g_unlink (fn.c_str());
+
+       assert (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS));
+       assert(id.find("\n") == string::npos);
+
+       id += "\n"; // add separator
+       const size_t rpl = bl.find(id);
+       if (rpl != string::npos) {
+               bl.replace(rpl, id.size(), "");
+       }
+       if (bl.empty()) {
+               return;
+       }
+
+       FILE * blacklist_fd = NULL;
+       if (! (blacklist_fd = fopen(fn.c_str(), "w"))) {
+               PBD::error << "Cannot open AU blacklist.\n";
+               return;
+       }
+       fprintf(blacklist_fd, "%s", bl.c_str());
+       ::fclose(blacklist_fd);
+}
+
+static bool is_blacklisted (std::string id)
+{
+       string fn = Glib::build_filename (ARDOUR::user_cache_directory(), "au_blacklist.txt");
+       if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
+               return false;
+       }
+       std::string bl;
+       std::ifstream ifs(fn.c_str());
+       bl.assign ((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
+
+       assert(id.find("\n") == string::npos);
+
+       id += "\n"; // add separator
+       const size_t rpl = bl.find(id);
+       if (rpl != string::npos) {
+               return true;
+       }
+       return false;
+}
+
+
 
 static OSStatus
 _render_callback(void *userData,
@@ -349,6 +423,7 @@ AUPlugin::AUPlugin (AudioEngine& engine, Session& session, boost::shared_ptr<CAC
        , input_offset (0)
        , input_buffers (0)
        , frames_processed (0)
+       , audio_input_cnt (0)
        , _parameter_listener (0)
        , _parameter_listener_arg (0)
        , last_transport_rolling (false)
@@ -360,6 +435,7 @@ AUPlugin::AUPlugin (AudioEngine& engine, Session& session, boost::shared_ptr<CAC
                p += preset_search_path;
                preset_search_path = p;
                preset_search_path_initialized = true;
+               DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Preset Path: %1\n", preset_search_path));
        }
 
        init ();
@@ -384,6 +460,9 @@ AUPlugin::AUPlugin (const AUPlugin& other)
 
 {
        init ();
+       for (size_t i = 0; i < descriptors.size(); ++i) {
+               set_parameter (i, other.get_parameter (i));
+       }
 }
 
 AUPlugin::~AUPlugin ()
@@ -434,6 +513,7 @@ AUPlugin::discover_factory_presets ()
 
                string name = CFStringRefToStdString (preset->presetName);
                factory_preset_map[name] = preset->presetNumber;
+               DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Factory Preset: %1 > %2\n", name, preset->presetNumber));
        }
 
        CFRelease (presets);
@@ -443,6 +523,7 @@ void
 AUPlugin::init ()
 {
        OSErr err;
+       CFStringRef itemName;
 
        /* these keep track of *configured* channel set up,
           not potential set ups.
@@ -450,6 +531,20 @@ AUPlugin::init ()
 
        input_channels = -1;
        output_channels = -1;
+       {
+               CAComponentDescription temp;
+               GetComponentInfo (comp.get()->Comp(), &temp, NULL, NULL, NULL);
+               CFStringRef compTypeString = UTCreateStringForOSType(temp.componentType);
+               CFStringRef compSubTypeString = UTCreateStringForOSType(temp.componentSubType);
+               CFStringRef compManufacturerString = UTCreateStringForOSType(temp.componentManufacturer);
+               itemName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ - %@ - %@"),
+                               compTypeString, compManufacturerString, compSubTypeString);
+               if (compTypeString != NULL) CFRelease(compTypeString);
+               if (compSubTypeString != NULL) CFRelease(compSubTypeString);
+               if (compManufacturerString != NULL) CFRelease(compManufacturerString);
+       }
+
+       au_blacklist(CFStringRefToStdString(itemName));
 
        try {
                DEBUG_TRACE (DEBUG::AudioUnits, "opening AudioUnit\n");
@@ -515,6 +610,9 @@ AUPlugin::init ()
        discover_factory_presets ();
 
        // Plugin::setup_controls ();
+
+       au_unblacklist(CFStringRefToStdString(itemName));
+       if (itemName != NULL) CFRelease(itemName);
 }
 
 void
@@ -532,7 +630,7 @@ AUPlugin::discover_parameters ()
 
        for (uint32_t i = 0; i < sizeof (scopes) / sizeof (scopes[0]); ++i) {
 
-               AUParamInfo param_info (unit->AU(), false, false, scopes[i]);
+               AUParamInfo param_info (unit->AU(), false, /* include read only */ true, scopes[i]);
 
                for (uint32_t i = 0; i < param_info.NumParams(); ++i) {
 
@@ -612,7 +710,7 @@ AUPlugin::discover_parameters ()
                        d.toggled = (info.unit == kAudioUnitParameterUnit_Boolean) ||
                                (d.integer_step && ((d.upper - d.lower) == 1.0));
                        d.sr_dependent = (info.unit == kAudioUnitParameterUnit_SampleFrames);
-                       d.automatable = !d.toggled &&
+                       d.automatable = /* !d.toggled && -- ardour can automate toggles, can AU ? */
                                !(info.flags & kAudioUnitParameterFlag_NonRealTime) &&
                                (info.flags & kAudioUnitParameterFlag_IsWritable);
 
@@ -915,8 +1013,11 @@ AUPlugin::configure_io (ChanCount in, ChanCount out)
 {
        AudioStreamBasicDescription streamFormat;
        bool was_initialized = initialized;
-       int32_t audio_in = in.n_audio();
        int32_t audio_out = out.n_audio();
+       if (audio_input_cnt > 0) {
+               in.set (DataType::AUDIO, audio_input_cnt);
+       }
+       int32_t audio_in = in.n_audio();
 
        DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("configure %1 for %2 in %3 out\n", name(), in, out));
 
@@ -977,11 +1078,11 @@ AUPlugin::input_streams() const
 {
        ChanCount c;
 
-       c.set (DataType::AUDIO, 1);
-       c.set (DataType::MIDI, 0);
 
        if (input_channels < 0) {
-               warning << string_compose (_("AUPlugin: %1 input_streams() called without any format set!"), name()) << endmsg;
+               // force PluginIoReConfigure -- see also commit msg e38eb06
+               c.set (DataType::AUDIO, 0);
+               c.set (DataType::MIDI, 0);
        } else {
                c.set (DataType::AUDIO, input_channels);
                c.set (DataType::MIDI, _has_midi_input ? 1 : 0);
@@ -996,11 +1097,10 @@ AUPlugin::output_streams() const
 {
        ChanCount c;
 
-       c.set (DataType::AUDIO, 1);
-       c.set (DataType::MIDI, 0);
-
        if (output_channels < 0) {
-               warning << string_compose (_("AUPlugin: %1 output_streams() called without any format set!"), name()) << endmsg;
+               // force PluginIoReConfigure - see also commit msg e38eb06
+               c.set (DataType::AUDIO, 0);
+               c.set (DataType::MIDI, 0);
        } else {
                c.set (DataType::AUDIO, output_channels);
                c.set (DataType::MIDI, _has_midi_output ? 1 : 0);
@@ -1038,11 +1138,37 @@ AUPlugin::can_support_io_configuration (const ChanCount& in, ChanCount& out)
        //if there's a configuration that keeps out==in
 
        if (in.n_midi() > 0 && audio_in == 0) {
-               audio_out = -1;
+               audio_out = 2; // prefer stereo version if available.
        } else {
                audio_out = audio_in;
        }
 
+       /* kAudioUnitProperty_SupportedNumChannels
+        * https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html#//apple_ref/doc/uid/TP40003278-CH12-SW20
+        *
+        * - both fields are -1
+        *   e.g. inChannels = -1 outChannels = -1
+        *    This is the default case. Any number of input and output channels, as long as the numbers match
+        *
+        * - one field is -1, the other field is positive
+        *   e.g. inChannels = -1 outChannels = 2
+        *    Any number of input channels, exactly two output channels
+        *
+        * - one field is -1, the other field is -2
+        *   e.g. inChannels = -1 outChannels = -2
+        *    Any number of input channels, any number of output channels
+        *
+        * - both fields have non-negative values
+        *   e.g. inChannels = 2 outChannels = 6
+        *    Exactly two input channels, exactly six output channels
+        *   e.g. inChannels = 0 outChannels = 2
+        *    No input channels, exactly two output channels (such as for an instrument unit with stereo output)
+        *
+        * - both fields have negative values, neither of which is –1 or –2
+        *   e.g. inChannels = -4 outChannels = -8
+        *    Up to four input channels and up to eight output channels
+        */
+
        for (vector<pair<int,int> >::iterator i = io_configs.begin(); i != io_configs.end(); ++i) {
 
                int32_t possible_in = i->first;
@@ -1093,21 +1219,12 @@ AUPlugin::can_support_io_configuration (const ChanCount& in, ChanCount& out)
                                audio_out = 2;
                                found = true;
                        } else if (possible_out < -2) {
-                               /* explicitly variable number of outputs. 
-
-                                   Since Ardour can handle any configuration,
-                                   we have to somehow pick a number. 
-
-                                   We'll use the number of inputs
-                                   to the master bus, or 2 if there
-                                   is no master bus.
-                                */
-                                boost::shared_ptr<Route> master = _session.master_out();
-                                if (master) {
-                                        audio_out = master->input()->n_ports().n_audio();
-                                } else {
-                                        audio_out = 2;
-                                }
+                               /* explicitly variable number of outputs.
+                                *
+                                * We really need to ask the user in this case.
+                                * stereo will be correct in 99.9% of all cases.
+                                */
+                               audio_out = 2;
                                found = true;
                        } else {
                                /* exact number of outputs */
@@ -1182,21 +1299,12 @@ AUPlugin::can_support_io_configuration (const ChanCount& in, ChanCount& out)
                                audio_out = 2;
                                found = true;
                        } else if (possible_out < -2) {
-                               /* explicitly variable number of outputs. 
-
-                                   Since Ardour can handle any configuration,
-                                   we have to somehow pick a number. 
-
-                                   We'll use the number of inputs
-                                   to the master bus, or 2 if there
-                                   is no master bus.
-                                */
-                                boost::shared_ptr<Route> master = _session.master_out();
-                                if (master) {
-                                        audio_out = master->input()->n_ports().n_audio();
-                                } else {
-                                        audio_out = 2;
-                                }
+                               /* explicitly variable number of outputs.
+                                *
+                                * We really need to ask the user in this case.
+                                * stereo will be correct in 99.9% of all cases.
+                                */
+                               audio_out = 2;
                                found = true;
                        } else {
                                /* exact number of outputs */
@@ -1232,21 +1340,25 @@ AUPlugin::can_support_io_configuration (const ChanCount& in, ChanCount& out)
                }
 
                if (found) {
+                       if (possible_in < -2 && audio_in == 0) {
+                               // input-port count cannot be zero, use as many ports
+                               // as outputs, but at most abs(possible_in)
+                               audio_input_cnt = max (1, min (audio_out, -possible_in));
+                       }
                        break;
                }
 
        }
 
        if (found) {
+               out.set (DataType::MIDI, 0); /// XXX
+               out.set (DataType::AUDIO, audio_out);
                DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("\tCHOSEN: in %1 out %2\n", in, out));
        } else {
                DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("\tFAIL: no io configs match %1\n", in));
                return false;
        }
 
-       out.set (DataType::MIDI, 0);
-       out.set (DataType::AUDIO, audio_out);
-
        return true;
 }
 
@@ -1667,21 +1779,36 @@ AUPlugin::parameter_is_audio (uint32_t) const
 }
 
 bool
-AUPlugin::parameter_is_control (uint32_t) const
+AUPlugin::parameter_is_control (uint32_t param) const
 {
-       return true;
+       assert(param < descriptors.size());
+       if (descriptors[param].automatable) {
+               /* corrently ardour expects all controls to be automatable
+                * IOW ardour GUI elements mandate an Evoral::Parameter
+                * for all input+control ports.
+                */
+               return true;
+       }
+       return false;
 }
 
 bool
-AUPlugin::parameter_is_input (uint32_t) const
+AUPlugin::parameter_is_input (uint32_t param) const
 {
-       return false;
+       /* AU params that are both readable and writeable,
+        * are listed in kAudioUnitScope_Global
+        */
+       return (descriptors[param].scope == kAudioUnitScope_Input || descriptors[param].scope == kAudioUnitScope_Global);
 }
 
 bool
-AUPlugin::parameter_is_output (uint32_t) const
+AUPlugin::parameter_is_output (uint32_t param) const
 {
-       return false;
+       assert(param < descriptors.size());
+       // TODO check if ardour properly handles ports
+       // that report is_input + is_output == true
+       // -> add || descriptors[param].scope == kAudioUnitScope_Global
+       return (descriptors[param].scope == kAudioUnitScope_Output);
 }
 
 void
@@ -1883,6 +2010,10 @@ AUPlugin::do_save_preset (string preset_name)
 
        CFRelease(propertyList);
 
+       user_preset_map[preset_name] = user_preset_path;;
+
+       DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Saving Preset to %1\n", user_preset_path));
+
        return string ("file:///") + user_preset_path;
 }
 
@@ -2071,6 +2202,7 @@ AUPlugin::find_presets ()
        find_files_matching_filter (preset_files, preset_search_path, au_preset_filter, this, true, true, true);
 
        if (preset_files.empty()) {
+               DEBUG_TRACE (DEBUG::AudioUnits, "AU No Preset Files found for given plugin.\n");
                return;
        }
 
@@ -2091,6 +2223,9 @@ AUPlugin::find_presets ()
 
                if (check_and_get_preset_name (get_comp()->Comp(), path, preset_name)) {
                        user_preset_map[preset_name] = path;
+                       DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Preset File: %1 > %2\n", preset_name, path));
+               } else {
+                       DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU INVALID Preset: %1 > %2\n", preset_name, path));
                }
 
        }
@@ -2099,6 +2234,7 @@ AUPlugin::find_presets ()
 
        for (UserPresetMap::iterator i = user_preset_map.begin(); i != user_preset_map.end(); ++i) {
                _presets.insert (make_pair (i->second, Plugin::PresetRecord (i->second, i->first)));
+               DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Adding User Preset: %1 > %2\n", i->first, i->second));
        }
 
        /* add factory presets */
@@ -2107,6 +2243,7 @@ AUPlugin::find_presets ()
                /* XXX: dubious */
                string const uri = string_compose ("%1", _presets.size ());
                _presets.insert (make_pair (uri, Plugin::PresetRecord (uri, i->first, i->second)));
+               DEBUG_TRACE (DEBUG::AudioUnits, string_compose("AU Adding Factory Preset: %1 > %2\n", i->first, i->second));
        }
 }
 
@@ -2120,6 +2257,7 @@ AUPlugin::has_editor () const
 
 AUPluginInfo::AUPluginInfo (boost::shared_ptr<CAComponentDescription> d)
        : descriptor (d)
+       , version (0)
 {
        type = ARDOUR::AudioUnit;
 }
@@ -2164,10 +2302,19 @@ AUPluginInfo::au_cache_path ()
 }
 
 PluginInfoList*
-AUPluginInfo::discover ()
+AUPluginInfo::discover (bool scan_only)
 {
        XMLTree tree;
 
+       /* AU require a CAComponentDescription pointer provided by the OS.
+        * Ardour only caches port and i/o config. It can't just 'scan' without
+        * 'discovering' (like we do for VST).
+        * 
+        * "Scan Only" means
+        * "Iterate over all plugins. skip the ones where there's no io-cache".
+        */
+       _scan_only = scan_only;
+
        if (!Glib::file_test (au_cache_path(), Glib::FILE_TEST_EXISTS)) {
                ARDOUR::BootMessage (_("Discovering AudioUnit plugins (could take some time ...)"));
        }
@@ -2262,7 +2409,8 @@ AUPluginInfo::au_start_crashlog ()
        assert(!_crashlog_fd);
        DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Creating AU Log: %1\n", fn));
        if (!(_crashlog_fd = fopen(fn.c_str(), "w"))) {
-               PBD::error << "Cannot create AU error-log\n";
+               PBD::error << "Cannot create AU error-log" << fn << "\n";
+               cerr << "Cannot create AU error-log" << fn << "\n";
        }
 }
 
@@ -2282,9 +2430,12 @@ AUPluginInfo::au_remove_crashlog ()
 void
 AUPluginInfo::au_crashlog (std::string msg)
 {
-       assert(_crashlog_fd);
-       fprintf(_crashlog_fd, "AU: %s\n", msg.c_str());
-       ::fflush(_crashlog_fd);
+       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
@@ -2298,12 +2449,14 @@ AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescrip
        while (comp != NULL) {
                CAComponentDescription temp;
                GetComponentInfo (comp, &temp, NULL, NULL, NULL);
+               CFStringRef itemName = NULL;
 
                {
+                       if (itemName != NULL) CFRelease(itemName);
                        CFStringRef compTypeString = UTCreateStringForOSType(temp.componentType);
                        CFStringRef compSubTypeString = UTCreateStringForOSType(temp.componentSubType);
                        CFStringRef compManufacturerString = UTCreateStringForOSType(temp.componentManufacturer);
-                       CFStringRef itemName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ - %@ - %@"),
+                       itemName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ - %@ - %@"),
                                        compTypeString, compManufacturerString, compSubTypeString);
                        au_crashlog(string_compose("Scanning ID: %1", CFStringRefToStdString(itemName)));
                        if (compTypeString != NULL)
@@ -2314,6 +2467,12 @@ AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescrip
                                CFRelease(compManufacturerString);
                }
 
+               if (is_blacklisted(CFStringRefToStdString(itemName))) {
+                       info << string_compose (_("Skipped blacklisted AU plugin %1 "), CFStringRefToStdString(itemName)) << endmsg;
+                       comp = FindNextComponent (comp, &desc);
+                       continue;
+               }
+
                AUPluginInfoPtr info (new AUPluginInfo
                                      (boost::shared_ptr<CAComponentDescription> (new CAComponentDescription(temp))));
 
@@ -2330,6 +2489,7 @@ AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescrip
                case kAudioUnitType_Panner:
                case kAudioUnitType_OfflineEffect:
                case kAudioUnitType_FormatConverter:
+                       comp = FindNextComponent (comp, &desc);
                        continue;
 
                case kAudioUnitType_Output:
@@ -2355,6 +2515,7 @@ AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescrip
                        break;
                }
 
+               au_blacklist(CFStringRefToStdString(itemName));
                AUPluginInfo::get_names (temp, info->name, info->creator);
                ARDOUR::PluginScanMessage(_("AU"), info->name, false);
                au_crashlog(string_compose("Plugin: %1", info->name));
@@ -2371,8 +2532,9 @@ AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescrip
                        info->version = 0;
                }
 
-               if (cached_io_configuration (info->unique_id, info->version, cacomp, info->cache, info->name)) {
+               const int rv = cached_io_configuration (info->unique_id, info->version, cacomp, info->cache, info->name);
 
+               if (rv == 0) {
                        /* here we have to map apple's wildcard system to a simple pair
                           of values. in ::can_do() we use the whole system, but here
                           we need a single pair of values. XXX probably means we should
@@ -2406,17 +2568,20 @@ AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescrip
 
                        plugs.push_back (info);
 
-               } else {
+               }
+               else if (rv == -1) {
                        error << string_compose (_("Cannot get I/O configuration info for AU %1"), info->name) << endmsg;
                }
 
+               au_unblacklist(CFStringRefToStdString(itemName));
                au_crashlog("Success.");
                comp = FindNextComponent (comp, &desc);
+               if (itemName != NULL) CFRelease(itemName); itemName = NULL;
        }
        au_crashlog(string_compose("End AU discovery for Type: %1", (int)desc.componentType));
 }
 
-bool
+int
 AUPluginInfo::cached_io_configuration (const std::string& unique_id,
                                       UInt32 version,
                                       CAComponent& comp,
@@ -2440,7 +2605,12 @@ AUPluginInfo::cached_io_configuration (const std::string& unique_id,
 
        if (cim != cached_info.end()) {
                cinfo = cim->second;
-               return true;
+               return 0;
+       }
+
+       if (_scan_only) {
+               PBD::info << string_compose (_("Skipping AU %1 (not indexed. Discover new plugins to add)"), name) << endmsg;
+               return 1;
        }
 
        CAAudioUnit unit;
@@ -2453,19 +2623,19 @@ AUPluginInfo::cached_io_configuration (const std::string& unique_id,
        try {
 
                if (CAAudioUnit::Open (comp, unit) != noErr) {
-                       return false;
+                       return -1;
                }
 
        } catch (...) {
 
                warning << string_compose (_("Could not load AU plugin %1 - ignored"), name) << endmsg;
-               return false;
+               return -1;
 
        }
 
        DEBUG_TRACE (DEBUG::AudioUnits, "get AU channel info\n");
        if ((ret = unit.GetChannelInfo (&channel_info, cnt)) < 0) {
-               return false;
+               return -1;
        }
 
        if (ret > 0) {
@@ -2491,7 +2661,7 @@ AUPluginInfo::cached_io_configuration (const std::string& unique_id,
        add_cached_info (id, cinfo);
        save_cached_info ();
 
-       return true;
+       return 0;
 }
 
 void