Audacity 3.2.0
AudioUnitEffectBase.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file AudioUnitEffectBase.cpp
6
7 Dominic Mazzoni
8 Leland Lucius
9
10 Paul Licameli split from AudioUnitEffect
11
12*******************************************************************//*******************************************************************/
18
19#if USE_AUDIO_UNITS
20#include "AudioUnitEffectBase.h"
21#include "AudioUnitInstance.h"
22#include "ConfigInterface.h"
23
24#include <wx/ffile.h>
25#include <wx/osx/core/private.h>
26
27//
28// When a plug-in's state is saved to the settings file (as a preset),
29// it is in binary and gets base64 encoded before storing.
30//
31// When exporting, save as XML without base64 encoding.
32//
33// The advantages of XML format is less chance of failures occurring
34// when exporting. But, it can take a bit more space per preset int
35// the Audacity settings file.
36//
37// Using binary for now. Use kCFPropertyListXMLFormat_v1_0 if XML
38// format is desired.
39//
40#define PRESET_FORMAT kCFPropertyListBinaryFormat_v1_0
41
42// Name of the settings key to use for the above value
43#define PRESET_KEY wxT("Data")
44
45// Where the presets are located
46#define PRESET_LOCAL_PATH wxT("/Library/Audio/Presets")
47#define PRESET_USER_PATH wxT("~/Library/Audio/Presets")
48
50 const RegistryPath &group, const wxString &path,
51 const void *blob, size_t len, bool allowEmpty) const
52{
53 // Base64 encode the returned binary property list
54 auto parms = wxBase64Encode(blob, len);
55 if (!allowEmpty && parms.IsEmpty())
56 return XO("Failed to encode preset from \"%s\"").Format(path);
57
58 // And write it to the config
59 if (!SetConfig(*this, PluginSettings::Private, group, PRESET_KEY, parms))
60 return XO("Unable to store preset in config file");
61 return {};
62}
63
65//
66// AudioUnitEffect
67//
69
71 const wxString & name, AudioComponent component,
72 Parameters *pParameters, AudioUnitEffectBase *master
73) : AudioUnitWrapper{ component, pParameters }
74 , mPath{ path }
75 , mName{ name.AfterFirst(wxT(':')).Trim(true).Trim(false) }
76 , mVendor{ name.BeforeFirst(wxT(':')).Trim(true).Trim(false) }
77{
78}
79
81
82// ============================================================================
83// ComponentInterface implementation
84// ============================================================================
85
87{
88 return mPath;
89}
90
92{
93 return mName;
94}
95
97{
98 return { mVendor };
99}
100
102{
103 UInt32 version;
104
105 OSStatus result = AudioComponentGetVersion(mComponent, &version);
106
107 return wxString::Format(wxT("%d.%d.%d"),
108 (version >> 16) & 0xffff,
109 (version >> 8) & 0xff,
110 version & 0xff);
111}
112
114{
115 /* i18n-hint: Can mean "not available," "not applicable," "no answer" */
116 return XO("n/a");
117}
118
119// ============================================================================
120// EffectDefinitionInterface implementation
121// ============================================================================
122
124{
125 if (mAudioIns == 0 && mAudioOuts == 0)
126 {
127 return EffectTypeNone;
128 }
129
130 if (mAudioIns == 0)
131 {
132 return EffectTypeGenerate;
133 }
134
135 if (mAudioOuts == 0)
136 {
137 return EffectTypeAnalyze;
138 }
139
140 return EffectTypeProcess;
141}
142
144{
146}
147
149{
150 return mInteractive;
151}
152
154{
155 return false;
156}
157
159{
160 return GetType() == EffectTypeProcess
163}
164
166{
167 bool supports = false;
169 [&supports](const ParameterInfo &pi, AudioUnitParameterID) {
170 if (pi.mInfo.flags & kAudioUnitParameterFlag_IsWritable)
171 supports = true;
172 // Search only until we find one, that's all we need to know
173 return !supports;
174 });
175 return supports;
176}
177
178std::shared_ptr<EffectInstance> AudioUnitEffectBase::MakeInstance() const
179{
180 bool useLatency;
182 useLatency, true);
183
184 return std::make_shared<AudioUnitInstance>(*this, mComponent, mParameters,
185 GetSymbol().Internal(), mAudioIns, mAudioOuts, useLatency);
186}
187
189{
190 // To implement the services of EffectPlugin -- such as, a query of the
191 // set of effect parameters, so that we can implement MakeSettings -- we
192 // also need what is called an AudioComponentInstance, also called an
193 // AudioUnit.
194 // It's not just for implementing EffectInstance. AudioUnits is unlike other
195 // third party effect families that distinguish the notions of plug-in and
196 // instance.
197
199
200 if (!CreateAudioUnit())
201 return false;
202
203 // Use an arbitrary rate while completing the discovery of channel support
204 if (!SetRateAndChannels(44100.0, GetSymbol().Internal()))
205 return false;
206
207 // Determine interactivity
209 if (!mInteractive) {
210 // Check for a Cocoa UI
211 // This could retrieve a variable-size property, but we only look at
212 // the first element.
213 AudioUnitCocoaViewInfo cocoaViewInfo;
215 !GetFixedSizeProperty(kAudioUnitProperty_CocoaUI, cocoaViewInfo);
216 if (!mInteractive) {
217 // Check for a Carbon UI
218 // This could retrieve a variable sized array but we only need the
219 // first
220 AudioComponentDescription compDesc;
222 kAudioUnitProperty_GetUIComponentList, compDesc);
223 }
224 }
225
226 return true;
227}
228
229#if 0
231{
232 // Retrieve the tail time
233 Float64 tailTime = 0.0;
234 if (!GetFixedSizeProperty(kAudioUnitProperty_TailTime, tailTime))
235 return tailTime * mSampleRate;
236 return 0;
237}
238#endif
239
240// Don't use the template-generated MakeSettings(), which default-constructs
241// the structure. Instead allocate a number of values chosen by the plug-in
243{
245 FetchSettings(settings, true);
246 return EffectSettings::Make<AudioUnitEffectSettings>(std::move(settings));
247}
248
250 const EffectSettings &, EffectSettings &) const
251{
252 // Not needed -- rely on EffectInstance::Message instead
253 return true;
254}
255
256constexpr auto PresetStr = "_PRESET";
257
260{
261 // Find a key to use for the preset that does not collide with any
262 // parameter name
263 wxString result = PresetStr;
264
265 // That string probably works but be sure
266 const auto &map = GetSettings(settings).values;
267 using Pair = decltype(*map.begin());
268 while (std::any_of(map.begin(), map.end(), [&](Pair &pair){
269 return pair.second && pair.second->first == result;
270 }))
271 result += "_";
272
273 return result;
274}
275
277{
278 RegistryPath result;
279 auto len = strlen(PresetStr);
280 if (auto [index, key] = std::tuple(0L, wxString{})
281 ; parms.GetFirstEntry(key, index)
282 ) do {
283 if (key.StartsWith(PresetStr)
284 && key.Mid(len).find_first_not_of("_") == wxString::npos
285 && key.length() > result.length())
286 result = key;
287 } while(parms.GetNextEntry(key, index));
288 return result;
289}
290
292 const EffectSettings &settings, CommandParameters & parms) const
293{
294 const auto &mySettings = GetSettings(settings);
295 if (mySettings.mPresetNumber) {
296 const auto key = ChoosePresetKey(settings);
297 parms.Write(key, *mySettings.mPresetNumber);
298 }
299
300 // Save settings into CommandParameters
301 // Iterate the map only, not using any AudioUnit handles
302 for (auto &[ID, pPair] : mySettings.values)
303 if (pPair)
304 // Write names, not numbers, as keys in the config file
305 parms.Write(pPair->first, pPair->second);
306 return true;
307}
308
310 const CommandParameters & parms, EffectSettings &settings) const
311{
312 // First clean all settings, in case any are not defined in parms
313 auto &mySettings = GetSettings(settings);
314 mySettings.ResetValues();
315 auto &map = mySettings.values;
316
317 // Reload preset first
318 if (auto presetKey = FindPresetKey(parms); !presetKey.empty()) {
319 SInt32 value = 0;
320 if (parms.Read(presetKey, &value))
322 }
323
324 // Load settings from CommandParameters
325 // Iterate the config only, not using any AudioUnit handles
326 if (auto [index, key, value] = std::tuple(
327 0L, wxString{}, AudioUnitParameterValue{})
328 ; parms.GetFirstEntry(key, index)
329 ) do {
330 if (auto pKey = ParameterInfo::ParseKey(key)
331 ; pKey && parms.Read(key, &value)
332 )
333 map[*pKey].emplace(mySettings.Intern(key), value);
334 } while(parms.GetNextEntry(key, index));
335 return true;
336}
337
340{
341 // To do: externalize state so const_cast isn't needed
342 return const_cast<AudioUnitEffectBase*>(this)->LoadPreset(name, settings);
343}
344
346 const RegistryPath & name, const EffectSettings &settings) const
347{
349}
350
353{
355 return { nullptr };
356 return {};
357}
358
360{
361 RegistryPaths presets;
362
363 // Retrieve the list of factory presets
364 CF_ptr<CFArrayRef> array;
365 if (!GetFixedSizeProperty(kAudioUnitProperty_FactoryPresets, array))
366 for (CFIndex i = 0, cnt = CFArrayGetCount(array.get()); i < cnt; ++i)
367 presets.push_back(wxCFStringRef::AsString(
368 static_cast<const AUPreset*>(CFArrayGetValueAtIndex(array.get(), i))
369 ->presetName));
370 return presets;
371}
372
374{
375 return true;
376}
377
379{
380 return true;
381}
382
383// ============================================================================
384// AudioUnitEffect Implementation
385// ============================================================================
386
388 const RegistryPath & group, EffectSettings &settings) const
389{
390 // Migration of very old format configuration file, should not normally
391 // happen and perhaps this code can be abandoned
392 // Attempt to load old preset parameters and resave using new method
393 constexpr auto oldKey = L"Parameters";
394 wxString parms;
396 group, oldKey, parms, wxEmptyString)) {
398 if (eap.SetParameters(parms))
399 if (LoadSettings(eap, settings))
400 if (SavePreset(group, GetSettings(settings)))
401 RemoveConfig(*this, PluginSettings::Private, group, oldKey);
402 return true;
403 }
404 return false;
405}
406
408 const RegistryPath & group, EffectSettings &settings) const
409{
410 if (MigrateOldConfigFile(group, settings))
411 return { nullptr };
412
413 if (AudioUnitWrapper::LoadPreset(*this, group, settings))
414 return { nullptr };
415 return {};
416}
417
419 const RegistryPath & group, const AudioUnitEffectSettings &settings) const
420{
421 wxCFStringRef cfname(wxFileNameFromPath(group));
422 const auto &[data, _] = MakeBlob(*this, settings, cfname, true);
423 if (!data)
424 return false;
425
426 // Nothing to do if we don't have any data
427 if (const auto length = CFDataGetLength(data.get())) {
428 auto error =
429 SaveBlobToConfig(group, {}, CFDataGetBytePtr(data.get()), length);
430 if (!error.empty())
431 return false;
432 }
433 return true;
434}
435
437 const AudioUnitEffectSettings &settings, const wxString & path) const
438{
439 // Create the file
440 wxFFile f(path, wxT("wb"));
441 if (!f.IsOpened())
442 return XO("Couldn't open \"%s\"").Format(path);
443
444 // First set the name of the preset
445 wxCFStringRef cfname(wxFileName(path).GetName());
446
447 const auto &[data, message] = MakeBlob(*this, settings, cfname, false);
448 if (!data || !message.empty())
449 return message;
450
451 // Write XML data
452 auto length = CFDataGetLength(data.get());
453 if (f.Write(CFDataGetBytePtr(data.get()), length) != length || f.Error())
454 return XO("Failed to write XML preset to \"%s\"").Format(path);
455
456 f.Close();
457 return {};
458}
459
461 AudioUnitEffectSettings &settings, const wxString & path) const
462{
463 // Open the preset
464 wxFFile f(path, wxT("r"));
465 if (!f.IsOpened())
466 return XO("Couldn't open \"%s\"").Format(path);
467
468 // Load it into the buffer
469 size_t len = f.Length();
470 wxMemoryBuffer buf(len);
471 if (f.Read(buf.GetData(), len) != len || f.Error())
472 return XO("Unable to read the preset from \"%s\"").Format(path);
473 buf.SetDataLen(len);
474
475 const auto error = InterpretBlob(settings, path, buf);
476 if (!error.empty())
477 return error;
478
479 return {};
480}
481
483{
484 // Does AU have channel info
486 if (GetVariableSizeProperty(kAudioUnitProperty_SupportedNumChannels, info)) {
487 // None supplied. Apparently all FX type units can do any number of INs
488 // and OUTs as long as they are the same number. In this case, we'll
489 // just say stereo.
490 //
491 // We should probably check to make sure we're dealing with an FX type.
492 mAudioIns = 2;
493 mAudioOuts = 2;
494 return;
495 }
496
497 // This is where it gets weird...not sure what is the best
498 // way to do this really. If we knew how many ins/outs we
499 // really needed, we could make a better choice.
500
501 bool haven2m = false; // nothing -> mono
502 bool haven2s = false; // nothing -> stereo
503 bool havem2n = false; // mono -> nothing
504 bool haves2n = false; // stereo -> nothing
505 bool havem2m = false; // mono -> mono
506 bool haves2s = false; // stereo -> stereo
507 bool havem2s = false; // mono -> stereo
508 bool haves2m = false; // stereo -> mono
509
510 mAudioIns = 2;
511 mAudioOuts = 2;
512
513 // Look only for exact channel constraints
514 for (auto &ci : info) {
515 int ic = ci.inChannels;
516 int oc = ci.outChannels;
517
518 if (ic < 0 && oc >= 0)
519 ic = 2;
520 else if (ic >= 0 && oc < 0)
521 oc = 2;
522 else if (ic < 0 && oc < 0) {
523 ic = 2;
524 oc = 2;
525 }
526
527 if (ic == 2 && oc == 2)
528 haves2s = true;
529 else if (ic == 1 && oc == 1)
530 havem2m = true;
531 else if (ic == 1 && oc == 2)
532 havem2s = true;
533 else if (ic == 2 && oc == 1)
534 haves2m = true;
535 else if (ic == 0 && oc == 2)
536 haven2s = true;
537 else if (ic == 0 && oc == 1)
538 haven2m = true;
539 else if (ic == 1 && oc == 0)
540 havem2n = true;
541 else if (ic == 2 && oc == 0)
542 haves2n = true;
543 }
544
545 if (haves2s) {
546 mAudioIns = 2;
547 mAudioOuts = 2;
548 }
549 else if (havem2m) {
550 mAudioIns = 1;
551 mAudioOuts = 1;
552 }
553 else if (havem2s) {
554 mAudioIns = 1;
555 mAudioOuts = 2;
556 }
557 else if (haves2m) {
558 mAudioIns = 2;
559 mAudioOuts = 1;
560 }
561 else if (haven2m) {
562 mAudioIns = 0;
563 mAudioOuts = 1;
564 }
565 else if (haven2s) {
566 mAudioIns = 0;
567 mAudioOuts = 2;
568 }
569 else if (haves2n) {
570 mAudioIns = 2;
571 mAudioOuts = 0;
572 }
573 else if (havem2n) {
574 mAudioIns = 1;
575 mAudioOuts = 0;
576 }
577
578 return;
579}
580#endif
wxT("CloseDown"))
@ Internal
Indicates internal failure from Audacity.
constexpr auto PresetStr
#define PRESET_KEY
constexpr auto OptionsKey
constexpr auto UseLatencyKey
#define AUDIOUNITEFFECTS_FAMILY
EffectType
@ EffectTypeAnalyze
@ EffectTypeGenerate
@ EffectTypeNone
@ EffectTypeProcess
std::optional< std::unique_ptr< EffectSettingsAccess::Message > > OptionalMessage
XO("Cut/Copy/Paste")
wxString RegistryPath
Definition: Identifier.h:218
wxString PluginPath
type alias for identifying a Plugin supplied by a module, each module defining its own interpretation...
Definition: Identifier.h:214
std::vector< RegistryPath > RegistryPaths
Definition: Identifier.h:219
#define _(s)
Definition: Internat.h:73
static const AudacityProject::AttachedObjects::RegisteredFactory key
wxString name
Definition: TagsEditor.cpp:166
static Settings & settings()
Definition: TrackInfo.cpp:51
An Effect class that handles a wide range of effects. ??Mac only??
std::shared_ptr< EffectInstance > MakeInstance() const override
Make an object maintaining short-term state of an Effect.
bool IsDefault() const override
Whether the effect sorts "above the line" in the menus.
static RegistryPath FindPresetKey(const CommandParameters &parms)
TranslatableString SaveBlobToConfig(const RegistryPath &group, const wxString &path, const void *blob, size_t len, bool allowEmpty=true) const
bool IsInteractive() const override
Whether the effect needs a dialog for entry of settings.
bool SupportsAutomation() const override
Whether the effect has any automatable controls.
TranslatableString GetDescription() const override
EffectFamilySymbol GetFamily() const override
Report identifier and user-visible name of the effect protocol.
bool SaveSettings(const EffectSettings &settings, CommandParameters &parms) const override
Store settings as keys and values.
TranslatableString Export(const AudioUnitEffectSettings &settings, const wxString &path) const
OptionalMessage LoadFactoryPreset(int id, EffectSettings &settings) const override
PluginPath GetPath() const override
EffectSettings MakeSettings() const override
TranslatableString Import(AudioUnitEffectSettings &settings, const wxString &path) const
bool SavePreset(const RegistryPath &group, const AudioUnitEffectSettings &settings) const
bool LoadSettings(const CommandParameters &parms, EffectSettings &settings) const override
May allocate memory, so should be called only in the main thread.
ComponentInterfaceSymbol GetSymbol() const override
RealtimeSince RealtimeSupport() const override
Since which version of Audacity has the effect supported realtime?
bool SaveUserPreset(const RegistryPath &name, const EffectSettings &settings) const override
Save settings in the configuration file as a user-named preset.
AudioUnitEffectBase(const PluginPath &path, const wxString &name, AudioComponent component, Parameters *pParameters=nullptr, AudioUnitEffectBase *master=nullptr)
EffectType GetType() const override
Type determines how it behaves.
wxString GetVersion() const override
OptionalMessage LoadPreset(const RegistryPath &group, EffectSettings &settings) const
VendorSymbol GetVendor() const override
static RegistryPath ChoosePresetKey(const EffectSettings &settings)
bool MigrateOldConfigFile(const RegistryPath &group, EffectSettings &settings) const
const PluginPath mPath
OptionalMessage LoadUserPreset(const RegistryPath &name, EffectSettings &settings) const override
bool HasOptions() const override
bool CopySettingsContents(const EffectSettings &src, EffectSettings &dst) const override
Update one settings object from another.
bool CanExportPresets() const override
Whether the effect supports export of presets to files, and importing too.
RegistryPaths GetFactoryPresets() const override
Report names of factory presets.
~AudioUnitEffectBase() override
Encapsulates parameter information for an AudioUnit.
static std::optional< AudioUnitParameterID > ParseKey(const wxString &key)
Recover the parameter ID from the key, if well formed.
CommandParameters, derived from wxFileConfig, is essentially doing the same things as the SettingsVis...
bool SetParameters(const wxString &parms)
TranslatableString GetName() const
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
RealtimeSince
In which versions of Audacity was an effect realtime capable?
virtual size_t GetTailSize() const
Holds a msgid for the translation catalog; may also bind format arguments.
size_t Count(const Ptr< Type, BaseDeleter > &p)
Find out how many elements were allocated with a Ptr.
Definition: PackedArray.h:143
bool SetConfig(const EffectDefinitionInterface &ident, ConfigurationType type, const RegistryPath &group, const RegistryPath &key, const Value &value)
bool RemoveConfig(const EffectDefinitionInterface &ident, PluginSettings::ConfigurationType type, const RegistryPath &group, const RegistryPath &key)
bool GetConfig(const EffectDefinitionInterface &ident, ConfigurationType type, const RegistryPath &group, const RegistryPath &key, Value &var, const Value &defval)
constexpr size_t npos(-1)
std::pair< const char *, const char * > Pair
Represents a cached copy of the state stored in an AudioUnit, but can outlive the original AudioUnit.
Manages and interacts with an AudioUnit, providing operations on audio effects.
Parameters & mParameters
bool LoadPreset(const EffectDefinitionInterface &effect, const RegistryPath &group, EffectSettings &settings) const
bool LoadFactoryPreset(const EffectDefinitionInterface &effect, int id, EffectSettings *pSettings) const
OSStatus GetFixedSizeProperty(AudioUnitPropertyID inID, T &property, AudioUnitScope inScope=kAudioUnitScope_Global, AudioUnitElement inElement=0) const
OSStatus GetVariableSizeProperty(AudioUnitPropertyID inID, PackedArray::Ptr< T > &pObject, AudioUnitScope inScope=kAudioUnitScope_Global, AudioUnitElement inElement=0) const
TranslatableString InterpretBlob(AudioUnitEffectSettings &settings, const wxString &group, const wxMemoryBuffer &buf) const
Interpret the dump made before by MakeBlob.
bool SetRateAndChannels(double sampleRate, const wxString &identifier)
static AudioUnitEffectSettings & GetSettings(EffectSettings &settings)
const AudioComponent mComponent
std::pair< CF_ptr< CFDataRef >, TranslatableString > MakeBlob(const EffectDefinitionInterface &effect, const AudioUnitEffectSettings &settings, const wxCFStringRef &cfname, bool binary) const
Obtain dump of the setting state of an AudioUnit instance.
void ForEachParameter(ParameterVisitor visitor) const
bool FetchSettings(AudioUnitEffectSettings &settings, bool fetchValues, bool fetchPreset=false) const
May allocate memory, so should be called only in the main thread.
Externalized state of a plug-in.