AU plugin compatibility

Hi,

This is the equivalent of the VST3 IPluginCompatility, but for AUv2. Let’s say you are releasing “Foo v2”, which is a different plugin from your older major release, “Foo v1”.
You want to allow your users to keep the older version installed, but also to have “Foo v1” replaced by “Foo v2” when v1 is not available on the user’s computer.

It is the purpose of the kAudioUnitMigrateProperty_FromPlugin property.

This is something that is probably quite legacy for AudioUnits (no equivalent for AUv3), but I know that Kontakt still implements it for upgrades from “Kontakt 7” to “Kontakt 8” etc..

Here is the patch to juce_audio_plugin_client_AU_1.mm

         return (UInt32) channelInfo.size();
     }
 
+    class PluginMigratePropertyHelper {
+        std::vector<AudioUnitOtherPluginDesc> compat;
+        std::vector<AudioUnitOtherPluginDesc*> compat_ptr;
+        PluginMigratePropertyHelper()
+        {
+            for (OSType pluginId : { JucePlugin_MigrateFrom_PluginCode }) {
+                if (pluginId == JucePlugin_PluginCode) continue;
+                
+                OSType pluginType = (JucePlugin_IsMidiEffect ? 'aumi' :
+                                    (JucePlugin_IsSynth ? 'aumu' :
+                                    (JucePlugin_WantsMidiInput ? 'aumi' : 'aufx')));
+                compat.push_back(AudioUnitOtherPluginDesc{kOtherPluginFormat_AU,
+                        { pluginType, pluginId, JucePlugin_ManufacturerCode }});
+            }
+            for (auto &d : compat)
+            {
+                compat_ptr.push_back(&d);
+            }
+        }
+      public:
+        static PluginMigratePropertyHelper &instance() {
+            static PluginMigratePropertyHelper mig;
+            return mig;
+        }
+
+        bool isEmpty() const { return compat.empty(); }
+
+        CFArrayRef getCFArrayRef() const
+        {
+            return CFArrayCreate(NULL, (const void**)&compat_ptr[0], compat_ptr.size(), nullptr);
+        }
+    };
+#endif
+
     //==============================================================================
     ComponentResult GetPropertyInfo (AudioUnitPropertyID inID,
                                      AudioUnitScope inScope,
@@ -527,6 +562,11 @@ public:
                     return noErr;
                #endif
 
+               case kAudioUnitMigrateProperty_FromPlugin:
+                   outWritable = false;
+                   outDataSize = PluginMigratePropertyHelper::instance().isEmpty() ? 0 : sizeof(CFArrayRef);
+                   return noErr;
+
                 default: break;
             }
         }
@@ -721,6 +763,13 @@ public:
                 }
                 break;
 
+                case kAudioUnitMigrateProperty_FromPlugin:
+                {
+                    *(CFArrayRef*)outData = PluginMigratePropertyHelper::instance().getCFArrayRef();
+                    return noErr;
+                }
+                break;
+
                 default:
                     break;
             }

And, next to your JucePlugin_PluginCode definition , you have to define the plugin codes that you want to be able to upgrade from (as a comma separated list of 4-char plugin codes):

#define JucePlugin_MigrateFrom_PluginCode 'Foo0', 'Foo1' // etc..

In Apple’s headers, next to kAudioUnitMigrateProperty_FromPlugin , there is also a kAudioUnitMigrateProperty_OldAutomation property that is declared. Unfortunately it does not seem to be checked by Logic. So, when migrating, nothing will be done to preserve the automation, at least by Apple hosts.

I have tested with

  • Logic, the AUv2 plugin is silently replaced when the old plugin is not available.
  • Garageband: same as Logic
  • Digital Performer 11 : the AUv2 plugin is silently replaced, automation is also preserved I don’t know how.
  • Live 12 : no replacement

When testing this, don’t forget you have to nuke as hard as possible all the AudioUnit caches of macOS. I’m also erasing Logic preferences . When you see Logic spending minutes scanning all plugins on the system, you’re good, otherwise it may be still caching some metadata about your plugin..

Related topic: AAX plugin compatibility. The patch is very simple:

# ifdef JucePlugin_AAXRelated_PluginCode
        std::vector<AAX_SPlugInIdentifierTriad> compat;
        for (auto pluginCode : { JucePlugin_AAXRelated_PluginCode }) {
          compat.push_back({ JucePlugin_AAXManufacturerCode, (AAX_CTypeID)pluginCode, (AAX_CTypeID)pluginID });
        }
        properties->AddPropertyWithIDArray(AAX_eProperty_Related_Native_Plugin_List, compat.data(), compat.size());
# endif

in juce_audio_plugin_client_AAX.cpp, createDescriptor, for example after the “#if JucePlugin_AAXDisableSaveRestore” block

Automation is preserved as long as you don’t mess with your juce parameter IDs.

Also: https://support.native-instruments.com/hc/en-us/articles/12972701353757-Notes-about-Auto-Migration-of-Kontakt-Versions-in-DAW-Projects is the link that gave me a hint that it was possible.