mirror of
https://github.com/cookiengineer/audacity
synced 2025-09-18 17:10:55 +02:00
Bug 2334 - Mac: AU effects cannot Export or Import the effect's settings
This removes the ability to import/export multiple presets at once and add the standard method used by the other plug-ings. It also adds a workaround for the AU view not updating after setting a new preset. I will continue to look for the true cause of this, but the workaround does correct the display issue.
This commit is contained in:
parent
6cf635fdc1
commit
9cf80a41ee
@ -47,6 +47,7 @@ public:
|
||||
void CreateCocoa();
|
||||
void CreateGeneric();
|
||||
void CocoaViewResized();
|
||||
void ForceRedraw();
|
||||
|
||||
void OnSize(wxSizeEvent & evt);
|
||||
|
||||
@ -55,8 +56,6 @@ public:
|
||||
void CreateCarbonOverlay();
|
||||
void CarbonViewResized();
|
||||
static pascal OSStatus ControlEventHandlerCallback(EventHandlerCallRef handler,
|
||||
EventRef event,
|
||||
void *data);
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
@ -23,7 +23,6 @@
|
||||
|
||||
#include "../../Audacity.h"
|
||||
#include "AUControl.h"
|
||||
|
||||
#include <AudioUnit/AudioUnitProperties.h>
|
||||
#include <AudioUnit/AUCocoaUIView.h>
|
||||
#include <CoreAudioKit/CoreAudioKit.h>
|
||||
@ -36,7 +35,10 @@
|
||||
|
||||
@interface AUView : NSView
|
||||
{
|
||||
@public
|
||||
AUControl *mControl;
|
||||
NSView *mView;
|
||||
bool mRedraw;
|
||||
}
|
||||
@end
|
||||
|
||||
@ -52,12 +54,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithControl:(AUControl *)control
|
||||
- (instancetype)initWithControl:(AUControl *)control;
|
||||
{
|
||||
// Make sure a parameters were provided
|
||||
NSParameterAssert(control);
|
||||
|
||||
mControl = control;
|
||||
mRedraw = NO;
|
||||
|
||||
[super init];
|
||||
|
||||
@ -69,6 +72,30 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)viewWillDraw
|
||||
{
|
||||
// LLL: Hackage alert! I have absolutely no idea why the AudioUnitView doesn't
|
||||
// get redrawn after the ClassInfo is updated, but this bit of malarkey
|
||||
// gets around this issue.
|
||||
//
|
||||
// To see the problem, comment out the setFrameSize calls below, create/save
|
||||
// a preset for the AUDelay effect with "Invert Feedback" checked, restore
|
||||
// factory defaults and then reselect the save preset. The display will not
|
||||
// update properly. But, resize the window and it does.
|
||||
//
|
||||
// Again, this is total hackage and I hope to find the real cause soon.
|
||||
if (mRedraw)
|
||||
{
|
||||
NSRect viewRect = [mView frame];
|
||||
NSRect bogusRect = {};
|
||||
[mView setFrameSize:bogusRect.size];
|
||||
[mView setFrameSize:viewRect.size];
|
||||
mRedraw = NO;
|
||||
}
|
||||
|
||||
[super viewWillDraw];
|
||||
}
|
||||
|
||||
- (void)cocoaViewResized:(NSNotification *)notification
|
||||
{
|
||||
mControl->CocoaViewResized();
|
||||
@ -190,6 +217,8 @@ bool AUControl::Create(wxWindow *parent, AudioComponent comp, AudioUnit unit, bo
|
||||
return false;
|
||||
}
|
||||
|
||||
((AUView *)mAUView)->mView = mView;
|
||||
|
||||
// wxWidgets takes ownership so safenew
|
||||
SetPeer(safenew AUControlImpl(this, mAUView));
|
||||
|
||||
@ -481,6 +510,11 @@ void AUControl::CocoaViewResized()
|
||||
GetEventHandler()->AddPendingEvent(wxSizeEvent(GetSize()));
|
||||
}
|
||||
|
||||
void AUControl::ForceRedraw()
|
||||
{
|
||||
((AUView *)mAUView)->mRedraw = YES;
|
||||
}
|
||||
|
||||
#if !defined(_LP64)
|
||||
|
||||
void AUControl::CreateCarbon()
|
||||
|
@ -72,6 +72,11 @@ struct CFReleaser
|
||||
template <typename T>
|
||||
using CFunique_ptr = std::unique_ptr<T, CFReleaser>;
|
||||
|
||||
// Uncomment to include parameter IDs in the final name. Only needed if it's
|
||||
// discovered that many effects have duplicate names. It could even be done
|
||||
// at runtime by scanning an effects parameters to determine if dups are present
|
||||
// and, if so, enable the clump and parameter IDs.
|
||||
#define USE_EXTENDED_NAMES
|
||||
class ParameterInfo
|
||||
{
|
||||
public:
|
||||
@ -91,7 +96,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool Get(AudioUnit mUnit, AudioUnitParameterID parmId)
|
||||
bool Get(AudioUnit mUnit, AudioUnitParameterID parmID)
|
||||
{
|
||||
OSStatus result;
|
||||
UInt32 dataSize;
|
||||
@ -101,7 +106,7 @@ public:
|
||||
result = AudioUnitGetProperty(mUnit,
|
||||
kAudioUnitProperty_ParameterInfo,
|
||||
kAudioUnitScope_Global,
|
||||
parmId,
|
||||
parmID,
|
||||
&info,
|
||||
&dataSize);
|
||||
if (result != noErr)
|
||||
@ -118,19 +123,36 @@ public:
|
||||
name = wxString(info.name);
|
||||
}
|
||||
|
||||
if (name.empty())
|
||||
#if defined(USE_EXTENDED_NAMES)
|
||||
// If the parameter has a non-empty name, then the final parameter name will
|
||||
// be either:
|
||||
//
|
||||
// <parmID,ParameterName>
|
||||
//
|
||||
// or (if the name isn't available):
|
||||
//
|
||||
// <parmID>
|
||||
if (!name.empty())
|
||||
{
|
||||
return false;
|
||||
name.Replace(idBeg, wxT('_'));
|
||||
name.Replace(idSep, wxT('_'));
|
||||
name.Replace(idEnd, wxT('_'));
|
||||
name.Append(idSep);
|
||||
}
|
||||
name = wxString::Format(wxT("%c%s%x%c"),
|
||||
idBeg,
|
||||
name,
|
||||
parmID,
|
||||
idEnd);
|
||||
|
||||
// If the parameter has a clumpID, then the final parameter name will be
|
||||
// either:
|
||||
//
|
||||
// <clumpID,clumpName>ParameterName
|
||||
// <clumpID,clumpName><parmID,ParameterName>
|
||||
//
|
||||
// or (if the clumpName isn't available):
|
||||
//
|
||||
// <clumpID>ParameterName
|
||||
// <clumpID><parmID,ParameterName>
|
||||
if (info.flags & kAudioUnitParameterFlag_HasClump)
|
||||
{
|
||||
wxString clumpName;
|
||||
@ -148,21 +170,20 @@ public:
|
||||
&dataSize);
|
||||
if (result == noErr)
|
||||
{
|
||||
clumpName.Printf(wxT("%c%s"),
|
||||
idSep,
|
||||
wxCFStringRef::AsString(clumpInfo.outName));
|
||||
clumpName.Replace(idEnd, wxT("_"));
|
||||
clumpName = wxCFStringRef::AsString(clumpInfo.outName);
|
||||
clumpName.Replace(idBeg, wxT('_'));
|
||||
clumpName.Replace(idSep, wxT('_'));
|
||||
clumpName.Replace(idEnd, wxT('_'));
|
||||
clumpName.Append(idSep);
|
||||
}
|
||||
|
||||
name.Replace(idBeg, wxT('_'));
|
||||
name.Replace(idEnd, wxT('_'));
|
||||
name = wxString::Format(wxT("%c%x%s%c%s"),
|
||||
name = wxString::Format(wxT("%c%s%x%c%s"),
|
||||
idBeg,
|
||||
info.clumpID,
|
||||
clumpName,
|
||||
info.clumpID,
|
||||
idEnd,
|
||||
name);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -415,7 +436,6 @@ AudioComponent AudioUnitEffectsModule::FindAudioUnit(const PluginPath & path,
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
name = tokens.GetNextToken();
|
||||
|
||||
return AudioComponentFindNext(NULL, &desc);
|
||||
}
|
||||
|
||||
@ -568,237 +588,6 @@ void AudioUnitEffectOptionsDialog::OnOk(wxCommandEvent & WXUNUSED(evt))
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// AudioUnitEffectExportDialog
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class AudioUnitEffectExportDialog final : public wxDialogWrapper
|
||||
{
|
||||
public:
|
||||
AudioUnitEffectExportDialog(wxWindow * parent, AudioUnitEffect *effect);
|
||||
virtual ~AudioUnitEffectExportDialog();
|
||||
|
||||
void PopulateOrExchange(ShuttleGui & S);
|
||||
wxString Export(const wxString & name);
|
||||
|
||||
void OnOk(wxCommandEvent & evt);
|
||||
|
||||
private:
|
||||
wxWindow *mParent;
|
||||
AudioUnitEffect *mEffect;
|
||||
|
||||
wxListCtrl *mList;
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(AudioUnitEffectExportDialog, wxDialogWrapper)
|
||||
EVT_BUTTON(wxID_OK, AudioUnitEffectExportDialog::OnOk)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
AudioUnitEffectExportDialog::AudioUnitEffectExportDialog(wxWindow * parent, AudioUnitEffect *effect)
|
||||
: wxDialogWrapper(parent, wxID_ANY, XO("Export Audio Unit Presets"))
|
||||
{
|
||||
mEffect = effect;
|
||||
|
||||
ShuttleGui S(this, eIsCreating);
|
||||
PopulateOrExchange(S);
|
||||
}
|
||||
|
||||
AudioUnitEffectExportDialog::~AudioUnitEffectExportDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void AudioUnitEffectExportDialog::PopulateOrExchange(ShuttleGui & S)
|
||||
{
|
||||
S.SetBorder(5);
|
||||
S.StartHorizontalLay(wxEXPAND, 1);
|
||||
{
|
||||
S.StartVerticalLay(true);
|
||||
{
|
||||
S.StartStatic(XO("Presets (may select multiple)"));
|
||||
{
|
||||
mList = S.Style(wxLC_REPORT | wxLC_HRULES | wxLC_VRULES |
|
||||
wxLC_NO_SORT_HEADER)
|
||||
.AddListControlReportMode( { XO("Preset") } );
|
||||
}
|
||||
S.EndStatic();
|
||||
}
|
||||
S.EndVerticalLay();
|
||||
}
|
||||
S.EndHorizontalLay();
|
||||
|
||||
S.AddStandardButtons();
|
||||
|
||||
RegistryPaths presets;
|
||||
|
||||
mEffect->mHost->GetPrivateConfigSubgroups(mEffect->mHost->GetUserPresetsGroup(wxEmptyString), presets);
|
||||
|
||||
std::sort( presets.begin(), presets.end() );
|
||||
|
||||
for (size_t i = 0, cnt = presets.size(); i < cnt; i++)
|
||||
{
|
||||
mList->InsertItem(i, presets[i]);
|
||||
}
|
||||
|
||||
// Set the list size...with a little extra for good measure
|
||||
wxSize sz = mList->GetBestSize();
|
||||
sz.x += 5;
|
||||
sz.y += 5;
|
||||
mList->SetMinSize(sz);
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
Center();
|
||||
|
||||
// Make the single column a reasonable size...not perfect but better
|
||||
// than using wxLIST_AUTOSIZE.
|
||||
sz = mList->GetSize();
|
||||
mList->SetColumnWidth(0, sz.x - 10);
|
||||
}
|
||||
|
||||
wxString AudioUnitEffectExportDialog::Export(const wxString & name)
|
||||
{
|
||||
RegistryPath group = mEffect->mHost->GetUserPresetsGroup(name);
|
||||
|
||||
// Make sure the user preset directory exists
|
||||
wxString path;
|
||||
path.Printf(wxT("%s/%s/%s/%s.aupreset"),
|
||||
PRESET_USER_PATH,
|
||||
mEffect->mVendor,
|
||||
mEffect->mName,
|
||||
name);
|
||||
wxFileName fn(path);
|
||||
fn.Normalize();
|
||||
if (!fn.Mkdir(0755, wxPATH_MKDIR_FULL))
|
||||
{
|
||||
return _("Couldn't create the \"%s\" directory").Format(fn.GetPath());
|
||||
}
|
||||
|
||||
// Create the file
|
||||
const wxString fullPath{fn.GetFullPath()};
|
||||
wxFFile f(fullPath, wxT("wb"));
|
||||
if (!f.IsOpened())
|
||||
{
|
||||
return _("Couldn't open \"%s\"").Format(fullPath);
|
||||
}
|
||||
|
||||
// Retrieve preset from config file
|
||||
wxString parms;
|
||||
if (!mEffect->mHost->GetPrivateConfig(group, PRESET_KEY, parms, wxEmptyString))
|
||||
{
|
||||
return _("Preset key \"%s\" not found in group \"%s\"").Format(PRESET_KEY, group);
|
||||
}
|
||||
|
||||
// Decode it
|
||||
wxMemoryBuffer buf = wxBase64Decode(parms);
|
||||
size_t bufLen = buf.GetDataLen();
|
||||
if (!bufLen)
|
||||
{
|
||||
return _("Failed to decode preset");
|
||||
}
|
||||
const uint8_t *bufPtr = (uint8_t *) buf.GetData();
|
||||
|
||||
// Determine if the data is binary or XML
|
||||
bool isBin = (bufLen >= 6 && memcmp(bufPtr, "bplist", 6) == 0);
|
||||
|
||||
// Convert binary plist to XML
|
||||
if (isBin)
|
||||
{
|
||||
// Create a CFData object that references the decoded preset
|
||||
CFunique_ptr<const __CFData> data
|
||||
{
|
||||
CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
|
||||
bufPtr,
|
||||
bufLen,
|
||||
kCFAllocatorNull)
|
||||
};
|
||||
if (!data)
|
||||
{
|
||||
return _("Failed to convert preset to internal data representation");
|
||||
}
|
||||
|
||||
// Convert it back to a property list.
|
||||
CFPropertyListRef content
|
||||
{
|
||||
CFPropertyListCreateWithData(kCFAllocatorDefault,
|
||||
data.get(),
|
||||
kCFPropertyListImmutable,
|
||||
NULL,
|
||||
NULL)
|
||||
};
|
||||
if (!content)
|
||||
{
|
||||
return _("Failed to create property list from preset data");
|
||||
}
|
||||
CFunique_ptr<char /* CFPropertyList */> ucontent { (char *) content };
|
||||
|
||||
// Serialize it as XML data
|
||||
data.reset(CFPropertyListCreateData(kCFAllocatorDefault,
|
||||
content,
|
||||
kCFPropertyListXMLFormat_v1_0,
|
||||
0,
|
||||
NULL));
|
||||
if (!data)
|
||||
{
|
||||
return _("Failed to convert property list to XML data");
|
||||
}
|
||||
|
||||
// Nothing to do if we don't have any data
|
||||
SInt32 length = CFDataGetLength(data.get());
|
||||
if (!length)
|
||||
{
|
||||
return _("XML data is empty after conversion");
|
||||
}
|
||||
|
||||
// Write XML data
|
||||
if (f.Write(CFDataGetBytePtr(data.get()), length) != length || f.Error())
|
||||
{
|
||||
return _("Failed to write XML preset to \"%s\"").Format(fullPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write XML data
|
||||
if (f.Write(bufPtr, bufLen) != bufLen || f.Error())
|
||||
{
|
||||
return _("Failed to write XML preset to \"%s\"").Format(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
f.Close();
|
||||
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
void AudioUnitEffectExportDialog::OnOk(wxCommandEvent & evt)
|
||||
{
|
||||
evt.Skip();
|
||||
|
||||
// Export all selected presets
|
||||
long sel = -1;
|
||||
while ((sel = mList->GetNextItem(sel, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) >= 0)
|
||||
{
|
||||
wxString name = mList->GetItemText(sel);
|
||||
|
||||
wxString msg = Export(name);
|
||||
|
||||
if (!msg.IsEmpty())
|
||||
{
|
||||
AudacityMessageBox(
|
||||
XO("Could not export \"%s\" preset\n\n%s").Format( name, msg ),
|
||||
XO("Export Audio Unit Presets"),
|
||||
wxOK | wxCENTRE,
|
||||
this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// AudioUnitEffectImportDialog
|
||||
@ -1748,12 +1537,7 @@ bool AudioUnitEffect::SetAutomationParameters(CommandParameters & parms)
|
||||
value,
|
||||
0);
|
||||
|
||||
AudioUnitParameter aup = {};
|
||||
aup.mAudioUnit = mUnit;
|
||||
aup.mParameterID = array[i];
|
||||
aup.mScope = kAudioUnitScope_Global;
|
||||
aup.mElement = 0;
|
||||
AUParameterListenerNotify(NULL, NULL, &aup);
|
||||
Notify(mUnit, array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1794,7 +1578,7 @@ bool AudioUnitEffect::LoadFactoryPreset(int id)
|
||||
return false;
|
||||
}
|
||||
|
||||
AUPreset *preset = (AUPreset *) CFArrayGetValueAtIndex(array, id);
|
||||
const AUPreset *preset = (const AUPreset *) CFArrayGetValueAtIndex(array, id);
|
||||
|
||||
result = AudioUnitSetProperty(mUnit,
|
||||
kAudioUnitProperty_PresentPreset,
|
||||
@ -1804,12 +1588,8 @@ bool AudioUnitEffect::LoadFactoryPreset(int id)
|
||||
sizeof(AUPreset));
|
||||
if (result == noErr)
|
||||
{
|
||||
AudioUnitParameter aup;
|
||||
aup.mAudioUnit = mUnit;
|
||||
aup.mParameterID = kAUParameterListener_AnyParameter;
|
||||
aup.mScope = kAudioUnitScope_Global;
|
||||
aup.mElement = 0;
|
||||
AUParameterListenerNotify(NULL, NULL, &aup);
|
||||
// Notify interested parties of change and propagate to slaves
|
||||
Notify(mUnit, kAUParameterListener_AnyParameter);
|
||||
}
|
||||
|
||||
return result == noErr;
|
||||
@ -1983,37 +1763,91 @@ bool AudioUnitEffect::CanExportPresets()
|
||||
|
||||
void AudioUnitEffect::ExportPresets()
|
||||
{
|
||||
RegistryPaths presets;
|
||||
// Generate the user domain path
|
||||
wxFileName fn;
|
||||
fn.SetPath(PRESET_USER_PATH);
|
||||
fn.AppendDir(mVendor);
|
||||
fn.AppendDir(mName);
|
||||
fn.Normalize();
|
||||
FilePath path = fn.GetFullPath();
|
||||
|
||||
mHost->GetPrivateConfigSubgroups(mHost->GetUserPresetsGroup(wxEmptyString), presets);
|
||||
|
||||
if (presets.size())
|
||||
{
|
||||
AudioUnitEffectExportDialog dlg(mDialog, this);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
else
|
||||
if (!fn.Mkdir(fn.GetFullPath(), 0755, wxPATH_MKDIR_FULL))
|
||||
{
|
||||
AudacityMessageBox(XO("No user presets to export."),
|
||||
XO("Export Presets"),
|
||||
wxOK | wxCENTRE,
|
||||
mDialog);
|
||||
wxLogError(_("Couldn't create the \"%s\" directory").Format(fn.GetPath()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask the user for the name to use
|
||||
//
|
||||
// Passing a valid parent will cause some effects dialogs to malfunction
|
||||
// upon returning from the FileNames::SelectFile().
|
||||
path = FileNames::SelectFile(FileNames::Operation::_None,
|
||||
XO("Export Audio Unit Preset As %s:").Format(fn.GetFullPath()),
|
||||
fn.GetFullPath(),
|
||||
wxEmptyString,
|
||||
wxT("aupreset"),
|
||||
{
|
||||
{ XO("Standard Audio Unit preset file"), { wxT("aupreset") }, true },
|
||||
},
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
|
||||
NULL);
|
||||
|
||||
// User canceled...
|
||||
if (path.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
wxString msg = Export(path);
|
||||
if (!msg.IsEmpty())
|
||||
{
|
||||
AudacityMessageBox(
|
||||
XO("Could not export \"%s\" preset\n\n%s").Format( path, msg ),
|
||||
XO("Export Audio Unit Presets"),
|
||||
wxOK | wxCENTRE,
|
||||
mParent);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioUnitEffect::ImportPresets()
|
||||
{
|
||||
AudioUnitEffectImportDialog dlg(mDialog, this);
|
||||
if (dlg.HasPresets())
|
||||
// Generate the user domain path
|
||||
wxFileName fn;
|
||||
fn.SetPath(PRESET_USER_PATH);
|
||||
fn.AppendDir(mVendor);
|
||||
fn.AppendDir(mName);
|
||||
fn.Normalize();
|
||||
FilePath path = fn.GetFullPath();
|
||||
|
||||
// Ask the user for the name to use
|
||||
//
|
||||
// Passing a valid parent will cause some effects dialogs to malfunction
|
||||
// upon returning from the FileNames::SelectFile().
|
||||
path = FileNames::SelectFile(FileNames::Operation::_None,
|
||||
XO("Import Audio Unit Preset As %s:").Format(fn.GetFullPath()),
|
||||
fn.GetFullPath(),
|
||||
wxEmptyString,
|
||||
wxT("aupreset"),
|
||||
{
|
||||
{ XO("Standard Audio Unit preset file"), { wxT("aupreset") }, true },
|
||||
},
|
||||
wxFD_OPEN | wxRESIZE_BORDER,
|
||||
NULL);
|
||||
|
||||
// User canceled...
|
||||
if (path.empty())
|
||||
{
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
wxString msg = Import(path);
|
||||
if (!msg.IsEmpty())
|
||||
{
|
||||
AudacityMessageBox(XO("No user or local presets to import."),
|
||||
XO("Import Presets"),
|
||||
wxOK | wxCENTRE,
|
||||
mDialog);
|
||||
AudacityMessageBox(
|
||||
XO("Could not import \"%s\" preset\n\n%s").Format( path, msg ),
|
||||
XO("Import Audio Unit Presets"),
|
||||
wxOK | wxCENTRE,
|
||||
mParent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2107,6 +1941,12 @@ bool AudioUnitEffect::LoadPreset(const RegistryPath & group)
|
||||
}
|
||||
CFunique_ptr<char /* CFPropertyList */> ucontent { (char *) content };
|
||||
|
||||
// See AUView::viewWillDraw
|
||||
if (mpControl)
|
||||
{
|
||||
mpControl->ForceRedraw();
|
||||
}
|
||||
|
||||
// Finally, update the properties and parameters
|
||||
OSStatus result;
|
||||
result = AudioUnitSetProperty(mUnit,
|
||||
@ -2121,38 +1961,8 @@ bool AudioUnitEffect::LoadPreset(const RegistryPath & group)
|
||||
return false;
|
||||
}
|
||||
|
||||
// And notify any interested parties
|
||||
AudioUnitParameter aup = {};
|
||||
aup.mAudioUnit = mUnit;
|
||||
aup.mParameterID = kAUParameterListener_AnyParameter;
|
||||
aup.mScope = kAudioUnitScope_Global;
|
||||
aup.mElement = 0;
|
||||
AUParameterListenerNotify(NULL, NULL, &aup);
|
||||
|
||||
// Make sure all slaves get the new preset as well
|
||||
for (size_t i = 0, cnt = mSlaves.size(); i < cnt; i++)
|
||||
{
|
||||
// Finally, update the properties and parameters
|
||||
OSStatus result;
|
||||
result = AudioUnitSetProperty(mSlaves[i]->mUnit,
|
||||
kAudioUnitProperty_ClassInfo,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&content,
|
||||
sizeof(content));
|
||||
if (result != noErr)
|
||||
{
|
||||
wxLogError(wxT("Failed to set slave class info for \"%s\" preset"), group);
|
||||
}
|
||||
|
||||
// And notify any interested parties
|
||||
AudioUnitParameter aup = {};
|
||||
aup.mAudioUnit = mUnit;
|
||||
aup.mParameterID = kAUParameterListener_AnyParameter;
|
||||
aup.mScope = kAudioUnitScope_Global;
|
||||
aup.mElement = 0;
|
||||
AUParameterListenerNotify(NULL, NULL, &aup);
|
||||
}
|
||||
// Notify interested parties of change and propagate to slaves
|
||||
Notify(mUnit, kAUParameterListener_AnyParameter);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -2382,12 +2192,7 @@ bool AudioUnitEffect::CopyParameters(AudioUnit srcUnit, AudioUnit dstUnit)
|
||||
}
|
||||
|
||||
// Notify interested parties
|
||||
AudioUnitParameter aup = {};
|
||||
aup.mAudioUnit = dstUnit;
|
||||
aup.mParameterID = kAUParameterListener_AnyParameter;
|
||||
aup.mScope = kAudioUnitScope_Global;
|
||||
aup.mElement = 0;
|
||||
AUParameterListenerNotify(NULL, NULL, &aup);
|
||||
Notify(dstUnit, kAUParameterListener_AnyParameter);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -2402,6 +2207,158 @@ void AudioUnitEffect::SetChannelCount(unsigned numChannels)
|
||||
mNumChannels = numChannels;
|
||||
}
|
||||
|
||||
wxString AudioUnitEffect::Export(const wxString & path)
|
||||
{
|
||||
// Create the file
|
||||
wxFFile f(path, wxT("wb"));
|
||||
if (!f.IsOpened())
|
||||
{
|
||||
return _("Couldn't open \"%s\"").Format(path);
|
||||
}
|
||||
|
||||
// First set the name of the preset
|
||||
wxCFStringRef cfname(wxFileName(path).GetName());
|
||||
|
||||
// Define the preset property
|
||||
AUPreset preset;
|
||||
preset.presetNumber = -1; // indicates user preset
|
||||
preset.presetName = cfname;
|
||||
|
||||
// And set it in the audio unit
|
||||
OSStatus result;
|
||||
result = AudioUnitSetProperty(mUnit,
|
||||
kAudioUnitProperty_PresentPreset,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&preset,
|
||||
sizeof(preset));
|
||||
if (result != noErr)
|
||||
{
|
||||
return _("Failed to set preset name");
|
||||
}
|
||||
|
||||
// Now retrieve the preset content
|
||||
CFPropertyListRef content;
|
||||
UInt32 size = sizeof(content);
|
||||
result = AudioUnitGetProperty(mUnit,
|
||||
kAudioUnitProperty_ClassInfo,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&content,
|
||||
&size);
|
||||
CFunique_ptr<char /* CFPropertyList */> ucontent { (char *) content };
|
||||
if (result != noErr)
|
||||
{
|
||||
return _("Failed to retrieve preset content");
|
||||
}
|
||||
|
||||
// And convert it to serialized XML data
|
||||
CFunique_ptr<const __CFData> data
|
||||
{
|
||||
CFPropertyListCreateData(kCFAllocatorDefault,
|
||||
content,
|
||||
kCFPropertyListXMLFormat_v1_0,
|
||||
0,
|
||||
NULL)
|
||||
};
|
||||
if (!data)
|
||||
{
|
||||
return _("Failed to convert property list to XML data");
|
||||
}
|
||||
|
||||
// Nothing to do if we don't have any data
|
||||
SInt32 length = CFDataGetLength(data.get());
|
||||
if (!length)
|
||||
{
|
||||
return _("XML data is empty after conversion");
|
||||
}
|
||||
|
||||
// Write XML data
|
||||
if (f.Write(CFDataGetBytePtr(data.get()), length) != length || f.Error())
|
||||
{
|
||||
return _("Failed to write XML preset to \"%s\"").Format(path);
|
||||
}
|
||||
|
||||
f.Close();
|
||||
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
wxString AudioUnitEffect::Import(const wxString & path)
|
||||
{
|
||||
// Open the preset
|
||||
wxFFile f(path, wxT("r"));
|
||||
if (!f.IsOpened())
|
||||
{
|
||||
return _("Couldn't open \"%s\"").Format(path);
|
||||
}
|
||||
|
||||
// Load it into the buffer
|
||||
size_t len = f.Length();
|
||||
wxMemoryBuffer buf(len);
|
||||
if (f.Read(buf.GetData(), len) != len || f.Error())
|
||||
{
|
||||
return _("Unable to read the preset from \"%s\"").Format(path);
|
||||
}
|
||||
|
||||
// Create a CFData object that references the decoded preset
|
||||
CFunique_ptr<const __CFData> data
|
||||
{
|
||||
CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
|
||||
(const UInt8 *) buf.GetData(),
|
||||
len,
|
||||
kCFAllocatorNull)
|
||||
};
|
||||
if (!data)
|
||||
{
|
||||
return _("Failed to convert preset to internal format");
|
||||
}
|
||||
|
||||
// Convert it back to a property list.
|
||||
CFPropertyListRef content
|
||||
{
|
||||
CFPropertyListCreateWithData(kCFAllocatorDefault,
|
||||
data.get(),
|
||||
kCFPropertyListImmutable,
|
||||
NULL,
|
||||
NULL)
|
||||
};
|
||||
if (!content)
|
||||
{
|
||||
return _("Failed to create property list for preset");
|
||||
}
|
||||
CFunique_ptr<char /* CFPropertyList */> ucontent { (char *) content };
|
||||
|
||||
// Finally, update the properties and parameters
|
||||
OSStatus result;
|
||||
result = AudioUnitSetProperty(mUnit,
|
||||
kAudioUnitProperty_ClassInfo,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&content,
|
||||
sizeof(content));
|
||||
if (result != noErr)
|
||||
{
|
||||
return _("Failed to set class info for \"%s\" preset");
|
||||
}
|
||||
|
||||
// Notify interested parties of change and propagate to slaves
|
||||
Notify(mUnit, kAUParameterListener_AnyParameter);
|
||||
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
void AudioUnitEffect::Notify(AudioUnit unit, AudioUnitParameterID parm)
|
||||
{
|
||||
// Notify any interested parties
|
||||
AudioUnitParameter aup = {};
|
||||
aup.mAudioUnit = unit;
|
||||
aup.mParameterID = parm;
|
||||
aup.mScope = kAudioUnitScope_Global;
|
||||
aup.mElement = 0;
|
||||
AUParameterListenerNotify(NULL, NULL, &aup);
|
||||
}
|
||||
|
||||
OSStatus AudioUnitEffect::Render(AudioUnitRenderActionFlags *inActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
|
@ -136,6 +136,9 @@ private:
|
||||
bool SetRateAndChannels();
|
||||
|
||||
bool CopyParameters(AudioUnit srcUnit, AudioUnit dstUnit);
|
||||
wxString Export(const wxString & path);
|
||||
wxString Import(const wxString & path);
|
||||
void Notify(AudioUnit unit, AudioUnitParameterID parm);
|
||||
|
||||
// Realtime
|
||||
unsigned GetChannelCount();
|
||||
|
Loading…
x
Reference in New Issue
Block a user